Как стать автором
Обновить

Создаем приватную сеть Ethereum Geth в контейнерах Docker

Время на прочтение9 мин
Количество просмотров9.5K


Если вам нужно развернуть приватную сеть Ethereum на базе Geth, то это можно сделать, например, создавая узлы сети на физических серверах или виртуальных машинах. Однако намного легче использовать готовые образы (Images) контейнеров Geth из репозитория Docker hub. В этом случае можно установить целую сеть узлов даже на одной виртуальной машине или на одном физическом сервере.

В этой статье мы расскажем как установить Docker на сервере с ОС Debian 9 (виртуальном или физическом), как создать несколько контейнеров с работающим Geth и объединить их в приватную сеть. Мы приведем пример скрипта для Node.js, который обращается к узлам Geth, работающим в контейнерах.

Устанавливаем Docker


Установка Docker описана на официальном сайте. Для Debian 9 и 10 вы найдете подробные инструкции на сайте https://docs.docker.com/install/linux/docker-ce/debian/.

Установка Docker
Прежде всего, обновите пакеты:

# apt-get update

Далее установите необходимые пакеты следующей командой:

# apt-get install apt-transport-https ca-certificates curl gnupg2 software-properties-common

Добавьте официальный ключ GPG:

# curl -fsSL https://download.docker.com/linux/debian/gpg | sudo apt-key add -

Убедитесь, что вы получили ключ с отпечатком 9DC8 5822 9FC7 DD38 854A E2D8 8D81 803C 0EBF CD88:

# apt-key fingerprint 0EBFCD88
pub   4096R/0EBFCD88 2017-02-22
Key fingerprint = 9DC8 5822 9FC7 DD38 854A  E2D8 8D81 803C 0EBF CD88
uid  Docker Release (CE deb) docker@docker.com
sub   4096R/F273FCD8 2017-02-22

Добавьте стабильный репозиторий Docker:

# add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/debian $(lsb_release -cs) stable"

Обновите пакеты и установите Docker:

# apt-get update
# apt-get install docker-ce docker-ce-cli containerd.io

Теперь остается только проверить, что все сделано правильно. Для этого запустите образ (Image) hello-world:

# docker run hello-world

Эта команда загрузит нужный образ, и запустит на выполнение. Как и следовало ожидать, вы увидите на консоли сообщение «Hello from Docker!».

Как видите, ничего сложного! Теперь можно приступить к созданию приватной сети Geth с узлами, работающими в контейнерах.

Создаем пользователя, каталоги и файлы


Создайте на сервере пользователя book, а в его домашнем каталоге — следующие подкаталоги:

/home/book/dock-test
/home/book/dock-test/distr

Далее в каталоге /home/book/dock-test создайте файл Dockerfile:

Листинг 1. Файл /home/book/dock-test/Dockerfile
FROM ubuntu:16.04
LABEL version="1.0"
LABEL maintainer="alexandre@frolov.pp.ru"
ENV DEBIAN_FRONTEND=noninteractive

RUN apt-get update && apt-get install --yes software-properties-common
RUN add-apt-repository ppa:ethereum/ethereum
RUN apt-get update && apt-get install --yes geth
RUN adduser --disabled-login --gecos "" eth_book

COPY distr /home/eth_book/distr
RUN chown -R eth_book:eth_book /home/eth_book/distr

USER eth_book
WORKDIR /home/eth_book
RUN geth --nousb init distr/genesis.json
ENTRYPOINT bash


Этот файл будет использован при создании контейнеров Docker.

Еще вам нужно будет создать файл /home/book/dock-test/distr/genesis.json, необходимый для инициализации узлов сети Ethereum:

Листинг 2. Файл /home/book/dock-test/distr/genesis.json
{
 "config": {
 "chainId": 98760,
 "homesteadBlock": 0,
 "eip150Block": 0,
 "eip155Block": 0,
 "eip158Block": 0
 },
 "difficulty": "10",
 "gasLimit": "5100000",
 "alloc": {}
}


Здесь мы задаем идентификатор нашей приватной сети 98760. Для облегчения майнинга мы указали в параметре difficulty значение 10. Это позволит работать на виртуальных машинах с относительно небольшим объемом памяти (например, 4 Гбайта).

Создаем сеть и контейнеры


Для того чтобы наши узлы могли обмениваться данными между собой, создадим сеть между контейнерами:

# docker network create PRIVATENET

Далее нужно сделать текущим каталог /home/book/dock-test, в котором расположен файл Dockerfile. Затем создайте нужное количество контейнеров, например, три:

# docker build -t node01 .
# docker build -t node02 .
# docker build -t node03 .

Создание первого контейнера идет относительно долго, т.к. нужно загрузить необходимые образы (images). Зато следующие контейнеры создаются практически моментально — сравните со скоростью создания виртуальных машин, даже через клонирование.

Теперь откройте четыре консольных окна. В трех из них мы будем работать с контейнерами, а в четвертом — получать необходимые нам данных о контейнерах и сети.

В первом консольном окне выдайте такую команду:

# docker run --rm -it -p 8545:8545 --net=PRIVATENET node01

Вы увидите приглашение вида:

eth_book@304bf4f09063:~$

Во втором и третьем консольном окне введите, соответственно, следующие команды:

# docker run --rm -it -p 8546:8546 --net=PRIVATENET node02
# docker run --rm -it -p 8547:8547 --net=PRIVATENET node03

Создание узлов приватной сети Ethereum и аккаунтов


На данный момент у нас работают три контейнера с Geth. Давайте создадим в каждом из них узел нашей приватной сети Ethereum и аккаунт.

Введите в первых трех консольных окнах команду:

$ geth account new

Нужно будет ввести пароль. Для простоты при тестах используйте один и тот же пароль на каждом узле, но запишите его где-нибудь. Если вы забудете пароль, то его никак не получится восстановить.

После создания аккаунтов на консоли появятся публичные адреса ключей, похожие на эти:

Public address of the key:   0xc5Df10a76Bb559332c385F8cA789C0F37dD77A54
Public address of the key:   0x0C976006a5762779bA36AC590D1D8Ebac1Ca2981
Public address of the key:   0xaB627feab4e962222a3333F3b09182dF68bB9422

Сохраните адреса (у вас будут другие), т.к. они нам понадобятся для запуска узлов.

Теперь нужно запустить инициализацию узлов. Это делается при помощи команды:

$ geth --nousb init distr/genesis.json

Введите эту команду во всех консольных окнах наших контейнеров. Параметр --nousb отключает все коммуникации с USB-устройствами.

Запуск узлов Geth


Теперь откройте по очереди каждое из консольных окон и запустите там Geth с помощью приведенных ниже команд.

Первое окно:

$ geth --identity="Node01" --etherbase "0xc5Df10a76Bb559332c385F8cA789C0F37dD77A54" --mine --minerthreads 1 --verbosity 3 --networkid 98760 --rpc --rpcaddr 127.0.0.1 --nousb --rpcapi="db,eth,net,web3,personal,web3" console

Второе окно:

$ geth --identity="Node02" --etherbase "0x0C976006a5762779bA36AC590D1D8Ebac1Ca2981" --mine --minerthreads 1 --verbosity 3 --networkid 98760 --rpc --rpcaddr 127.0.0.1 --rpcport=8546 --nousb --rpcapi="db,eth,net,web3,personal,web3" console

Третье окно:

$ geth --identity="Node03" --etherbase "0xaB627feab4e962222a3333F3b09182dF68bB9422" --mine --minerthreads 1 --verbosity 3 --networkid 98760 --rpc --rpcaddr 127.0.0.1 --rpcport=8547 --nousb --rpcapi="db,eth,net,web3,personal,web3" console

В каждом из открытых окон появятся сообщения о генерации DAG:

…
INFO [12-19|17:57:44.072] Generating DAG in progress               epoch=0 percentage=34 elapsed=29.740s
INFO [12-19|17:57:44.898] Generating DAG in progress               epoch=0 percentage=35 elapsed=30.566s
INFO [12-19|17:57:45.671] Generating DAG in progress               epoch=0 percentage=36 elapsed=31.339s
…

Дождитесь, пока генерация будет завершена. После этого eth.hashrate и eth.blockNumber будут отличны от 0:

> eth.hashrate
4
> eth.blockNumber
2

Проверить текущий баланс аккаунта можно так:

> web3.fromWei( eth.getBalance(eth.coinbase) )

Чтобы вам не мешали сообщения о создании новых блоков и другие сообщения в консольных окнах, завершите работу Geth командой exit и перезапустите заново, указав значение параметра verbosity, равным 1.

Объединение узлов в сеть


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

# docker ps
CONTAINER ID        IMAGE               COMMAND             CREATED             STATUS              PORTS                    NAMES
fa70a5418618        node03              "/bin/sh -c bash"   2 hours ago         Up 2 hours          0.0.0.0:8547->8547/tcp   gifted_curran
49a028744b4b        node02              "/bin/sh -c bash"   2 hours ago         Up 2 hours          0.0.0.0:8546->8546/tcp   reverent_wescoff
5a9ade2947eb        node01              "/bin/sh -c bash"   2 hours ago         Up 2 hours          0.0.0.0:8545->8545/tcp   clever_ellis

Здесь для каждого контейнера показывается его идентификатор, название образа, имя контейнера и другая информация.

Для объединения контейнеров в сеть вам потребуются адреса IP контейнеров в нашей приватной сети PRIVATENET. Вы можете получить эти адреса по идентификатору контейнера, например, следующим образом:

# docker inspect 5a9ade2947eb | grep IPAddress
            "SecondaryIPAddresses": null,
            "IPAddress": "",
                    "IPAddress": "172.21.0.2",

Также можно воспользоваться командой docker network inspect PRIVATENET:

docker network inspect PRIVATENET
[
    {
        "Name": "PRIVATENET",
        "Id": "576ec7edba5b4c228740deaf7fabb5e2ba003d310086153dd7f15e2c7de0c1b2",
        "Created": "2019-12-20T11:52:07.90695857+03:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.21.0.0/16",
                    "Gateway": "172.21.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "49a028744b4b6073f6dbca23e78625bc58fc0cdacadec7cded4bb0e888c7e37b": {
                "Name": "reverent_wescoff",
                "EndpointID": "11006b596b5a46df9bf9f95a9456784795d333a3e6901b15bd2db746fd4b5513",
                "MacAddress": "02:42:ac:15:00:03",
                "IPv4Address": "172.21.0.3/16",
                "IPv6Address": ""
            },
            "5a9ade2947ebd8e55594ede9763aac71f5e6529c03e762ef723adb2c592c5ccd": {
                "Name": "clever_ellis",
                "EndpointID": "41ef69a0a93b5b1de495836028bac1742c303de92ffe42a0855ed32c93c28953",
                "MacAddress": "02:42:ac:15:00:02",
                "IPv4Address": "172.21.0.2/16",
                "IPv6Address": ""
            },
            "fa70a54186185de01db3647e7333bf6c71250162fafefb78dbe9998e5ac93f34": {
                "Name": "gifted_curran",
                "EndpointID": "d368c032bc0886c27ad4895d1856e4f00cf1b25ce040f3b42393dbff778c18e5",
                "MacAddress": "02:42:ac:15:00:04",
                "IPv4Address": "172.21.0.4/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

Сопоставляя данные, полученные этими командами, составим список IP-адресов для наших контейнеров:

node01 - 172.21.0.2
node02 - 172.21.0.3
node03 - 172.21.0.4

Разумеется, у вас будет другой список, и при перезапуске контейнеров эти адреса могут меняться.

Получив список адресов, перезапустите geth во всех контейнерах, указав в параметре --rpcaddr адрес своего контейнера. Можно, конечно, задать адрес 0.0.0.0, но это плохо с точки зрения безопасности — к узлу сможет подключиться кто или что угодно. Например, это могут быть боты, которые попытаются «увести» все средства из этих узлов, дождавшись, когда узел будет разблокирован.

Для объединения узлов вам нужно будет воспользоваться командой admin.addPeer. В качестве параметра этой команде нужно передать URL в формате enode. Получите этот URL для каждого контейнера с помощью команды admin.nodeInfo.enode:

> admin.nodeInfo.enode
"enode://0a84e562c9b22e43269b7dca215cf2ed8c20bbf35da67bae8d5ee81b36d8bbb69e3ec704b9b6f7501059fe861843a836b2fbab641f36616cdd77365b1a522d5b@62.152.63.28:30303?discport=1350"

"enode://ee49f69e25c068e006fec4a8d74370370b1d2be9715b86eddd99f97a3a5a9c692a265ab7d01fb36410d59c3f6e2b253a22f652ecbf1941eef0b3f1d30b19a535@62.152.63.28:30303?discport=1345"

"enode://156d43648b47078439c7481e54f697bbf1c6b6e762029ba2969f1556ceb94e51ad03f8bd2bed35f466073165810600f52925d155f0fceef832ae86fc39a8c135@62.152.63.28:30303?discport=1348"

Полученные адреса сохраните.

Для того чтобы подключить один узел к другому, откройте консольное окно первого узла и введите там команду следующего вида:

> admin.addPeer( "enode://0a84e562c9b22e43269b7dca215cf2ed8c20bbf35da67bae8d5ee81b36d8bbb69e3ec704b9b6f7501059fe861843a836b2fbab641f36616cdd77365b1a522d5b@172.21.0.3:30303")

Здесь мы передали адрес URL, указав в нем адрес IP подключаемого узла. Проделайте аналогичную процедуру и на других узлах сети.

Для того чтобы проверить, что соединение установлено, воспользуйтесь командой admin.peers. Если соединений нет, команда вернет пустой результат:

> admin.peers
[]

В нашем случае команда показывает, что первый узел с адресом 172.21.0.2 подключен к узлам с адресами 172.21.0.3 и 172.21.0.4:

Посмотреть результат, когда есть соединения
> admin.peers

[{
    caps: ["eth/63", "eth/64"],
    enode: "enode://156d43648b47078439c7481e54f697bbf1c6b6e762029ba2969f1556ceb94e51ad03f8bd2bed35f466073165810600f52925d155f0fceef832ae86fc39a8c135@172.21.0.4:30303",
    id: "4dac1d10cb6ae8bfc1fdebd3f5334b24ee62ec38a50bc92c89104cfc3251b5fc",
    name: "Geth/Node03/v1.9.9-stable-01744997/linux-amd64/go1.13.4",
    network: {
      inbound: false,
      localAddress: "172.21.0.2:40652",
      remoteAddress: "172.21.0.4:30303",
      static: true,
      trusted: false
    },
    protocols: {
      eth: {
        difficulty: 98414119,
        head: "0x6b31a5bb9cde06fab5a8cc1ae9b18bada30de0d1b76cb3286c1081e76dbf5b83",
        version: 64
      }
    }
}, {
    caps: ["eth/63", "eth/64"],
    enode: "enode://ee49f69e25c068e006fec4a8d74370370b1d2be9715b86eddd99f97a3a5a9c692a265ab7d01fb36410d59c3f6e2b253a22f652ecbf1941eef0b3f1d30b19a535@172.21.0.3:30303",
    id: "b74277d278c15317fa7f7fa492daca60492ea22053bfc53281dd0071eba1c16b",
    name: "Geth/Node02/v1.9.9-stable-01744997/linux-amd64/go1.13.4",
    network: {
      inbound: false,
      localAddress: "172.21.0.2:42576",
      remoteAddress: "172.21.0.3:30303",
      static: true,
      trusted: false
    },
    protocols: {
      eth: {
        difficulty: 99041423,
        head: "0x0ec44735bbb425cb8db96103f52300dfaae1147ba0e03aa4892d041250ce4408",
        version: 64
      }
    }
}]

Обратите внимание, что после успешного объединения узлов и их синхронизации команда eth.blockNumber будет возвращать одно и то же значение при запуске в любом из наших трех контейнеров.

Команда web3.fromWei( eth.getBalance(eth.coinbase) ) напротив, будет показывать разный баланс на разных узлах, т.к. у каждого узла свой собственный аккаунт.

Работа с узлами сети с помощью Node.js


В листинге 3 мы привели простой скрипт, работающий под управлением Node.js, который показывает на консоли список аккаунтов указанного узла и баланс каждого из них:

Листинг 3. Файл /home/book/ list_accounts.js
var Web3 = require('web3')
var web3 = new Web3(new Web3.providers.HttpProvider("http://172.21.0.2:8545"));

web3.eth.getAccounts()
.then(accList => {
    return accList;
})
.then(function (accounts) {
  var balancePromeses = [];
  for(let i = 0; i < accounts.length; i++) {
    balancePromeses[i] = web3.eth.getBalance(accounts[i]);
  }

  Promise.all(balancePromeses).then(values => {
     for(let i = 0; i < values.length; i++) {
       console.log('Account: ', accounts[i], 'balance: ', values[i], 'wei, ', web3.utils.fromWei(values[i], 'ether'), 'ether');
     }
   });
})
.catch(function (error) {
  console.error(error);
});


Этот скрипт вы можете запустить в отдельном, например, в четвертом консольном окне:

# node list_accounts.js
Account:  0x0C976006a5762779bA36AC590D1D8Ebac1Ca2981 balance:  3350000000000000000000 wei,  3350 ether

Что дальше


Тема разработки ПО для криптовалют вообще и для Ethereum в частности довольно увлекательна. Если после прочтения этой статьи у вас появились вопросы и захотелось узнать больше, читайте мою книгу «Создание смарт-контрактов Solidity для блокчейна Ethereum. Практическое руководство», которая вышла в издательстве Литрес.

Также возможно, вам пригодится хорошая шпаргалка по командам Docker.

Буду благодарен за любые замечания и дополнения по статье и книге!
Теги:
Хабы:
Если эта публикация вас вдохновила и вы хотите поддержать автора — не стесняйтесь нажать на кнопку
Всего голосов 4: ↑4 и ↓0+4
Комментарии5

Публикации

Истории

Работа

Ближайшие события

19 сентября
CDI Conf 2024
Москва
24 сентября
Конференция Fin.Bot 2024
МоскваОнлайн
30 сентября – 1 октября
Конференция фронтенд-разработчиков FrontendConf 2024
МоскваОнлайн