DRAFT — черновик (статья находится в процессе доработки)
Децентрализованное приложение очень широкое понятие. Но, поскольку мы работаем со смарт-контрактами эфира, то определим децентрализованное приложение как:
Đapps или Децентрализованное приложение — приложение, которое взаимодействует с блокчейном ethereum с помощью смарт-контрактов.
В разных источниках может встречаться аббревиатура Đapps, Dapp или Đapp. Поскольку это первая статья посвященная децентрализованным приложениям, то мы сначала решим организационные вопросы: знания для понимая статьи и инструменты и их установка. А потом напишем наше первое децентрализованное приложение.
С чем вы должны быть знакомы для понимания статьи
- С web-программированием. Тут потребуется не только опыт в использовании HTML и CSS, но и знакомство с языком JavaScript.
- С платформой nodeJs. Гуру быть в этом вопросе не нужно. Достаточно уметь ставить пакеты с помощью пакетного менеджера и уметь работать в консоле node.
- C написанием смарт-контрактов ethereum. Если не знакомы, то можете прочитать три статьи отсюда. Для начала этого хватит.
- С Apache2 или другим веб-сервером. Нужно уметь запускать веб-сервер и знать куда сохранять файлы проекта.
Инструменты и их установка
Для выполнения примеров нам потребуются инструменты.
- Apache2 — веб сервер, можно и любой другой, главное чтобы вы умели им пользоваться.
- nodeJs — очень популярный JavaScript фреймворк.
- npm — менеджер пакетов для nodeJs. И пакеты:
- web3 версии 0.20.1 — API для работы с нодой ethereum
- solc — solidity компилятор
- testrpc — тестовое окружение. Полностью эмулирует блокчейн эфира для конракта.
К сожалению, установка инструментов выходит за рамки темы этой статьи и может зависеть от типа операционной системы. Поэтому я дам ссылки на официальные сайты, но сам процесс рассматривать не буду (возможно только для ubuntu).
- apache2 — сайт для загрузки. Установка для ubuntu:
1sudo apt-get install apache2 - npm и nodejs версия 6 — сайт для загрузки. Установка для ubuntu:
1234curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -sudo apt-get updatesudo apt-get install -y nodejssudo apt-get install -y build-essential - testrpc — сайт . Тут есть какая-то инструкция для Windows пользователей. Установка для ubunutu:
1sudo npm install -g ethereumjs-testrpc
В статье все примеры будут выполняться в Linux. По-возможности от специфики операционной системы буду отходить.
Теперь можно приступать.
Архитектура DApp
Грубо говоря типовая архитектура децентрализованного приложения состоит из трех частей.
- Нода с блокчейном эфира
- web3.js
- Интерфейс
Сама по себе нода не очень удобна для программирования. Поэтому между интерфейсом и нодой используют web.js.
Наше первое DApp приложение
Наше первое DApp приложение будет читать и записывать строчку приветствия в контракт через web-страничку.
Но для начала мы подготовим проект.
- Создайте папку для проекта. У меня это будет testdapp
- Перейдите в эту папку
- Теперь нам надо инициализировать конфигурацию node проекта. Для этого выполните
1npm init - На все вопросы нажимайте enter. Там будут спрашивать название проекта, описание и так далее. Когда нажимаем enter устанавливаются значения по-умолчанию.
- Теперь необходимо установить два модуля
- Модуль web3 версии 0.20.1. Это и есть тот модуль, который поможет нам взаимодействовать с нодой эфира.
1npm install --save web3@0.20.1 - Модуль solc. С помощью этого модуля мы будем компилировать наши контракты в байткод.
1npm install --save solc
- Модуль web3 версии 0.20.1. Это и есть тот модуль, который поможет нам взаимодействовать с нодой эфира.
Папка с проектом готова. А теперь давайте напишем наш контракт.
1 2 3 4 5 6 7 8 9 10 11 |
pragma solidity ^0.4.16; contract WellcomeContract { string public wellcomeString; function setWellcomeString(string newWellcomeString) { wellcomeString = newWellcomeString; } } |
В нем ничего сложного нет. Сохраните его в файле wellcome.sol. Теперь в отдельной консоле вам нужно запустить testrpc одноименной командой. Таким образом мы запустим тестовое окружение, которое будет играть у нас роль ноды эфира. После запуска testrpc выведет список тестовых аккаунтов.
Мы будем использовать нулевой аккаунт чтобы заливать наш контракт.
Теперь нам надо скомпилировать наш контракт и залить в блокчйен. Делать мы это будем с помощью модуля web3.js. Запускаем nodejs:
1 |
node |
У нас появится консоль nodejs. В которой мы подключим web3js и создадим его инстанс:
1 2 |
Web3 = require('web3') web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")) |
Теперь мы можем работать с web3js. Давайте скомпилируем наш контракт:
1 2 3 |
code = fs.readFileSync('wellcome.sol').toString() solc = require('solc') compiledCode = solc.compile(code) |
Первая строчка — чтение кода из файла в переменную code. Вторая подключение модуля компилятора solc. А третья сама компиляция. Теперь давайте зальем контракт в блокчейн.
1 2 3 4 |
abiDefinition = JSON.parse(compiledCode.contracts[':WellcomeContract'].interface) WellcomeContract = web3.eth.contract(abiDefinition) byteCode = compiledCode.contracts[':WellcomeContract'].bytecode deployedContract = WellcomeContract.new({data: byteCode, from: web3.eth.accounts[0], gas: 300000}) |
В первой строчке мы получаем API интерфейс нашего контракта. На базе этого интерфейса создаем класс контракта во второй строчке. В третьей получаем байткод. А в четвертой заливаем наш контракт в блокчейн от имени аккаунта 0. Количество газа для транзакции можно вычислить в remix.
Давайте выведем адрес нашего контракта в блокчейне. Он нам понадобится при написании web-странички.
1 |
deployedContract.address |
Скопируйте этот адрес куда-нибудь.
Кстати теперь мы можем общаться с контрактом прямо из консоли node. Для этого выполните
1 |
contractInstance = WellcomeContract.at(deployedContract.address) |
К примеру теперь мы можем получить строчку wellcomeString
1 |
contractInstance.wellcomeString() |
На что консоль нам выведет пустую строчку, потому что она у нас не установлена. Таким мы можем вызывать любые функции нашего контракта у contractInstance. Только в отличие от функций чтения, функции для функций на запись придется указывать номер адреса с которого вызываем функцию и количество газа. Как вы уже заметили газ и адрес указываются последним параметром. Давайте установим строчку приветствия в «Hello, world»:
1 |
contractInstance.setWellcomeString("Hello, world", {from: web3.eth.accounts[0], gas: 300000}) |
Если все прошло успешно, то коносль отобразит вам хэш транзакции. А теперь самостоятельно выведите строчку приветствия.
Давайте приступим к написанию HTML странички, которая будет с помощью web3js получать у контракта и выводить строчку приветствия.
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>DApp</title> </head> <body> <div id="wellcomeStringContainer"></div> <script src="https://cdn.rawgit.com/ethereum/web3.js/develop/dist/web3.js"></script> <script src="https://code.jquery.com/jquery-3.1.1.slim.min.js"></script> <script src="./index.js"></script> </body> </html> |
Ничего сложно. Просто подключили web3.js и наш index.js, который мы сейчас напишем. Ну и конечно jQuery для удобств. Строка приветствия будет выводится в контейнер wellcomeStringContainer.
Давайте теперь добавим логику в index.js. У нас после загрузки странички web3.js будет подключаться к нашей ноде testrpc. А она у нас принимает запросы по адресу localhost:8445. После подключения мы с помощью web3.js будем получать wellcomeString и назначать ее контейнеру wellcomeStringContainer.
Для того чтобы мы могли обращаться к методам контракта нам потребуется ABI интерфейс. Для этого выполните
1 |
compiledCode.contracts[':WellcomeContract'].interface |
И в консоль выведется ABI интерфейс. Еще нам нужно получить адрес от которого заливали контракт.
1 |
contractInstance.address |
Теперь мы можем приступать непосредственно к написанию index.js.
1 2 3 4 5 6 7 8 9 |
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); abi = JSON.parse('[{"constant":true,"inputs":[],"name":"wellcomeString","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newWellcomeString","type":"string"}],"name":"setWellcomeString","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]'); WellcomeContract = web3.eth.contract(abi); contractInstance = WellcomeContract.at('0xc04d0c1bb4984b3a158a780ff3bfc0561484917f'); $(document).ready(function() { let val = contractInstance.wellcomeString.call().toString(); $("#wellcomeStringContainer").html(val); }); |
В первой строчке web3.js подключается к нашей ноде. Во второй строчке мы получаем ABI интерфейс нашего контракта и на его основе создаем класс WellcomeContract в третей строчке. В четвертой строчке создаем инстанс WellcomeContract и теперь можем обращаться к функциям нашего контракта. Помните мы получали адрес нашего контракта в блокчейне? В четвертой строчке вам надо заменить адрес на тот который вы получили. Если не сохранили, ничего страшного, просто выведите его снова «contractInstance.address».
Далее идет код, который выполняется после загрузки HTML документа. В нем мы получаем строчку приветствия из конракта. А затем устанавливаем эту строчку HTML элементу с идентификатором wellcomeStringContainer.
Теперь наше децентрализованное приложение готово. Давайте проверим его работу. Для этого включите ваш веб-сервер если он еще не включен. И переместите файлы index.html и index.js в папку вашего веб-сервера. Например для ubuntu эта папка ‘/var/www/html’.
Теперь откройте localhost в браузере и вы увидите строчку «Hello, world».
Наше первое децентрализованное приложение заработало!
Давайте теперь добавим возможность менять строчку приветствия. Для этого в HTML мы добавим элемент ввода и ссылку.
1 2 |
<input type="text" id="wellcomeStringInput"/> <a href="#" onclick="changeWellcomeString()">Изменить строчку</a> |
Когда мы будем нажимать на ссылку будет вызываться функция changeWellcomeString из index.js. А эта функция будет вызывать у нашего контракта setWellcomeString тем самым устанавливая новое значение строки приветствия.
Давайте подправим наш index.js. Во первых код чтения строки из контракта выделим в отдельную функцию updateWellcomString.
1 2 3 4 |
function updateWellcomeString() { let val = contractInstance.wellcomeString.call().toString(); $("#wellcomeStringContainer").html(val); } |
И будем ее вызывать при загрузке веб-страницы:
1 2 3 |
$(document).ready(function() { updateWellcomeString(); }); |
И допишем функцию изменения строки приветствия. При вызове функций на запись в контракт мы должны определить функция-callback которая будет вызвана после записи. Внутри этого callback’а мы вызовем updateWellcomeString чтобы прочитать новое значение строки приветствия и вывести его в веб-страничку. После всех этих манипуляций код index.js будет выглядеть так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
web3 = new Web3(new Web3.providers.HttpProvider("http://localhost:8545")); abi = JSON.parse('[{"constant":true,"inputs":[],"name":"wellcomeString","outputs":[{"name":"","type":"string"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":false,"inputs":[{"name":"newWellcomeString","type":"string"}],"name":"setWellcomeString","outputs":[],"payable":false,"stateMutability":"nonpayable","type":"function"}]'); WellcomeContract = web3.eth.contract(abi); contractInstance = WellcomeContract.at('0xc04d0c1bb4984b3a158a780ff3bfc0561484917f'); function updateWellcomeString() { let val = contractInstance.wellcomeString.call().toString(); $("#wellcomeStringContainer").html(val); } function changeWellcomeString() { newWellcomeString = $("#wellcomeStringInput").val(); contractInstance.setWellcomeString(newWellcomeString, {from: web3.eth.accounts[0]}, function() { updateWellcomeString(); }); } $(document).ready(function() { updateWellcomeString(); }); |
Если вы редактировали index.js и index.html не в папке веб-сервера, то не забудьте эти файлы скопировать в веб-сервер.
Откройте в браузере localhost и попробуйте изменить строчку приветствия.
Теперь наше децентрализованное приложение умеет как читать из контракта, так и записывать в него значения!
Если у вас возникли вопросы то можете смело писать на электронную почту (раздел «контакты«). Также приветствуется критика.
Если уроки оказались вам полезными, то автор не откажется от благодарности) Адрес кошелька Ethereum — 0xEA15Adb66DC92a4BbCcC8Bf32fd25E2e86a2A770.
будет ли продолжение уроков по созданию децентрализованных приложений?
спасибо.
Да. Будет. Пока фокус будет на примеры из практики. Т.е. как вывести progress bar, как отобразить форму для того чтобы узнать сколько начислено токенов.
А можно узнать точные даты продолжения статьи?
Точные, к сожалению сказать не могу. Сильно загружен. Надеюсь в выходные выпустить. Будет скорее всего про реализацию progress bar. По сути это тоже самое что и первый урок, но это одна из типичных задач, которая возникает во время ICO. Может несколько рассмотрю.
У меня работает даже без веб-сервера. Просто открыл file:///C:/testdapp/index.htm — и все заработало.
Уроки отличные. Автору — огромная благодарность. Ждем продолжения)
А почему в статье используется web3@0.20.1 а не web3@1.0.0?
Потому что 1.0.0 — это бета пока еще.
Огромная благодарность за каждую из статей !
Очень доступно и то, что нужно.
Пожалуйста, не останавливайтесь )
почему при вызове setWellcomestring со странички не указано количество gas?
contractInstance.setWellcomeString(newWellcomeString, {from: web3.eth.accounts[0] ??????}, function() {
updateWellcomeString();
Здравствуйте подскажите пожалуйста как можно автоматически компилировать приложение и заливать контракт
Здравствуйте! Возможно ли заказать вам написание смарт контракта?
поправьте пож, что ethereumjs-testrpc пакет заменили на ganache-cli
здравствуйте. при вводе abiDefinition = JSON.parse(compiledCode.contracts[‘:WellcomeContract’].interface) ругается, пишет: TypeError: Cannot read property ‘:WellcomeContract’ of undefined
Что делать, хотя код на Solidity я и копировал тот, что на странице, и сам вводил, всё равно ругается.