Сергей Коба

Сергей Коба

Веб, блокчейн, мобильная разработка и интернет вещей

Веб тимлид в MobiDev. Цель: изучать и учить чему-то новому без остановки. Основные языки: PHP и Ruby. Также интересно: блокчейн, мобильная разработка, IoT и DevOps. Жизненное кредо: я жив, пока я учу что-то новое.

Локальная разработка Ethereum смарт контрактов с помощью Docker

19 января 11:20

Не так давно мне выпал шанс посетить несколько блокчейн ивентов. Быстро меня заинтересовала техническая сторона этой популярной технологии. После разработки простого Bitcoin кошелька настало время чего-то более сложного. В этой статье я поделюсь набором инструментов для компиляции, развертывания и тестирования Ethereum Смарт Контрактов используя Docker.

Иллюстрация от Анны Коба.

Локальный Ethereum блокчейн с помощью Docker

Прежде всего стоит упомянуть, что есть несколько подходов к разработке смарт контрактов:

  1. Использование Ethereum Testnet
  2. Truffle и Ganache - ваш персональный локальный Ethereum подобный блокчейн и полный набор инструментов для смарт контрактов. Лучшая опция для быстрого старта.
  3. Развертывание своего персонального локального Ethereum блокчейна. Наиболее приближенный к реальности подход.

Что касается меня, то я выбрал последний вариант, т.к. хотел построить окружение как можно более приблеженное к реальному Ethereum блокчейну, при этом сохраняя возможность полного контроля над инструментами разработки.

Быстро я понял, что мне понадобится развернуть локально несколько Ethereum нод и что-нибудь для их мониторинга. Когда дело касается исследования новых низведанных технологий я предпочитаю использовать Docker. Ничто так не радует меня как возможность запустить десятки сервисов без малейших знаний об их установке :D Бустрый поиск в интернете дал мне то, что я хотел: готовый Docker Compose Ethereum шаблон для запуска локального блокчейна. Он позволяет запустить необходимое количество Ethereum нод, имеет набор начальных пользователей с ETH на счету и Netstat сервис для мониторинга нод. Круто! Давайте взглянем на docker-compose.yml файл проекта (я пропустил многие строки файла для облегчения понимания)

version: '2'
services:
bootstrap:
build: monitored-geth-client
#...
links:
- netstats
command: '--datadir=~/.ethereum/devchain --nodekeyhex=091bd6067cb4612df85d9c1ff85cc47f259ced4d4cd99816b14f35650f59c322 --rpcapi "db,personal,eth,net,web3" --rpccorsdomain="*" --networkid=456719 --rpc --rpcaddr="0.0.0.0"'
#...
ports:
- "30303:30303"
- "30303:30303/udp"
- "8545:8545"
eth:
build: monitored-geth-client
links:
- bootstrap
- netstats
#...
command: '--datadir=~/.ethereum/devchain --rpccorsdomain="*" --networkid=456719 --rpc --bootnodes="enode://288b97262895b1c7ec61cf314c2e2004407d0a5dc77566877aad1f2a36659c8b698f4b56fd06c4a0c0bf007b4cfb3e7122d907da3b005fa90e724441902eb19e@XXX:30303"'
netstats:
build: eth-netstats
#...
ports:
- "3000:3000"

Bootstrap сервис выступает в роли ноды для RPC вызовов и создает изначальных пользователей. Чтобы приблизить это к реальным условиям добавлен сервис eth, который участвует в валидации транзакций. Оба они привязаны к Netsat сервису для мониторинга.

Все что нужно для запуска локальной копии Ethereum это запустить следующую команду в папке проекта

docker-compose up -d

С помощью следующей команды можно одновременно запустить любое количество нод (запускает 3 eth ноды)

docker-compose scale eth=3

После успешного запуска по адресу http://localhost/3000 (Netstat) Вы должны увидеть нечто похожее

ВАЖНО! чтобы задеплоить смарт контракт и подтвердить транзакции нужно запустить майнинг хотя бы на одной ноде. Этого можно достичь следующими командами

docker exec -it bootstrap geth --datadir=~/.ethereum/devchain attach

miner.start()
exit

Зайдите снова на Netstat, чтобы удостоверится, что майнинг запущен. Итого мы имеем полностью функциональный локальный Ethereum с RPC сервером, доступным по адресу http://localhost:8545.

Компиляция смарт контрактов

Я всегда хотел изучить Go, вдоновленный этой статьей я решил использовать Go как базовый язык для работы со смарт контрактами (деплой, получение и обновление информации).

Прежде всего давайте создадим сервис для компиляции и деплоя смарт контрактов. Для этого нам понадобится solc (Solidity компилятор), Go и Go-Ethereum (официальная реализация Ethereum протокола на Go). Docker образ для такого сервиса мог бы выглядеть подобным образом

# ./builder/Dockerfile
FROM ethereum/solc:stable

# Install Go
RUN apk update && apk add curl git bash mercurial build-base linux-headers bzr 'go=1.8.4-r0' && rm -rf /var/cache/apk/*
ENV GOROOT /usr/lib/go
ENV GOPATH /gopath
ENV GOBIN /gopath/bin
ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin
# Install go-ethereum
RUN go get github.com/ethereum/go-ethereum &&\
cd $GOPATH/src/github.com/ethereum/go-ethereum/ &&\
make &&\
make devtools # Override default solc entrypoint
ENTRYPOINT []

Сервис для этого Dockerfile очень прост

# ./docker-compose.yml
# ...
builder:
build: builder
container_name: builder
links:
- bootstrap
volumes:
- ./contracts:/contracts

Обратите внимания я примонтировал папку ./contracts к папке контейнера /contracts для синхронизации. Теперь все готово для первого смарт контракта. Эта статья не про язык Solidity поэтому для примера используем базовый Hello World (Greeter) смарт контракт из официального туториала

/* ./contracts/samples/greeter/greeter.sol */
contract mortal {
/* Define variable owner of the type address */
address owner;
/* This function is executed at initialization and sets the owner of the contract */
function mortal() { owner = msg.sender; }
/* Function to recover the funds on the contract */
function kill() { if (msg.sender == owner) selfdestruct(owner); }
}
contract greeter is mortal {
/* Define variable greeting of the type string */
string greeting;
/* This runs when the contract is executed */
function greeter(string _greeting) public {
greeting = _greeting;
}
/* Main function */
function greet() constant returns (string) {
return greeting;
}
}

Для взаимодействия с Ethereum блокчейном необходимо использовать RPC ноды.

Однако, разработка кода, который переводит конструкции на языке Go в вызовы RPC процедур и назад это чрезвычайно длогий и хрупкий процесс: баги могут быть обнаружены только во время вызова кода и практически невозможно развивать контракт, т.к. даже малейшее изменение в Solidity может быть болезненным при портировании на Go.

Существует инструмент в go-ethereum, который позволяет генерировать Go биндинги для контракта. Эти Go биндинги содержат методы для деплоя и взаимодействия со смарт контрактами. Следующая команда генерирует биндинги для /contracts/samples/greeter/greeter.sol и сохраняет их в /contracts/samples/greeter/greeter.go

docker-compose run builder abigen --sol=/contracts/samples/greeter/greeter.sol --pkg=main --out=/contracts/samples/greeter/greeter.go

Взгляните на сгенерированный файл. Больше всего нас интересуют методы DeployGreeterNewGreeter и Greet. Они позволяют задеплоить, инициировать объект существующего контракта и вызвать метод greet соответственно.

Деплой смарт контракта

Для деплоя смарт контракта напишем небольшую программу на Go.

/* ./contracts/samples/greeter/deploy_greeter.go */
package main
import (
"os"
"fmt"
"strings"
"log"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
)
const key = "{\"address\":\"007ccffb7916f37f7aeef05e8096ecfbe55afc2f\",\"Crypto\":{\"cipher\":\"aes-128-ctr\",\"ciphertext\":\"44600de3be08153de243f0de344b9841c774a2fcd5b7f3406b9ea9a42c8be97b\",\"cipherparams\":{\"iv\":\"9ca680df904bc0138d2d52f9c432a797\"},\"kdf\":\"scrypt\",\"kdfparams\":{\"dklen\":32,\"n\":262144,\"p\":1,\"r\":8,\"salt\":\"a6524c4ef60060476f2e47dea7f5945aac8159019d3bf5275a683c1970d03c40\"},\"mac\":\"4daf73bc6da7f75beb9a8ea28896c5f1f074fb8cd54880fa9a4e34700dc0ee00\"},\"id\":\"2615dd6a-6d73-4b05-a02d-257b0fd28d3a\",\"version\":3}"
func main() {
conn, err := ethclient.Dial("http://bootstrap:8545")
if err != nil {
fmt.Printf("Cannot connect to node %v", err)
os.Exit(1)
}
auth, errAuth := bind.NewTransactor(strings.NewReader(key), "")
if errAuth != nil {
log.Fatalf("Failed to create new transactor : %v", errAuth)
}
action := os.Args[1]
fmt.Printf("Executing action: %v\n", action)
switch action {
case "deploy":
DoDeploy(auth, conn)
}
}
func DoDeploy(auth *bind.TransactOpts, conn bind.ContractBackend) (common.Address, *Greeter, error) {
addr, _, contract, err := DeployGreeter(auth, conn, "Hello I am your first deployed SC!")
if err != nil {
log.Fatalf("could not deploy contract: %v", err)
}
fmt.Printf("Contract address: %v\n", addr.Hex())
return addr, contract, err
}

На строке 12 объявляется константа key, которая содержит один из созданных Ethereum адресов. Он будет использован для деплоя контракта и будет его владельцем. Строка 14 осуществляет подключение к Ethereum ноде для выполнения RPC вызовов. В нашем случае подключаемся к bootstrap ноде. Программа принимает 1 аргумент и если он равен строке "deploy", то выполняет DoDeploy функцию, которая вызывает функцию DeployGreeter из сгенерированного файла с биндингами и выполняет деплой смарт контракта. Обратите внимание на строку "Hello I am your first deployed SC!", она будет сохранена в переменной greeting внутри смарт контракта. Чтобы запустить программу достаточно выполнить следующую команду

docker-compose run builder bash -c "cd /contracts/samples/greeter/ && go build . && ./greeter deploy"

Запишите адрес смарт контракта, который в случае успеха будет выведен на экран, он будет использован позже для взаимодействия со смарт контрактом.

Взаимодействие со смарт контрактом

Используя биндинги, вызвать функции смарт контракта из Go очень просто. Создадим еще одну функцию DoGreet

/* ./contracts/samples/greeter/deploy_greeter.go */
/* ... */
func DoGreet(conn bind.ContractBackend, addr common.Address) (string, error) {
// Instantiate the contract and display its greeting
greeter, err := NewGreeter(addr, conn)
if err != nil {
log.Fatalf("Failed to instantiate a Greeter contract: %v", err)
}
greeting, err := greeter.Greet(nil)
if err != nil {
log.Fatalf("Failed to retrieve greeter greeting: %v", err)
}
fmt.Println("Contract Greeting:", greeting)
return greeting, err
}

Для инициализации объекта контракта необходим его адрес и объект соединения. На строке 5 выполняется инициализация объекта смарт контракта Greeter. После этого можно с легкостью вызывать его функции. Например получить значение хранимой переменной greeting с помощью функции Greet. Добавим изменения в коде главной программы для вызовы функции DoGreet

/* ./contracts/samples/greeter/deploy_greeter.go */ /* ... */ switch action { case "deploy": DoDeploy(auth, conn) case "greet": hexAddr := os.Args[2] DoGreet(conn, common.HexToAddress(hexAddr)) }

Запуск программы

docker-compose run builder bash -c "cd /contracts/samples/greeter/ && go build . && ./greeter greet 0xFF37a57B8D373518aBE222Db1077eD9A968a5FDf"

Замените 0xFF37a57B8D373518aBE222Db1077eD9A968a5FDf на адрес Вашего смарт контракта. На экране должна появиться надпись "Hello I am your first deployed SC!". Вот и все! Вы задеплоили свой первый смарт контракти вызвали его функцию!

Просмотр блоков и транзакций

Хорошо бы иметь какой-то простой аналог Etherscan для просмотра локального блокчейна. Это будет полезно для просмотра транзакций, баланса, блоков и т.д. Оказалось, очень сложно найти хорошее open-source решения для geth. После нескольких попыток мне удалось найти приемлемое решение ETHExplorer V2. Склонируйте проект в папку explorer-v2. Чтобы запустить его через Docker необходимо сделать 2 изменения. Для начала создать Dockerfile

# ./explorer-v2/Dockerfile
FROM node:6

RUN mkdir -p /usr/src/app
WORKDIR /usr/src/app
COPY . /usr/src/app
RUN npm install && \
node_modules/.bin/bower install --allow-root

И поменять start скрипт package.json на "start": "http-server ./app -a 0.0.0.0 -p 8000 -c-1". Это необходимо, чтобы разрешить подключение к ETHExplorer V2 c любого IP адреса вне докера. Далее создадим сервис

# ./docker-compose.yml
# ... explorer: build: explorer-v2 container_name: explorer command: npm start ports: - "8000:8000"

Перезапустите проект и зайдите в браузере на http://localhost:8000. Вы должны увидеть приложение и суметь просмотреть информацию о блоках, адресах и транзакциях! Например

REST API для смарт контракта

Вы не забыли, что у нас есть Go бмндмнги для смарт контракта? Никто не запрещает нам использовать их в связке с API веб сервером на Go. В целях проверки этого подхода я создал API приложение на BeeGo. Конечно, я сделал из него сервис для docker-compose. На тестовый код можно взглянуть здесь. Важное замечание: помните, что Вам необходимы пакеты go-ethereum для использования биндингов.

Заключение

 Как обычно было очень весело и легко пробовать новые для меня технологии, используя Docker. В результате были созданы следующие сервисы:

  • bootstrap и eth - Ethereum блокчейн ноды;
  • netstat - мониторинг нод;
  • builder - компиляция и деплой смарт контрактов;
  • explorer - просмотр блоков, транзакций и адресов локального блокчейна.

Достоинства: Решение приближено к реальному блокчейну, позволяет создавать свои собственные блокчейны с помощью Docker, имеется полный контроль над инструментами разработки, легко расширяется с помощью сервисов, не нужно устанавливать никакое ПО кроме Docker и Docker Compose.

Недостатки: Нет визуального интерфейса для компиляции и деплоя смарт контрактов (можете сами его разработать или использовать существующий инструмент в качестве сервиса), майнинг иногда весьма ощутимо нагружает компьютер.

Весь исходный код статьи доступен по ссылке.

Назад