Dapp (Decentralized application) - приложения, логика которых построена не на обычных централизованых серверах, а работающие на децентрализованых peer-to-peer сетях.
Примеры Dapp:
При традиционном подходе к разработке, приложение взаимодействует с сервером, который обрабатывает запросы и формирует логику общения с пользователем. В Dapp же всё немного иначе - в роли сервера выступает блокчейн сеть, имеющая функцию смарт-контрактов или похожего функционала.
Как правило, взаимодействие с обычными веб-приложениями строится следующим образом:
А Dapp как правило работают следующим образом:
Мы сфокусируемся на разработке децентрализованных приложений с помощью блокчейна Ethereum и смарт контрактов. Это наиболее популярный и стабильный выбор для реализации Dapp на сегодняшний день.
Ethereum имеет поддержку смарт контрактов - именно их мы будем использовать для реализации логики наших приложений.
Для тестирования наших приложений мы будем пользоваться публичными тестовыми сетями Ethereum. Это позволит нам создавать транзации и ничего не платить за газ. При этом они поддерживают весь функционал mainnet сети.
Тестовые сети:
Все транзакции, проведенные в тестовой сети, бесплатны и никак не влияют на mainnet сеть.
JSON RPC - простой и легковесный протокол удалённого вызова процедур, использующий JSON для кодирования сообщений.
JSON RPC понадобится нам для взаимодействия с узлом Ethereum - большинство ПО для нод (Geth, Parity, PyEthereum, Aleth) поддерживает JSON RPC API. С помощью JSON RPC API мы сможем создавать транзакции и взаимодействовать со смарт контрактами.
Пример запроса к ноде, чтобы узнать цену газа:
{
"jsonrpc": "2.0",
"method": "eth_gasPrice",
"params": [],
"id":73
}
Ответ:
{
"jsonrpc": "2.0",
"id":73,
"result": "0x09184e72a000"
}
Как пользователю проводить операции в блокчейне Ethereum через браузер?
Для этого есть web3.js - коллекция библиотек, позволяющая взаимодействовать с локальными и удаленными узлами Ethereum (использует JSON RPC). С помощью web3.js браузер может создавать транзакции, и следить за состоянием блокчейн сети.
Metamask - это расширение для браузера, встраивающее web3 в браузер, на котором оно установленно. Таким образом, любой пользователь может взаимодействать с блокчейном Ethereum на вашем сайте.
Ropsten test network
в качестве провайдераРассмотрим простое веб-приложение, показывающее номер последнего блока Ethereum.
index.html:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Текущий блок</title>
<script type="text/javascript" src="script.js"></script>
</head>
<body>
<p>Текущий блок: <span id='block' style="font-size: 120%">неизвестно</span></p>
<button id='update-button'>Обновить</button>
</body>
</html>
script.js:
window.addEventListener('load', () => {
window.web3 = new Web3(window.ethereum);
if (window.ethereum) {
try {
await ethereum.enable()
} catch (error) {
alert('MetaMask access is not granted!')
}
button = document.getElementById('update-button')
blockSpan = document.getElementById('block');
button.addEventListener('click', () => {
web3.eth.getBlockNumber(function(err, blockNumber) {
blockSpan.textContent = blockNumber;
});
});
} else {
alert('MetaMask needed!')
}
});
Файлы index.html
и script.js
находятся в директории с названием content
.
Как сервер будем использовать nginx в docker контейнере:
Dockerfile:
FROM nginx
COPY content /var/www/html
COPY default.conf /etc/nginx/conf.d/
Docker build:
docker build -t block-viewer .
Для запуска контейнера (предварительно заменив /path/to/content
на путь до директории content
):
docker run -it -p 8080:80 -v /path/to/content:/var/www/html block-viewer
Разработайте веб-приложение, позволяющее показать текущий баланс пользователя.
nginx
+ docker
web3.eth.sendTransaction({
'to': '0x..', // адрес получателя
'value': 2077 // сумма в сатоши
})
Подробнее с методом sendTransaction
можно познакомиться здесь: https://web3js.readthedocs.io/en/v1.2.1/web3-eth.html#sendtransaction
Модифицируйте ваше веб-приложение из задания №2 таким образом, чтобы пользователь мог:
Обменяйтесь адресами и попробуйте отправить транзакции друг другу.
Solidity - язык программирования, позволяющий разрабатывать смарт-контракты для Ethereum. Компилируется в байткод EVM (Ethereum Virtual Machine).
Документация: https://solidity.readthedocs.io
Пример кода на Solidity:
pragma solidity^0.5.1;
contract Storage {
int public value ;
address public owner;
constructor(int _value) public {
owner = msg.sender;
value = _value;
}
function setValue(int _value) public {
require(msg.sender == owner);
value = _value;
}
}
Данный контракт имеет функционал простого хранилища единственной числовой переменной value
. При этом любой участник блокчейн сети может увидеть это значение, а менять его может только создатель контракта owner
. Создателем считается адрес, создавший транзакцию на создание контракта в блокчейне.
Разберем код по частям:
pragma solidity^0.5.1;
Указание версии компилятора, в данном случае контракт скомпилируется любой версией solc от 0.5.1
до 6.0
.
contract Storage {
int public value ;
address public owner;
Объявляем переменные состояния: целочисленное значение value
и адрес owner
.
Указываем их область видимости, в данном случае public
, при этом геттеры будут сгенерированы автоматически.
constructor(int _value) public {
owner = msg.sender;
value = _value;
}
constructor
- это специальный метод, вызывающийся ровно один раз - при создании контракта. В нашем конструкторе будут инициализироваться значения owner
и value
.
Переменная owner
примет значение адреса отправителя транзакции - msg.sender
(т.е. адреса, который отправил транзакцию на создание контракта).
Переменная value
будет иметь значение, которое создатель отправит в виде аргумента _value
при создании контракта.
function setValue(int _value) public {
Объявляем метод setValue
, принимающий единственный аргумент _value
- новое значение переменной value
. Указываем область видимости public
, чтобы метод можно было вызвать извне.
Прочитать больше про области видимости можно здесь: https://solidity.readthedocs.io/en/v0.5.3/contracts.html#visibility-and-getters
require(msg.sender == owner);
value = _value;
Так как право на изменение переменной имеет только владелец (создатель) контракта, нужно проверить адрес отправителя транзакции. Для этого можно использовать функцию require
, аргументом которой является булевое значение (true
или false
). Если require
вызвать с аргументом false
, транзакция не будет выполнена (будет отвергнута).
Remix - это онлайн IDE, в которой можно разрабатывать, деплоить и тестировать смарт-контракты.
Выберите окружение Solidity
. После этого, в окне компилятора можно отметить поле Auto compile
для удобства.
Чтобы создать новый контракт, нажмите на New File
:
Далее введите название контракта с расширением .sol
. Откроется окно редактора, где можно писать код на Solidity, а также окно компилятора.
Для деплоя (развертывания) контракта в блокчейне и его тестирования, в Remix есть вкладка Deploy & run transactions
:
Storage
, который мы разбирали ранееInjected Web3
можно в поле Environment
)owner
и value
, попробуйте изменить значение value
ABI (Application Binary Interface) - это описание методов контракта в json формате. Это описание позволяет внешним программам (например: веб-сайту, использующем web3) правильно взаимодействовать со смарт-контрактом.
Пример ABI:
[
{
"constant": false,
"inputs": [
{
"internalType": "int256",
"name": "_value",
"type": "int256"
}
],
"name": "setValue",
"outputs": [],
"payable": false,
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "int256",
"name": "_value",
"type": "int256"
}
],
"payable": false,
"stateMutability": "nonpayable",
"type": "constructor"
},
{
"constant": true,
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
},
{
"constant": true,
"inputs": [],
"name": "value",
"outputs": [
{
"internalType": "int256",
"name": "",
"type": "int256"
}
],
"payable": false,
"stateMutability": "view",
"type": "function"
}
]
Как мы видим, ABI показывает:
1. Типы и названия аргументов функции
2. Тип возвращаемых значений
3. Область видимости функции
4. Способность функции изменять состояние контракта
5. Способность функции принимать эфир
BIN - это байткод (bytecode), в котором закодированы инструкции для EVM.
Байткод контракта Storage.sol
, который мы рассматривали ранее:
608060405234801561001057600080fd5b5060405161023d38038061023d8339818101604052602081101561003357600080fd5b810190808051906020019092919050505033600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555080600081905550506101a18061009c6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80633fa4f245146100465780635093dc7d146100645780638da5cb5b14610092575b600080fd5b61004e6100dc565b6040518082815260200191505060405180910390f35b6100906004803603602081101561007a57600080fd5b81019080803590602001909291905050506100e2565b005b61009a610146565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b60005481565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461013c57600080fd5b8060008190555050565b600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff168156fea265627a7a723158207c33070c35f57ab226edc1a386a531f4cd873b74455a8e1809de33b5fd19392964736f6c634300050b0032
Рассмотрим пример работы с контрактом Storage.sol
с помощью web3
, встроенного в браузер пользователя:
abi = "ABI контракта";
address = "адрес контракта";
function callbackSet(err, txHash) {
if (err) {
console.error("Ошибка при отправке транзакции");
} else {
console.log("Хэш транзакции: " + txHash);
}
}
function callbackGet(err, value) {
if (err) {
console.error("Ошибка при получении значения");
} else {
alert("Значение: " + value);
}
}
// Инициализация контракта на определенном адресе
contract = web3.eth.contract(abi).at(address);
// Вызов функции, изменяющей состояние контракта
// в формате contract.methodName.sendTransaction(...args, callback)
contract.setValue.sendTransaction(10, callbackSet);
// Вызов функции, не изменяющей состояние контракта
// в формате contract.methodName.call(...args, callback)
contract.value.call(callbackGet);
Разработайте смарт-контракт, позволяющий хранить эфир на его счету, и при определенной сумме на балансе контракта, позволяющий выводить их на адрес владельца контракта. Разработайте веб-приложение, позволяющее отправить на смарт-контракт средства, а также вывести их.
Подсказка: для отправки транзакции к контракту, нужно правильно указать количество газа, т.к. стандартное количество газа для обычной транзакции - 21000, а вызов метода контракта потребляет больше.
Фреймворк для создания и тестирования смарт-контрактов
Позволяет развертывать локальный тестовый Ethereum блокчейн
Иногда, при создании и тестировании децентрализованных приложений, удобно пользоваться сторонними API, предоставляющими Ethereum узлы, с которыми можно взаимодействовать с помощью JSON RPC. Это позволяет разработчикам не запускать и синхронизировать Etherem узел на своем компьютере.