Первая сеть на Waves Enterprise
Зачем это нужно
В прошлой главе весь блокчейн существовал только на бумаге: хеш, блок, транзакция, консенсус — понятия, которые мы разбирали словами и настоящими, но вручную посчитанными хешами. Пора сделать следующий шаг и увидеть, как всё это устроено внутри настоящей приватной, разрешённой (permissioned) платформы — Waves Enterprise, той самой из сравнения в конце прошлой главы.
Эта глава — про запуск локальной сети из трёх нод у себя на компьютере через Docker, и про то, что вообще происходит, когда в такую сеть разворачивают первый смарт-контракт. Все команды здесь — настоящие терминальные и Docker-команды: в браузере их не исполнить, поэтому вместо запускаемых песочниц (playground) в этой главе — точные посимвольные разборы (SyntaxBreakdown) и тренировка печати (CodeTyping), а все сообщения об ошибках ниже — реальные, снятые вживую с настоящего Docker в этой же сессии. Ничего не считаем очевидным: если в команде есть флаг, у него есть объяснение, зачем он там стоит.
- Зачем это нужно
- Что понадобится
- Что такое нода в Waves Enterprise
- Зачем именно три ноды, а не одна
- docker-compose: поднимаем сеть по шагам
- Локальный Docker registry: зачем он нужен
- Первый смарт-контракт: как это устроено концептуально
- В WebStorm: где в проекте искать декораторы
- Типичные ошибки
- Слова главы
- Проверь себя руками
- Символы и команды главы
- Куда дальше: проверенные ресурсы
- Челлендж ⭐
- Что должен уметь
Что понадобится
Прежде чем поднимать сеть и разворачивать первый контракт, нужны два инструмента: Docker — он запускает ноды и локальный registry, — и IDE с поддержкой TypeScript для кода самого контракта. Площадка чемпионата встречается на разных ОС — Windows, macOS и Ubuntu, — поэтому у Docker ниже три отдельные официальные ссылки, а не одна общая.
Код контракта в этой главе — TypeScript, и писать его можно в любой IDE с его поддержкой. В этой главе и на площадке используется WebStorm — специализированная IDE для JS/TS от JetBrains: понимает декораторы SDK из коробки, знает package.json и npm-скрипты без дополнительной настройки. Подойдёт и VS Code — более лёгкий редактор, который даёт тот же результат после установки расширения TypeScript, но часть возможностей WebStorm в нём нужно донастраивать руками.
Что такое нода в Waves Enterprise
Нода (node) из прошлой главы — здесь не абстракция, а конкретная программа: официальный Docker-образ wavesenterprise/node, упакованный так, что для запуска не нужно ничего компилировать или ставить вручную — достаточно Docker. Каждая запущенная нода:
- хранит собственную полную копию цепочки блоков (тот самый распределённый реестр из прошлой главы);
- принимает транзакции и проверяет их — хеши, подписи, всё то, что мы разбирали руками;
- обменивается новыми блоками с соседними нодами по сети;
- открывает наружу несколько сетевых портов, через которые с ней можно разговаривать: один — REST API (обычный HTTP, через него приложения отправляют транзакции и читают состояние сети), и ещё несколько — для служебного общения нод друг с другом и с работающими на ней смарт-контрактами.
Зачем именно три ноды, а не одна
Одна-единственная нода ничем не отличается от обычной базы данных на одном сервере: если она единственный источник правды, распределённость из прошлой главы попросту не работает — некому сверять хеши и обнаруживать подмену. Минимальное число нод, на котором вообще имеет смысл понятие «согласие большинства» (consensus), — три: тогда большинство — это две ноды из трёх, и сеть продолжает работать и договариваться, даже если одна нода на время отключилась или ведёт себя неправильно. Поэтому все официальные обучающие и демонстрационные окружения Waves Enterprise по умолчанию поднимают именно три ноды — этого достаточно, чтобы вживую увидеть согласование блоков между независимыми участниками, и при этом не тратить лишние ресурсы компьютера.
Проверить это утверждение можно прямо здесь: перед тобой та самая сеть из трёх нод, и любую из них можно выключить кликом.
Квест: выключи ровно одну ноду кликом по ней и отправь транзакцию — сеть из двух оставшихся должна её подтвердить.
docker-compose: поднимаем сеть по шагам
Docker Compose — инструмент, который описывает несколько связанных контейнеров в одном файле docker-compose.yml и запускает их одной командой, вместо того чтобы вручную набирать docker run для каждого сервиса по отдельности. Для сети из трёх нод такой файл описывает три похожих сервиса — node-0, node-1, node-2 — каждый на основе одного и того же образа, но со своими портами и своим набором файлов конфигурации.
Шаг 1. Файл сети: три сервиса в одном docker-compose.yml
Вот как выглядит описание одной ноды внутри такого файла — фрагмент в духе официального образа Waves Enterprise.
Отступы в YAML — не просто оформление, а часть синтаксиса: они показывают, что вложено во что. Сбитый хотя бы на пробел отступ ломает разбор всего файла.
Ниже — реальная ошибка, которую покажет docker compose, если один из отступов сбит хотя бы на пробел (снята вживую в этой сессии):
yaml: while parsing a block mapping at line 2, column 5: line 5, column 6: did not find expected key
Теперь собери этот фрагмент файла сам — строки перемешаны, а отступы в них уже проставлены.
Шаг 2. Конфиги и ключи нод — до первого запуска
Прежде чем запускать сеть, у каждой ноды должен появиться файл настроек (node.conf) и собственная пара криптографических ключей — тех самых, которыми нода будет подписывать и проверять транзакции (вспомни цифровую подпись из прошлой главы). Для этого используют отдельный, одноразовый служебный контейнер-генератор: он один раз запускается, создаёт папку с конфигами и ключами для всех трёх нод и завершает работу — сам в сети не участвует.
Вместе с конфигами генератор создаёт файл с учётными данными каждой ноды: её адрес в сети, пароль от связки ключей, ключ доступа к REST API.
Файл с учётными данными нод — как связка паролей: он должен появиться на диске один раз локально, никогда не попадать в git-репозиторий и не публиковаться никуда. Если он всё же случайно засветился — правильное действие одно: перегенерировать ключи заново, а не «просто удалить файл» — старые ключи к этому моменту уже нельзя считать секретом.
Шаг 3. Проверить активацию нужных возможностей сети
В файле настроек каждой ноды есть раздел, перечисляющий функциональные возможности (features) протокола, которые сеть поддерживает с самого первого блока — например, возможность выполнять смарт-контракты. Список активированных возможностей должен быть одинаковым во всех нодах сети одновременно, иначе ноды перестанут соглашаться друг с другом о содержимом новых блоков. Готовые шаблоны конфигурации обычно уже включают всё нужное для актуальной версии ноды — но если написан свой конфиг «с нуля» или взят шаблон от старой версии образа, стоит свериться с журналом изменений (changelog) именно той версии ноды, которую запускаешь: набор доступных возможностей и их номера меняются от версии к версии.
Шаг 4. Запуск и остановка сети
Когда файл сети и конфиги готовы, саму сеть поднимают и останавливают двумя короткими командами.
Цель: скорость ≥ 100 зн/мин, точность ≥ 90%
docker compose up -d
Для полной остановки и очистки — например, чтобы начать сеть заново с чистого состояния, — используют вторую команду с другим флагом.
Цель: скорость ≥ 100 зн/мин, точность ≥ 90%
docker compose down -v
Все четыре шага вместе — это конкретный порядок команд. Выстрой его сам, от генерации конфигов до полной остановки.
Локальный Docker registry: зачем он нужен
У Waves Enterprise необычная архитектура выполнения смарт-контрактов: сама нода не выполняет код контракта внутри себя — вместо этого она запускает контракт как отдельный Docker-контейнер рядом с собой и общается с ним по сети (тем самым gRPC-портом из первого раздела). Значит, чтобы развернуть контракт, нода должна суметь скачать его образ — точно так же, как она скачивает wavesenterprise/node при первом запуске.
Для локальной сети, которая ни с кем в интернете не связана, публичный Docker Hub для собственных учебных образов не подходит — нужен свой, локальный реестр образов (registry). Он поднимается как ещё один обычный контейнер, официальный образ registry c Docker Hub:
Важно не перепутать термины. В прошлой главе слово «реестр» (ledger) означало учётную книгу всех транзакций блокчейна. Docker registry — это совсем другое: обычное хранилище Docker-образов, к блокчейн-реестру прямого отношения не имеющее. Совпадение перевода — случайность, но в разговоре лучше уточнять: «блокчейн-реестр» или «Docker-реестр образов».
Первый смарт-контракт: как это устроено концептуально
Смарт-контракт (smart contract) в терминах прошлой главы — это ещё один участник сети со своим кодом, который умеет читать и менять состояние, хранящееся в блокчейне, по строго заданным правилам. В экосистеме Waves Enterprise контракты для JavaScript/TypeScript пишут с помощью специального набора инструментов (SDK) с декораторами — короткими пометками над классом и его методами, которые говорят платформе, как именно устроен контракт.
@Contract()
class ScoreCounter {
@State() state
@Var({ key: 'SCORE' })
score
@Action({ onInit: true })
init() {
this.score.set(0)
}
@Action()
increment() {
// прочитать текущее значение и увеличить на единицу
}
}
Разберём назначение декораторов — не как исполняемый код (это TypeScript, а не Kotlin, и в этой главе он не запускается), а как концептуальную схему:
@Contract()— помечает класс как смарт-контракт целиком: именно из него платформа соберёт Docker-образ, который позже запустит рядом с нодой.@State()— доступ к общему состоянию контракта, хранящемуся в блокчейне, а не в оперативной памяти обычной программы.@Var({ key: 'SCORE' })— отдельная именованная переменная внутри состояния контракта: значение под ключомSCOREостанется в блокчейне даже после перезапуска контракта.@Action({ onInit: true })— действие, которое выполнится ровно один раз, в момент создания контракта в сети; здесь удобно задавать значения по умолчанию.@Action()без параметров — обычное действие контракта, которое можно вызывать сколько угодно раз после того, как контракт уже создан и работает.
Разворачивание контракта в сеть и последующий вызов его действий — это два разных вида транзакций, которые ноды проверяют по тем же общим правилам из прошлой главы (подпись, хеш, согласие большинства нод):
- CreateContractTx — «создать контракт»: транзакция, которая регистрирует контракт в сети. Образ к этому моменту уже собран и загружен в registry разработчиком (обычными
docker buildиdocker push) — в самой транзакции лежат только ссылка на образ и его хеш. Получив такую транзакцию, каждая нода скачивает образ по ссылке, сверяет хеш с записанным в блокчейне и один раз выполняет действие сonInit: true. С этого момента у контракта есть свой идентификатор (contract id) в сети. - CallContractTx — «вызвать контракт»: транзакция, которая называет уже существующий контракт по его идентификатору, конкретное действие (например,
increment) и параметры для него. Такую транзакцию, как и обычный перевод из прошлой главы, подписывает отправитель — без действительной подписи нода её не примет.
В WebStorm: где в проекте искать декораторы
Все декораторы из прошлого раздела живут в одном конкретном файле — проще разобраться, когда видно, где он лежит среди остальных. Официальный CLI create-we-contract (часть того же js-contract-sdk) командой npm create we-contract MyContract разворачивает готовый каркас проекта — вот как он выглядит в панели Project слева и с открытым contract.ts справа.
docker compose из этой главы выполняются прямо в нём, без переключения окон.Порядок первого запуска один и тот же вне зависимости от IDE — от открытия папки до нажатия Run. Собери его сам.
Выполнено: 0 из 6
Нажми сочетание: Показать/скрыть панель Project
Сочетание Alt+1 перехватит браузер — здесь его не проверить. Потренируй его в той программе, где оно нужно.
Run/Debug запускает не сам файл, а сохранённую конфигурацию запуска (run configuration) — какой именно npm-скрипт выполнить. В WebStorm открой package.json: рядом с каждым скриптом в разделе scripts (build, deploy, deploy:testnet) появляется значок ▶ — клик по нему создаёт конфигурацию автоматически и сразу запускает скрипт; повторный клик по кнопке Run просто перезапускает уже созданную конфигурацию, не создавая новую.
Типичные ошибки
Все четыре сообщения ниже — реальный вывод настоящего Docker Engine, снятый вживую в этой сессии, а не восстановленный по памяти.
1. Команда запущена не в той папке
no configuration file provided: not found
Перевод: «конфигурационный файл не предоставлен: не найден». docker compose по умолчанию ищет файл docker-compose.yml в текущей папке — и не находит его, если запустить команду не оттуда, где файл лежит, или до того, как файл вообще создан.
Как починить: перейти (cd) в папку, где лежит docker-compose.yml, либо явно указать путь к файлу флагом -f путь/до/docker-compose.yml.
2. Сбитый отступ в docker-compose.yml
yaml: while parsing a block mapping at line 2, column 5: line 5, column 6: did not find expected key
Перевод: «yaml: при разборе блочного отображения в строке 2, столбце 5: строка 5, столбец 6: не найден ожидаемый ключ». YAML определяет вложенность по отступам — если один из элементов списка портов сдвинут не на ту же ширину, что и соседние, парсер теряет структуру файла и не понимает, какому ключу что принадлежит.
Как починить: выровнять отступы во всём файле одинаково — например, ровно по два пробела на уровень вложенности, без табуляций вперемешку с пробелами.
3. Порт уже занят
Bind for 0.0.0.0:6862 failed: port is already allocated
Перевод: «привязка к 0.0.0.0:6862 не удалась: порт уже занят». Один и тот же порт на компьютере не могут слушать два процесса сразу, а порт 6862 уже кем-то занят: контейнером из другой папки (другого проекта Compose), забытым контейнером с прошлого раза или вообще посторонней программой.
Отдельно запомни, чего эта ошибка не значит: повторный docker compose up -d в той же самой папке её не вызывает. Compose узнаёт свои уже запущенные контейнеры по имени проекта и просто печатает Container node-0 Running, ничего не поднимая заново.
Как починить: сначала найти виновника — docker ps --filter publish=6862 покажет контейнер, который держит порт. Дальше либо остановить его (docker stop имя-контейнера, а для чужой сети — docker compose down в её папке), либо поменять в своём docker-compose.yml первое число в паре host:container на свободный порт, например 16862:6862.
4. Опечатка в имени образа
Error response from daemon: pull access denied for wavesenterprise/nonexistent-demo-image, repository does not exist or may require 'docker login'
Перевод: «ответ демона с ошибкой: доступ к скачиванию запрещён для …, репозиторий не существует либо требует docker login». Docker не смог найти образ с указанным именем — либо в имени или теге версии опечатка, либо образ действительно приватный и требует авторизации, которой у нас нет.
Как починить: свериться с точным названием и тегом образа в официальной документации проекта, исправить опечатку в docker-compose.yml; если образ приватный — выполнить docker login с доступом, который действительно есть.
Слова главы
Проверь себя руками
Команду запуска сети — до автоматизма:
Цель: скорость ≥ 120 зн/мин, точность ≥ 90%
docker compose up -d
И полную очистку с удалением томов:
Цель: скорость ≥ 120 зн/мин, точность ≥ 90%
docker compose down -v
А теперь — все команды и строки этой главы вперемешку, с живой клавиатурой:
Цель: скорость ≥ 140 зн/мин, точность ≥ 90%
docker compose up -d
Символы и команды главы
| Команда / флаг | Что делает | Пример |
|---|---|---|
docker compose up | строит и запускает все сервисы из docker-compose.yml | docker compose up -d |
docker compose down | останавливает и удаляет контейнеры, сети и (с -v) тома | docker compose down -v |
-d | detached: запустить контейнер(ы) в фоне, не занимая терминал | docker run -d ... |
-v (у down) | удалить тома с данными вместе с контейнерами | docker compose down -v |
-p host:container | пробросить порт с компьютера внутрь контейнера | -p 5000:5000 |
--name | задать контейнеру человекочитаемое имя | --name registry |
--restart always | перезапускать контейнер автоматически при падении или перезагрузке | docker run --restart always ... |
image: (в YAML) | ключ docker-compose.yml — какой образ использовать для сервиса | image: wavesenterprise/node:v1.16.0 |
ports: (в YAML) | ключ docker-compose.yml — список пробрасываемых портов сервиса | ports: |
Углубиться
Контейнер — не виртуальная машина. У полноценной виртуальной машины — своя операционная система с собственным ядром и драйверами. Контейнер — изолированный процесс, который делит ядро операционной системы хоста с другими контейнерами, поэтому запускается и весит на порядки меньше. Это и позволяет держать на одном учебном ноутбуке одновременно три ноды, служебный генератор конфигов и локальный registry без ощутимой нагрузки.
Registry — не только для приватных сетей. Даже у публичного Docker Hub — та же природа: хранилище образов с версиями (тегами), из которого клиенты их скачивают. Разница только в том, кто имеет право публиковать и скачивать — весь интернет или ограниченный круг участников локальной сети, совсем как с приватным и публичным блокчейном из прошлой главы.
Дальше по теме:
- What is a container? — Docker Docs — официальное определение контейнера и разница с виртуальной машиной.
- registry — официальный образ на Docker Hub — актуальная команда запуска и базовая проверка работы собственного реестра образов.
Куда дальше: проверенные ресурсы
-
waves-enterprise/we-node — GitHub — официальный репозиторий ноды: Docker-образ, шаблоны конфигурации, файл docker-compose.yml для сети из трёх нод.
-
waves-enterprise/js-contract-sdk — GitHub — набор инструментов для разработки контрактов на TypeScript: декораторы
@Contract,@State,@Action, готовый пример и CLI для нового проекта. -
Как развернуть свою блокчейн-платформу на базе технологий Web3 Tech — Хабр — официальная статья команды платформы: пошаговое развёртывание локальной сети из трёх нод, генератор конфигов, консенсус.
-
Смарт-контракты в виде докер-сервисов: как они работают у нас — Хабр — что происходит внутри платформы, когда контракт выполняется как отдельный Docker-контейнер: авторизация, gRPC, параллельность.
-
Docker Compose overview — что такое docker-compose.yml и зачем он нужен, официальными словами.
-
docker compose up — CLI reference — все флаги команды up, включая -d, из первоисточника.
-
docker compose down — CLI reference — все флаги команды down, включая -v, из первоисточника.
-
Meet WebStorm — JetBrains — официальный обзор окна IDE: панель Project, редактор, встроенный терминал.
-
WebStorm — карточка горячих клавиш (Windows/Linux, PDF) — полный список сочетаний по умолчанию, откуда взяты хоткеи этой главы.
-
WebStorm — карточка горячих клавиш (macOS, PDF) — то же самое для раскладки macOS.
-
Видео и материалы сообщества — ниже — ролики по теме и ссылки от студентов и преподавателей смотри в самом низу страницы.
Челлендж ⭐
Ступень 1. Вечером перед защитой проекта на чемпионате ты поднимаешь сеть в папке ~/champ/network — а утром возился с прошлогодней копией той же сети в папке ~/champ/network-old и выключить её забыл. Терминал показал:
Bind for 0.0.0.0:6862 failed: port is already allocated
Опиши словами, что именно произошло, какой командой найти виновника, и предложи два разных рабочих способа исправить ситуацию, не выключая компьютер.
Ступень 2 ⭐. В docker-compose.yml сети node-0 занимает порт 6862, node-1 и node-2 — свои собственные (у каждой ноды в этой сети порты разные, чтобы все три могли работать на одном компьютере одновременно). Тебе нужно добавить в файл ещё один сервис — веб-панель мониторинга, слушающую внутри своего контейнера порт 3000 — и пробросить его наружу на свободный порт 9090 компьютера. Напиши, как будет выглядеть нужная строка в разделе ports: этого нового сервиса, и объясни, что случится при запуске, если по ошибке вместо 9090 написать снова 6862.
Решение (сначала попробуй сам)
Ступень 1: утренняя нода из network-old всё ещё работает и держит порт 6862 на компьютере. Compose считает каждую папку отдельным проектом со своими именами контейнеров, поэтому про соседа он ничего не знает — и честно пытается занять уже занятый порт, а один и тот же порт на хосте не может слушать два процесса одновременно.
Найти виновника: docker ps --filter publish=6862 — в колонке NAMES будет контейнер из network-old. Два рабочих способа: (1) погасить старую сеть — cd ~/champ/network-old && docker compose down (без -v, если данные прошлогодней сети ещё нужны) или точечно docker stop имя-контейнера, после чего спокойно поднять новую; (2) не трогать старую сеть, а в своём docker-compose.yml поменять первое число пары host:container на свободный порт, например 16862:6862 — тогда обе сети живут на одном компьютере, просто REST API новой открыт на 16862.
Чего в этой ситуации точно не было: повторный docker compose up -d в одной и той же папке такой ошибки не даёт — Compose узнаёт свои контейнеры и печатает Container node-0 Running.
Ступень 2 ⭐:
ports:
- "9090:3000"
Если по ошибке написать "6862:6862" вместо "9090:3000", при запуске Docker Compose попытается занять портом мониторинга тот же порт 6862, который уже держит node-0 — и получится ровно та же ошибка port is already allocated, что и в ступени 1, только теперь конфликтуют два сервиса внутри одного файла, а не два разных проекта Compose.
Сводная проверка по всей главе: 7 вопросов, 5:00 на всё, без подсказок по ходу. Вопросы показываются по одному, ответ изменить нельзя. Если время выйдет — экзамен завершится с тем, что успел ответить. Пересдавать можно сколько угодно раз.
Что должен уметь
- Объяснять, что физически представляет собой нода Waves Enterprise, и какие сетевые порты она открывает и зачем.
- Обосновывать, почему минимальная демонстрационная сеть — это три ноды, а не одна и не две.
- Читать файл
docker-compose.ymlдля сети из нескольких нод: назначение ключейimage,ports, вложенность через отступы YAML. - Разбирать по частям команды
docker compose up -dиdocker compose down -v, включая назначение каждого флага. - Объяснять, зачем локальной сети собственный Docker registry и чем он отличается от блокчейн-реестра (ledger) из прошлой главы.
- Описывать концептуально, как устроен смарт-контракт на Waves Enterprise: назначение декораторов
@Contract,@State,@Action, и разницу между CreateContractTx и CallContractTx. - Находить
src/contract.tsв структуре TS-проекта контракта и пользоваться в WebStorm панелью Project, встроенным терминалом и run configuration для npm-скриптов. - Узнавать по тексту четыре типичные ошибки Docker — отсутствие конфигурационного файла, сбитый отступ YAML, занятый порт, опечатку в имени образа — и знать, как каждую починить.
Комментарии
Комментарии появятся после настройки. Нужен аккаунт GitHub — вход прямо в виджете выше.