Neo разрабатывает системы умной экономики, и мы в NeoSPCC помогаем им с этой нелегкой задачей. В нашем блоге вы можете найти статью how we run NeoFS public test net, но это не единственная вещь, над которой мы работаем.
Как вы знаете, сеть состоит из нод. В текущий момент ноды имеют несколько реализаций:
Данная статья посвящена последней реализации, поскольку мы в NeoSPCC занимаемся ее разработкой. Мы надеемся, что данная статья позволит вам понять, как устроена нода neo-go, и поможет научиться писать и разворачивать смрт-контракты.
Главная цель нод - взаимодействовие друг с другом по протоколу P2P и синхронизация блоков в сети. Кроме того, ноды позволяют пользователям компилировать и запускать смарт-контракты в сети блокчейн. Нода состоит из Клиента (CLI), Сетевого слоя, Консенсуса, Виртуальной машины, Компилятора и Блокчейна. Рассмотрим каждую компоненту более подробно.
Клиент (CLI) позволяет пользователям запускать команды в терминале. Команды делятся на 4 категории:
- серверные операции
- операции со смарт контрактами
- операции виртуальной машины
- wallet-операции
Например, чтобы подключить ноду к запущенной частной сети (Private Network), вы можете использовать следующую команду:
go run cli/main.go node -p
Здесь вы можете найти больше информации о Private Network и ее запуске. Проще говоря, private network - это сеть, которую вы можете запустить локально.
Другой пример использования CLI - компиляция смарт-контракта:
$ ./bin/neo-go vm
_ ____________ __________ _ ____ ___
/ | / / ____/ __ \ / ____/ __ \ | | / / |/ /
/ |/ / __/ / / / /_____/ / __/ / / /____| | / / /|_/ /
/ /| / /___/ /_/ /_____/ /_/ / /_/ /_____/ |/ / / / /
/_/ |_/_____/\____/ \____/\____/ |___/_/ /_/
NEO-GO-VM >
После запуска данной команды мы можем взаимодействовать с виртуальной машиной.
Для получения списка поддерживаемых операций используйте help
:
NEO-GO-VM > help
NAME:
VM CLI - Official VM CLI for Neo-Go
USAGE:
[global options] command [command options] [arguments...]
VERSION:
0.98.2
COMMANDS:
exit Exit the VM prompt
ip Show current instruction
break Place a breakpoint
estack Show evaluation stack contents
istack Show invocation stack contents
sslot Show static slot contents
lslot Show local slot contents
aslot Show arguments slot contents
loadnef Load a NEF-consistent script into the VM
loadbase64 Load a base64-encoded script string into the VM
loadhex Load a hex-encoded script string into the VM
loadgo Compile and load a Go file with the manifest into the VM
reset Unload compiled script from the VM
parse Parse provided argument and convert it into other possible formats
run Execute the current loaded script
cont Continue execution of the current loaded script
step Step (n) instruction in the program
stepinto Stepinto instruction to take in the debugger
stepout Stepout instruction to take in the debugger
stepover Stepover instruction to take in the debugger
ops Dump opcodes of the current loaded program
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--help, -h show help
--version, -v print the version
Как вы видите, тут есть с чем поэкспериментировать. Давайте создадим простой смарт-контракт 1-print.go
и скомпилируем его:
package main
import (
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
)
func Main() {
runtime.Log("Hello, world!")
}
Используйте команду loadgo
для компиляции:
NEO-GO-VM > loadgo 1-print.go
READY: loaded 21 instructions
NEO-GO-VM 0 >
Теперь вы можете увидеть, сколько инструкций было сгенерировано. Также вы можете получить опкоды (opcodes) данной программы:
NEO-GO-VM 0 > ops
INDEX OPCODE PARAMETER
0 PUSHDATA1 48656c6c6f2c20776f726c6421 ("Hello, world!") <<
15 SYSCALL System.Runtime.Log (cfe74796)
20 RET
Этот скомпилированный контракт пригодится нам позже =). Больше информации об использовании CLI здесь.
Network-слой - один из самых важных частей ноды. В нашем случае поддерживаются два протокола: протокол P2P позволяет нодам взаимодействовать друг с другом, а протокол RPC используется для получения информации о балансе, аккаунтах, текущем состоянии чейна и т.д. Здесь вы найдете поддерживаемые вызовы RPC.
Консенсус - это механизм, позволяющий нодам приходить к общему значению (блокам в случае блокчейна). Мы используем нашу собственную реализацию алгоритма dBFT.
Компилятор позволяет генерировать байт-код, благодаря чему вы можете писать смарт-контракты на Golang. Все примеры в данном воркшопе были сгенерированы компилятором.
Виртуальная машина запускает скомпилированный байт-код. Виртуальная машина Neo является стековой. Для вычислений в ней содержится два стека.
Блокчейн - достаточно большая часть NeoGo, содержащая в себе операции по принятию и валидации транзакций, их подписи, работе с аккаунтами, ассетами, хранению блоков в базе данных (или в кэше).
Существует 3 типа сетей. Частная сеть (Private net) - это сеть, которую вы можете запустить локально. Тестовая сеть (Testnet) и Основная сеть (Mainnet) - сети, в которых запущены большинство нод Neo по всему миру. Каждую ноду, запущенную в сети блокчейн, вы можете найти в Neo Monitor
Воркшоп содержит обучающее руководство, примеры и подсказки, помогающие начать разработку смарт контрактов и децентрализованных приложений для экосистемы Neo с помощью инструментов, предлагаемых проектом NeoGo. Воркшоп содержит несколько частей:
- Подготовка. В этой части рассказывается, как запустить локальную сеть Neo, перевести средства на аккаунт с помощью NeoGo CLI и проверить баланс с помощью вызова JSON RPC.
- Часть 1. Содержит инструкции для компиляции, исследования, развертывания и вызовов простого
пример контракта
Hello, world!
, написанного на Go. - Часть 2. Помогает познакомиться с протоколом Neo JSON-RPC и утилитами NeoGo CLI для получения информации от RPC-нод Neo. В эту часть включено описание концепции хранилища смарт контрактов. Кроме того, в часть включена инструкция по компиляции, развертыванию и вызовам смарт контракта, который демонстрирует вариант использования своего хранилища.
- Часть 3. Содержит описание стандарта токенов NEP-17 и пример контракта, поддерживающего стандарт NEP-17.
- Часть 4. Резюмирует знания, полученные о смарт контрактах. Содержит инструкции по компиляции, развертыванию и вызовам более сложного смарт контракта.
- Часть 5. Описывает, как разработать простое децентрализованное приложение для экосистемы Neo, используя инструменты NeoGo.
В этой части мы настроим окружение: запустим частную сеть, подсоединим к ней ноду neo-go и переведем немного GAS на аккаунт, с который будем использовать далее для создания транзакций. Давайте начнем.
Для этого воркшопа у вам понадобятся установленные Debian 10, Docker, docker-compose и go:
Если у вас уже установлен neo-go или есть смарт-контракты на go, пожалуйста, обновите go modules чтобы использовать свежую версию API интеропов. Если нет, скачайте neo-go и соберите проект (ветку master):
$ git clone https://github.com/nspcc-dev/neo-go.git
$ cd neo-go
$ make build
Запустим локальную сеть из четырёх узлов в реализации NeoGo.
$ make env_image
$ make env_up
В результате должна запуститься приватная сеть:
=> Bootup environment
Creating network "neo_go_network" with the default driver
Creating volume "docker_volume_chain" with local driver
Creating neo_go_node_four ... done
Creating neo_go_node_two ... done
Creating neo_go_node_one ... done
Creating neo_go_node_three ... done
Для остановки используйте:
$ make env_down
Запустите ноду neo-go, которая подключится к запущенной ранее privatenet:
$ ./bin/neo-go node --privnet
Результат:
2020-12-17T14:51:53.200+0300 INFO no storage version found! creating genesis block
2020-12-17T14:51:53.203+0300 INFO starting rpc-server {"endpoint": ":20331"}
2020-12-17T14:51:53.203+0300 INFO service is running {"service": "Prometheus", "endpoint": ":2112"}
2020-12-17T14:51:53.203+0300 INFO service hasn't started since it's disabled {"service": "Pprof"}
2020-12-17T14:51:53.203+0300 INFO node started {"blockHeight": 0, "headerHeight": 0}
_ ____________ __________
/ | / / ____/ __ \ / ____/ __ \
/ |/ / __/ / / / /_____/ / __/ / / /
/ /| / /___/ /_/ /_____/ /_/ / /_/ /
/_/ |_/_____/\____/ \____/\____/
/NEO-GO:0.91.1-pre-657-gc13d6ecc/
2020-12-17T14:51:53.204+0300 INFO new peer connected {"addr": "127.0.0.1:20333", "peerCount": 1}
2020-12-17T14:51:53.206+0300 INFO started protocol {"addr": "127.0.0.1:20333", "userAgent": "/NEO-GO:0.91.1-pre-657-gc13d6ecc/", "startHeight": 0, "id": 3172166887}
2020-12-17T14:51:54.204+0300 INFO blockchain persist completed {"persistedBlocks": 0, "persistedKeys": 71, "headerHeight": 0, "blockHeight": 0, "took": "765.955µs"}
2020-12-17T14:51:56.204+0300 INFO new peer connected {"addr": "127.0.0.1:20336", "peerCount": 2}
2020-12-17T14:51:56.204+0300 INFO new peer connected {"addr": "127.0.0.1:20334", "peerCount": 3}
2020-12-17T14:51:56.205+0300 INFO new peer connected {"addr": "127.0.0.1:20335", "peerCount": 4}
2020-12-17T14:51:56.205+0300 INFO new peer connected {"addr": "127.0.0.1:20333", "peerCount": 5}
2020-12-17T14:51:56.205+0300 INFO started protocol {"addr": "127.0.0.1:20336", "userAgent": "/NEO-GO:0.91.1-pre-657-gc13d6ecc/", "startHeight": 0, "id": 90708676}
2020-12-17T14:51:56.206+0300 WARN peer disconnected {"addr": "127.0.0.1:20333", "reason": "already connected", "peerCount": 4}
2020-12-17T14:51:56.206+0300 INFO started protocol {"addr": "127.0.0.1:20334", "userAgent": "/NEO-GO:0.91.1-pre-657-gc13d6ecc/", "startHeight": 0, "id": 410946741}
2020-12-17T14:51:56.207+0300 INFO started protocol {"addr": "127.0.0.1:20335", "userAgent": "/NEO-GO:0.91.1-pre-657-gc13d6ecc/", "startHeight": 0, "id": 4085957952}
2020-12-17T14:52:35.213+0300 INFO blockchain persist completed {"persistedBlocks": 1, "persistedKeys": 19, "headerHeight": 1, "blockHeight": 1, "took": "518.786µs"}
2020-12-17T14:52:50.217+0300 INFO blockchain persist completed {"persistedBlocks": 1, "persistedKeys": 19, "headerHeight": 2, "blockHeight": 2, "took": "384.966µs"}
2020-12-17T14:53:05.222+0300 INFO blockchain persist completed {"persistedBlocks": 1, "persistedKeys": 19, "headerHeight": 3, "blockHeight": 3, "took": "496.654µs"}
...
Переведите немного GAS с мультисигового аккаунта на аккаунт, который мы будем использовать в дальнейшем.
-
Создадим транзакцию перевода GAS токенов:
$ ./bin/neo-go wallet nep17 transfer -w .docker/wallets/wallet1.json --out my_tx.json -r http://localhost:20331 --from NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq --to NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB --token GAS --amount 29999999
Где
./bin/neo-go
запускает neo-gowallet nep17 transfer
- команда с аргументами в neo-go-w .docker/wallets/wallet1.json
- путь к кошельку для первой ноды в созданной частной сети--out my_tx.json
- файл для записи подписанной транзакции-r http://localhost:20331
- RPC-эндпоинт ноды--from NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq
- мультисиговый аккаунт, являющийся владельцем GAS--to NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
- наш аккаунт из кошелька--token GAS
- имя переводимого токена (в данном случае это GAS)--amount 29999999
- количество GAS для перевода
Введите пароль
one
:Enter password >
Результатом является транзакция, подписанная первой нодой и сохраненная в
my_tx.json
. -
Подпишите созданную транзакцию, используя адрес второй ноды:
$ ./bin/neo-go wallet sign -w .docker/wallets/wallet2.json --in my_tx.json --out my_tx2.json --address NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq
Где
-w .docker/wallets/wallet2.json
- путь к кошельку для второй ноды в частной сети--in my_tx.json
- транзакция перевода, созданная на предыдущем шаге--out my_tx2.json
- выходной файл для записи подписанной транзакции--address NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq
- мультисиговый аккаунт для подписи транзакции
Введите пароль
two
:Enter password >
Результатом является транзакция, подписанная первой и второй нодой частной сети.
-
Подпишите транзакцию, использую адрес третьей ноды и отправьте ее в цепочку:
$ ./bin/neo-go wallet sign -w ./.docker/wallets/wallet3.json --in my_tx2.json --out my_tx3.json --address NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq -r http://localhost:20331
Введите пароль
three
:Enter password >
Результатом является транзакция, подписанная первой, второй и третьей нодами частной сети, отправленная в цепочку.
-
Проверьте баланс:
На данный момент на балансе аккаунта
NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
должно находиться 29999999 GAS. Чтобы проверить, что трансфер прошел успешно, воспользуйтесьgetnep17transfers
RPC-вызовом:curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getnep17transfers", "params": ["NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB"] }' localhost:20331 | json_pp
Результат должен выглядеть следующим образом:
{
"result" : {
"received" : [
{
"txhash" : "0x7f1a2c41f0c03107f7a44ac510fa95fe11dde4c4994d30d61439f73f27e70f0d",
"transfernotifyindex" : 0,
"transferaddress" : "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq",
"blockindex" : 27,
"timestamp" : 1657014304108,
"amount" : "2999999900000000",
"assethash" : "0xd2a4cff31913016155e38e474a2c06d08be276cf"
}
],
"address" : "NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB",
"sent" : []
},
"jsonrpc" : "2.0",
"id" : 1
}
Теперь все готово для того, чтобы написать, развернуть и вызовать ваш первый смарт-контракт. Начнем!
Используйте представленный в репозитории воркшопа контракт 1-print.go. Его код прост, это "Hello World" из смарт-контракта:
package main
import (
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
)
func Main() {
runtime.Log("Hello, world!")
}
Конфигурация контракта доступна в том же каталоге, 1-print.yml.
Скомпилируйте смарт-контракт "Hello World":
$ ./bin/neo-go contract compile -i 1-print/1-print.go -c 1-print/1-print.yml -m 1-print/1-print.manifest.json
Где
./bin/neo-go
запускает neo-gocontract compile
команда с аргументами из neo-go-i 1-print/1-print.go
путь к смарт-контракту-c 1-print/1-print.yml
путь к конфигурационному файлу-m 1-print/1-print.manifest.json
путь к файлу манифеста, который потребуется в дальнейшем при деплое смарт-контракта
Результат:
Скомпилированный смарт-контракт 1-print.nef
и созданный манифест смарт-контракта 1-print.manifest.json
.
Для просмотра опкодов вы можете воспользоваться командой:
$ ./bin/neo-go contract inspect -i 1-print/1-print.nef
Разверните смарт-контракт в запущенной ранее частной сети:
$ ./bin/neo-go contract deploy -i 1-print/1-print.nef -manifest 1-print/1-print.manifest.json -r http://localhost:20331 -w my_wallet.json
Где
contract deploy
- команда для развертывания-i 1-print/1-print.nef
- путь к смарт-контракту-manifest 1-print/1-print.manifest.json
- файл манифеста смарт-контракта-r http://localhost:20331
- эндпоинт ноды-w my_wallet.json
- кошелек, в котором хранится ключ для подписи транзакции (вы можете взять его из репозитория воркшопа)
Введите пароль qwerty
для аккаунта:
Enter account NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB password >
CLI предложит подтвердить отправку транзакции с указанными комиссиями. Здесь и
далее введите y
для подтверждения:
Network fee: 0.0151452
System fee: 10.0104553
Total fee: 10.0256005
Relay transaction (y|N)> y
Результат:
Sent invocation transaction b0436603d27d14e3aa27280e1bc2cdb17d4def8cb8cda2204c3b6a203203e6bf
Contract: bfad19135422aaddf2fc86f86ec5d4b1371e8e93
На данном этапе ваш контракт ‘Hello World’ развернут и может быть вызван. В следующем шаге вызовем этот контракт.
Вызовите контракт.
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json bfad19135422aaddf2fc86f86ec5d4b1371e8e93 main
Где
contract invokefunction
запускает вызов контракта с заданными параметрами-r http://localhost:20331
определяет эндпоинт RPC, используемый для вызова функции-w my_wallet.json
- кошелекbfad19135422aaddf2fc86f86ec5d4b1371e8e93
хеш контракта, полученный в результате выполнения предыдущей команды (развертывание из шага 6)Main
- вызываемый метод контракта
Введите пароль qwerty
для аккаунта:
Enter account NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB password >
Результат:
Network fee: 0.0117652
System fee: 0.0196731
Total fee: 0.0314383
Relay transaction (y|N)> y
Sent invocation transaction 60fbf79b06714e34a3f55e782bf509eedcc602661e49848f5611aaaf2f3442de
В консоли, где была запущена нода (шаг 5), вы увидите:
2022-07-05T12:52:49.413+0300 INFO runtime log {"tx": "60fbf79b06714e34a3f55e782bf509eedcc602661e49848f5611aaaf2f3442de", "script": "bfad19135422aaddf2fc86f86ec5d4b1371e8e93", "msg": "Hello, world!"}
Что означает, что контракт был выполнен.
На этом все. Вам потребовалось всего 4 шага, чтобы развернуть свой контракт, и это оказалось довольно легко. Спасибо!
В этой части мы выполним несколько RPC вызовов и попробуем написать, задеплоить и вызвать смарт-контракт, использующий хранилище. Начнем!
Давайте рассмотрим более детально, что происходит с нашим смарт-контрактом при развертывании и вызове.
Каждая нода neo-go предоставляет API интерфейс для получения данных о блокчейне.
Данное взаимодействие осуществляется по протоколу JSON-RPC
, использующему HTTP для общения.
Полный Neo JSON-RPC 3.0 API
описан здесь.
RPC-сервер ноды neo-go, запущенной на шаге 5, доступен по localhost:20331
. Давайте выполним несколько вызовов RPC.
GetRawTransaction возвращает информацию о транзакции по ее хешу.
Запросите информацию о нашей разворачивающей транзакции из шага 3:
curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getrawtransaction", "params": ["b0436603d27d14e3aa27280e1bc2cdb17d4def8cb8cda2204c3b6a203203e6bf", 1] }' localhost:20331 | json_pp
Где:
"jsonrpc": "2.0"
- версия протокола"id": 1
- id текущего запроса Contract: bfad19135422aaddf2fc86f86ec5d4b1371e8e93 b0436603d27d14e3aa27280e1bc2cdb17d4def8cb8cda2204c3b6a203203e6bf1
этоverbose
параметр для получения детального ответа в формате json-строки
Результат:
{
"id" : 1,
"result" : {
"nonce" : 2596996162,
"sysfee" : "1001045530",
"size" : 531,
"attributes" : [],
"blocktime" : 1657014649346,
"script" : "DOZ7Im5hbWUiOiJIZWxsb1dvcmxkIGNvbnRyYWN0IiwiYWJpIjp7Im1ldGhvZHMiOlt7Im5hbWUiOiJtYWluIiwib2Zmc2V0IjowLCJwYXJhbWV0ZXJzIjpbXSwicmV0dXJudHlwZSI6IlZvaWQiLCJzYWZlIjpmYWxzZX1dLCJldmVudHMiOltdfSwiZmVhdHVyZXMiOnt9LCJncm91cHMiOltdLCJwZXJtaXNzaW9ucyI6W10sInN1cHBvcnRlZHN0YW5kYXJkcyI6W10sInRydXN0cyI6W10sImV4dHJhIjpudWxsfQxjTkVGM25lby1nby0wLjk5LjEtcHJlLTEwMy1nM2ZiYzEzMzEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUMDUhlbGxvLCB3b3JsZCFBz+dHlkCTKBNVEsAfDAZkZXBsb3kMFP2j+kNG6lMqJY/El92t22Q3yf3/QWJ9W1I=",
"validuntilblock" : 51,
"signers" : [
{
"account" : "0x410b5658f92f9937ed7bdd4ba04c665d3bdbd8ae",
"scopes" : "CalledByEntry"
}
],
"netfee" : "1514520",
"sender" : "NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB",
"version" : 0,
"confirmations" : 15,
"witnesses" : [
{
"invocation" : "DEDAFsqnEFjXighqESMUGAAZxR2vaDBpYfbMgH55C1Q8TFNl5AfQA7Cder+MCDjPLNu7S1KHqwp97XlZK2OpZGnf",
"verification" : "DCEDhEhWuuSSNuCc7nLsxQhI8nFlt+UfY3oP0/UkYmdH7G5BVuezJw=="
}
],
"blockhash" : "0x1fbfb494e669c03a4666fb8b2da9ed2f8205b07aabe55125311bb1f569e83d92",
"hash" : "0xb0436603d27d14e3aa27280e1bc2cdb17d4def8cb8cda2204c3b6a203203e6bf",
"vmstate" : "HALT"
},
"jsonrpc" : "2.0"
}
GetApplicationLog возвращает лог контракта по соответствующему хешу транзакции.
Запросите информацию о контракте для нашей вызывающей транзакции, полученной на шаге 4:
curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["60fbf79b06714e34a3f55e782bf509eedcc602661e49848f5611aaaf2f3442de"] }' localhost:20331 | json_pp
Где в качестве параметра:
60fbf79b06714e34a3f55e782bf509eedcc602661e49848f5611aaaf2f3442de
- хеш вызывающей транзакции из шага 4
Результат:
{
"jsonrpc" : "2.0",
"id" : 1,
"result" : {
"txid" : "0x60fbf79b06714e34a3f55e782bf509eedcc602661e49848f5611aaaf2f3442de",
"executions" : [
{
"gasconsumed" : "1967310",
"trigger" : "Application",
"stack" : [
{
"type" : "Any"
}
],
"vmstate" : "HALT",
"notifications" : []
}
]
}
}
curl -d '{ "jsonrpc": "2.0", "id": 5, "method": "getversion", "params": [] }' localhost:20331
curl -d '{ "jsonrpc": "2.0", "id": 5, "method": "getblockcount", "params": [] }' localhost:20331
curl -d '{ "jsonrpc": "2.0", "id": 5, "method": "getconnectioncount", "params": [] }' localhost:20331
curl -d '{ "jsonrpc": "2.0", "id": 5, "method": "getcontractstate", "params": ["0xbfad19135422aaddf2fc86f86ec5d4b1371e8e93"] }' localhost:20331
Список всех поддерживаемых нодой neo-go вызовов RPC вы найдете здесь.
neo-go CLI предоставляет утилиту query tx
для проверки состояния транзакции. Данная утилита
ипользует вызовы RPC getrawtransaction
и getapplicationlog
для определения состояния транзакции
и выводит детали выполнения транзакции на экран. Используйте команду query tx
для того, чтобы
убедиться, что транзакция была успешно принята в блок:
./bin/neo-go query tx 60fbf79b06714e34a3f55e782bf509eedcc602661e49848f5611aaaf2f3442de -r http://localhost:20331 -v
где
60fbf79b06714e34a3f55e782bf509eedcc602661e49848f5611aaaf2f3442de
- хеш вызывающей транзакции из шага #7-r http://localhost:20331
- адрес и порт RPC узла-v
- флаг для вывода более детальной информации о транзакции (подписантов, комиссий и скрипта)
Результат выполнения данной команды:
Hash: 60fbf79b06714e34a3f55e782bf509eedcc602661e49848f5611aaaf2f3442de
OnChain: true
BlockHash: da4b4959936208b4658136e338bec1608772cdd16124675efc4862d1b576cf74
Success: true
Signer: NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB (None)
SystemFee: 0.0196731 GAS
NetworkFee: 0.0117652 GAS
Script: wh8MBG1haW4MFJOOHjex1MVu+Ib88t2qIlQTGa2/QWJ9W1I=
INDEX OPCODE PARAMETER
0 NEWARRAY0 <<
1 PUSH15
2 PUSHDATA1 6d61696e ("main")
8 PUSHDATA1 938e1e37b1d4c56ef886fcf2ddaa22541319adbf
30 SYSCALL System.Contract.Call (627d5b52)
Поле OnChain
говорит о том, была ли транзакция принята в блок. Поле Success
является индикатором
того, был ли скрипт транзакции выполнен без ошибок, т.е. были ли сохранены изменения, вносимые транзакцией в чейн
и осталась ли VM в состоянии HALT
после выполнения скрипта транзакции.
Давайте изучим еще один пример смарт-контракта: 2-storage.go. Он достаточно простой и, так же как предыдущий, не принимает никаких аргументов. С другой стороны, этот контракт умеет считать количество его вызовов, сохраняя целое число и увеличивая его на 1 после каждого вызова. Подобный контракт будет интересен нам, поскольку он способен хранить значения, т.е. обладает хранилищем, которое является общим для всех вызовов данного контракта.
За наличие хранилища у нашего контракта нужно заплатить дополнительное количество GAS, которое определяется вызываемым методом (например, put) и объемом данных.
В контракте 2-storage.go
также описан специальный метод _deploy
, который выполняется во время развертывания или обновления контракта.
Данный метод не возвращает никаких значений и принимает единственный булевый аргумент, служащий индикатором обновления контракта.
Метод _deploy
в нашем контракте предназначен для первичной инициализации счетчика вызовов контракта во время его развертывания.
Теперь, когда мы узнали о хранилище, давайте скомпилируем, развернем и вызовем смарт-контракт.
Скомпилируйте смарт-контракт 2-storage.go:
$ ./bin/neo-go contract compile -i 2-storage/2-storage.go -c 2-storage/2-storage.yml -m 2-storage/2-storage.manifest.json
Результат:
Скомпилированный смарт-контракт 2-storage.nef
и манифест 2-storage.manifest.json
в каталоге 2-storage
.
Разверните скомпилированный смарт-контракт:
$ ./bin/neo-go contract deploy -i 2-storage/2-storage.nef -manifest 2-storage/2-storage.manifest.json -r http://localhost:20331 -w my_wallet.json
... введите пароль qwerty
:
Enter account NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB password >
Результат:
Network fee: 0.0210952
System fee: 10.0624424
Total fee: 10.0835376
Relay transaction (y|N)> y
Sent invocation transaction 58b98332ad8456da1ab6c9d162c1c557ed2ef85a67ae6bbec17e33cdad599c25
Contract: ccd533440d0317e9f366c50648d0013540e82741
Что означает, что наш контракт развернут, и теперь мы можем вызывать его.
Давайте проверим, что значение количества вызовов контракта было проинициализировано. Используйте для этого RPC-вызов getapplicaionlog
с хешем развертывающей транзакции в качестве параметра:
curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["58b98332ad8456da1ab6c9d162c1c557ed2ef85a67ae6bbec17e33cdad599c25"] }' localhost:20331 | json_pp
Результат:
{
"jsonrpc" : "2.0",
"result" : {
"txid" : "0x58b98332ad8456da1ab6c9d162c1c557ed2ef85a67ae6bbec17e33cdad599c25",
"executions" : [
{
"vmstate" : "HALT",
"notifications" : [
{
"contract" : "0xccd533440d0317e9f366c50648d0013540e82741",
"state" : {
"value" : [
{
"type" : "Buffer",
"value" : "U3RvcmFnZSBrZXkgbm90IHlldCBzZXQuIFNldHRpbmcgdG8gMA=="
}
],
"type" : "Array"
},
"eventname" : "info"
},
{
"contract" : "0xccd533440d0317e9f366c50648d0013540e82741",
"eventname" : "info",
"state" : {
"type" : "Array",
"value" : [
{
"value" : "U3RvcmFnZSBrZXkgaXMgaW5pdGlhbGlzZWQ=",
"type" : "Buffer"
}
]
}
},
{
"contract" : "0xfffdc93764dbaddd97c48f252a53ea4643faa3fd",
"state" : {
"value" : [
{
"value" : "QSfoQDUB0EgGxWbz6RcDDUQz1cw=",
"type" : "ByteString"
}
],
"type" : "Array"
},
"eventname" : "Deploy"
}
],
"stack" : [
...skipped serialized contract representation...
],
"gasconsumed" : "1006244000",
"trigger" : "Application"
}
]
},
"id" : 1
}
Обратите внимание на поле notifications
: оно содержит два уведомления info
с сообщениями в base64.
Чтобы декодировать сообщения, испоьзуйте команду echo string | base64 -d
:
$ echo U3RvcmFnZSBrZXkgbm90IHlldCBzZXQuIFNldHRpbmcgdG8gMA== | base64 -d
результат: Storage key not yet set. Setting to 0
.
$ echo U3RvcmFnZSBrZXkgaXMgaW5pdGlhbGlzZWQ= | base64 -d
результат: Storage key is initialised
.
Поскольку мы не вызывали наш смарт-контракт раньше, при первом вызове он должен инкрементировать лежащее в хранилище значение 0
и положить новое значение = 1 в хранилище.
Давайте проверим:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json ccd533440d0317e9f366c50648d0013540e82741 main
... введите пароль qwerty
:
Enter account NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB password >
Результат:
Network fee: 0.0117652
System fee: 0.0717313
Total fee: 0.0834965
Relay transaction (y|N)> y
Sent invocation transaction bf92dbe258d9113f2a0684b6c782566b5b7bcda7524b349e085beb99147d8dc1
Для проверки значения счетчика вызовем getapplicaionlog
вызов RPC для вызывающей транзакции:
curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["bf92dbe258d9113f2a0684b6c782566b5b7bcda7524b349e085beb99147d8dc1"] }' localhost:20331 | json_pp
Результат:
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"txid" : "0xbf92dbe258d9113f2a0684b6c782566b5b7bcda7524b349e085beb99147d8dc1",
"executions" : [
{
"trigger" : "Application",
"gasconsumed" : "7173130",
"notifications" : [
{
"eventname" : "info",
"contract" : "0xccd533440d0317e9f366c50648d0013540e82741",
"state" : {
"type" : "Array",
"value" : [
{
"value" : "VmFsdWUgcmVhZCBmcm9tIHN0b3JhZ2U=",
"type" : "ByteString"
}
]
}
},
{
"state" : {
"value" : [
{
"value" : "U3RvcmFnZSBrZXkgYWxyZWFkeSBzZXQuIEluY3JlbWVudGluZyBieSAx",
"type" : "ByteString"
}
],
"type" : "Array"
},
"eventname" : "info",
"contract" : "0xccd533440d0317e9f366c50648d0013540e82741"
},
{
"eventname" : "info",
"contract" : "0xccd533440d0317e9f366c50648d0013540e82741",
"state" : {
"type" : "Array",
"value" : [
{
"type" : "ByteString",
"value" : "TmV3IHZhbHVlIHdyaXR0ZW4gaW50byBzdG9yYWdl"
}
]
}
}
],
"vmstate" : "HALT",
"stack" : [
{
"type" : "Integer",
"value" : "1"
}
]
}
]
}
}
Обратите внимание на поле notifications
. Оно содержит сообщения, переданные методу runtime.Notify
.
В нашем случае в нем находятся три массива байт в base64, которые можно декодировать в 3 сообщения с помощью
echo string | base64 -d
команды CLI, например:
$ echo VmFsdWUgcmVhZCBmcm9tIHN0b3JhZ2U= | base64 -d
даст в результате:
Value read from storage
Используя эту команду, декодируем сообщения:
Value read from storage
, которое было вызвано после того как мы попытались достать значение счетчика из хранилищаStorage key already set. Incrementing by 1
, которое было вызвано после того, как мы поняли, что полученное значение = 0New value written into storage
, которое было вызвано после того, как мы записали новое значение в хранилище
И последняя часть - поле stack
. Данное поле содержит все возвращенные контрактом значения, поэтому здесь вы можете увидеть целое 1
,
которое является значением счетчика, определяющего количество вызовов смарт-контракта.
Для того чтобы убедиться, что все работает как надо, давайте вызовем наш контракт еще раз и проверим, что счетчик будет увеличен:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json ccd533440d0317e9f366c50648d0013540e82741 main
... введите пароль qwerty
:
Enter account NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB password >
Результат:
Network fee: 0.0117652
System fee: 0.0717313
Total fee: 0.0834965
Relay transaction (y|N)> y
Sent invocation transaction 4edf5fcceef8fddd6c145cafaad52fee6ebe83f166d28c1ad862349182f5550d
Для проверки значения счетчика, выполните getapplicaionlog
вызов RPC для вызывающей транзакции:
curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["4edf5fcceef8fddd6c145cafaad52fee6ebe83f166d28c1ad862349182f5550d"] }' localhost:20331 | json_pp
Результат:
{
"id" : 1,
"result" : {
"txid" : "0x4edf5fcceef8fddd6c145cafaad52fee6ebe83f166d28c1ad862349182f5550d",
"executions" : [
{
"vmstate" : "HALT",
"notifications" : [
{
"state" : {
"value" : [
{
"type" : "ByteString",
"value" : "VmFsdWUgcmVhZCBmcm9tIHN0b3JhZ2U="
}
],
"type" : "Array"
},
"contract" : "0xccd533440d0317e9f366c50648d0013540e82741",
"eventname" : "info"
},
{
"eventname" : "info",
"contract" : "0xccd533440d0317e9f366c50648d0013540e82741",
"state" : {
"value" : [
{
"value" : "U3RvcmFnZSBrZXkgYWxyZWFkeSBzZXQuIEluY3JlbWVudGluZyBieSAx",
"type" : "ByteString"
}
],
"type" : "Array"
}
},
{
"eventname" : "info",
"contract" : "0xccd533440d0317e9f366c50648d0013540e82741",
"state" : {
"value" : [
{
"type" : "ByteString",
"value" : "TmV3IHZhbHVlIHdyaXR0ZW4gaW50byBzdG9yYWdl"
}
],
"type" : "Array"
}
}
],
"stack" : [
{
"value" : "2",
"type" : "Integer"
}
],
"trigger" : "Application",
"gasconsumed" : "7173130"
}
]
},
"jsonrpc" : "2.0"
}
Теперь поле stack
содержит значение 2
- счетчик был увеличен, как мы и ожидали.
В этой части мы узнаем о стандарте токена NEP-17 и попробуем написать, задеплоить и вызвать более сложный смарт-контракт. Начнем!
NEP-17 - это стандарт токена блокчейна Neo, обеспечивающий системы обобщенным механизмом взаимодействия для токенизированных смарт-контрактов. Пример с реализацией всех требуемых стандартом методов вы можете найти в nep17.go
Давайте посмотрим на пример смарт-контракта NEP-17: token.go
Этот смарт-контракт принимает в качестве параетра строку с операцией, которая может принимать следующие значения:
symbol
возвращает код токенаdecimals
возвращает количество десятичных знаков токенаtotalSupply
возвращает общий множитель * токенаbalanceOf
возвращает баланс токена, находящегося на указанном адресе и требует дополнительног аргумента:account
адрес запрашиваемого аккаунта
transfer
переводит токен от одного пользователя к другому и требует дополнительных аргументов:from
адрес аккаунта, с которого будет списан токенto
адрес аккаунта, на который будет зачислен токенamount
количество токена для переводаdata
любая дополнительная информация, которая будет передана методуonPayment
(если получатель является контрактом)
Давайте проведем несколько операций с помощью этого контракта.
Для компиляции token.go можно использовать файл конфигурации. Поскольку данный контракт использует хранилище, необходимо установить флаг
Скомпилируйте смарт-контракт:
$ ./bin/neo-go contract compile -i examples/token/token.go -c examples/token/token.yml -m examples/token/token.manifest.json
Разверните скомпилированный контракт:
$ ./bin/neo-go contract deploy -i examples/token/token.nef -manifest examples/token/token.manifest.json -r http://localhost:20331 -w my_wallet.json
... введите пароль qwerty
:
Enter account NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB password >
Результат:
Network fee: 0.0308452
System fee: 10.0107577
Total fee: 10.0416029
Relay transaction (y|N)> y
Sent invocation transaction ab6f934a5e2137d008613977b41b0a791e5497c2e97a2a84aed0bb684af2c5c3
Contract: c36534b6b81621178980438c18796f23a463441a
Что означает, что наш контракт был развернут, и теперь мы можем вызывать его.
Давайте вызовем контракт для осуществления операций с NEP-17.
Для начала, запросите символ созданного токена NEP-17:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json c36534b6b81621178980438c18796f23a463441a symbol
Где
c36534b6b81621178980438c18796f23a463441a
- хеш нашего контракта, полученный на шаге #1.name
- строка операции, описанная ранее и возвращающая символ токена.
... не забудьте пароль от аккаунта qwerty
.
Результат:
Network fee: 0.0117852
System fee: 0.0141954
Total fee: 0.0259806
Relay transaction (y|N)> y
Sent invocation transaction 1e9d018b4ea8fc3442229ae437bc8451e876216dd04a9c071672753437c9ada5
Теперь давайте подробнее посмотрим на полученную вызывающую транзакцию с помощью getapplicationlog
RPC-вызова:
curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["1e9d018b4ea8fc3442229ae437bc8451e876216dd04a9c071672753437c9ada5"] }' localhost:20331 | json_pp
Результат:
{
"id" : 1,
"result" : {
"txid" : "0x1e9d018b4ea8fc3442229ae437bc8451e876216dd04a9c071672753437c9ada5",
"executions" : [
{
"vmstate" : "HALT",
"stack" : [
{
"value" : "QU5U",
"type" : "ByteString"
}
],
"notifications" : [],
"trigger" : "Application",
"gasconsumed" : "1419540"
}
]
},
"jsonrpc" : "2.0"
}
Поле stack
полученного JSON-сообщения содержит массив байтов в base64 со значением символа токена.
Следующие команды позволят получить вам дополнительную информацию о токене:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json c36534b6b81621178980438c18796f23a463441a decimals
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json c36534b6b81621178980438c18796f23a463441a totalSupply
Настало время для более интересных вещей. Для начала проверим баланс NEP-17 токенов на нашем счету с помощью метода balanceOf
:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json c36534b6b81621178980438c18796f23a463441a balanceOf NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
... с паролем qwerty
. Результат:
Network fee: 0.0120452
System fee: 0.0249927
Total fee: 0.0370379
Relay transaction (y|N)> y
Sent invocation transaction 2f9d830cfc52747c6d0658aeb25c75e334afd1e4badd1dc946c706a22a7d1e10
Для более детального рассмотрения транзакции используем getapplicationlog
RPC-вызов:
curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["2f9d830cfc52747c6d0658aeb25c75e334afd1e4badd1dc946c706a22a7d1e10"] }' localhost:20331 | json_pp
Результат:
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"txid" : "0x2f9d830cfc52747c6d0658aeb25c75e334afd1e4badd1dc946c706a22a7d1e10",
"executions" : [
{
"vmstate" : "HALT",
"stack" : [
{
"type" : "Integer",
"value" : "0"
}
],
"notifications" : [],
"gasconsumed" : "2499270",
"trigger" : "Application"
}
]
}
}
Как вы видите, поле stack
содержит целое значение 0
, то есть в настоящий момент мы не обладаем токенами.
Но не стоит об этом беспокоиться, переходите к следующему шагу.
Перед тем как мы будем способны использовать наш токен (например, попытаемся передать его кому-либо), мы должны его выпустить.
Другими словами, мы должны перевести все имеющееся количество токена (total supply) на чей-нибудь аккаунт.
Для этого в нашем контракте существует специальная функция - Mint
. Однако, эта функция использует сисколл CheckWitness
, чтобы
проверить, является ли вызывающий контракта его владельцем, и обладает ли он правами управлять начальным количеством токенов.
Для этой цели существуют подписанты транзакции: проверка заданного хэша осуществляется с помощью листа подписантов, прикрепленного к ней.
Чтобы пройти эту проверку, нам необходимо добавить наш аккаунт с областью CalledByEntry к подписантам транзакции перевода. Давайте выпустим токен на наш адрес:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json c36534b6b81621178980438c18796f23a463441a mint NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB -- NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB:CalledByEntry
Где
--
специальный разделитель, служащий для обозначения списка подписантов транзакцииNbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
сам подписант транзакции (наш аккаунт)
... с паролем qwerty
. Результат:
Network fee: 0.0119952
System fee: 0.1371123
Total fee: 0.1491075
Relay transaction (y|N)> y
Sent invocation transaction 9a54e07e54550e57ab9d7d1a1a001516ae2514bae23f6691632f2a3bc1c2d8b7
getapplicationlog
RPC-вызов для этой транзакции дает нам следующий результат:
{
"jsonrpc" : "2.0",
"result" : {
"txid" : "0x9a54e07e54550e57ab9d7d1a1a001516ae2514bae23f6691632f2a3bc1c2d8b7",
"executions" : [
{
"stack" : [
{
"value" : true,
"type" : "Boolean"
}
],
"notifications" : [
{
"state" : {
"type" : "Array",
"value" : [
{
"type" : "Any"
},
{
"value" : "rtjbO11mTKBL3XvtN5kv+VhWC0E=",
"type" : "ByteString"
},
{
"type" : "Integer",
"value" : "1100000000000000"
}
]
},
"contract" : "0xc36534b6b81621178980438c18796f23a463441a",
"eventname" : "Transfer"
}
],
"trigger" : "Application",
"vmstate" : "HALT",
"gasconsumed" : "13711230"
}
]
},
"id" : 1
}
Обратите внимание, что поле stack
содержит значение true
- токен был успешно выпущен.
Давайте убедимся в этом, еще раз запросив баланс нашего аккаунта с помощью метода balanceOf
:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json c36534b6b81621178980438c18796f23a463441a balanceOf NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
... пароль qwerty
. Результат:
Network fee: 0.0120452
System fee: 0.0274533
Total fee: 0.0394985
Relay transaction (y|N)> y
Sent invocation transaction 870607e5bbffdaef9adb38cf4ca08125481554bf674d6a63e79c3779c924017c
... со следующим сообщением от getapplicationlog
вызова RPC:
{
"id" : 1,
"result" : {
"executions" : [
{
"trigger" : "Application",
"gasconsumed" : "2745330",
"stack" : [
{
"type" : "Integer",
"value" : "1100000000000000"
}
],
"vmstate" : "HALT",
"notifications" : []
}
],
"txid" : "0x870607e5bbffdaef9adb38cf4ca08125481554bf674d6a63e79c3779c924017c"
},
"jsonrpc" : "2.0"
}
Теперь мы видим целое значение в поле stack
, а именно, 1100000000000000
является значением баланса токена NEP-17 на нашем аккаунте.
Важно, что токен может быть выпущен лишь однажды.
После того, как мы закончили с выпуском токена, мы можем перевести некоторое количество токена кому-нибудь.
Давайте переведем 5 токенов аккаунту с адресом NgzuJWWGVEwFGsRrgzj8knswEYRJrTe7sm
с помощью функции transfer
:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json c36534b6b81621178980438c18796f23a463441a transfer NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB NgzuJWWGVEwFGsRrgzj8knswEYRJrTe7sm 500000000 null -- NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB:CalledByEntry
... пароль qwerty
. Результат:
Network fee: 0.0123652
System fee: 0.1188695
Total fee: 0.1312347
Relay transaction (y|N)> y
Sent invocation transaction a796fc3d5b75f6c5289aca9d2f77d6e50c5d9fdbd860068fb5771b99ff747e96
Наш любимый вызов RPC getapplicationlog
говорит нам:
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"txid" : "0xa796fc3d5b75f6c5289aca9d2f77d6e50c5d9fdbd860068fb5771b99ff747e96",
"executions" : [
{
"stack" : [
{
"type" : "Boolean",
"value" : true
}
],
"gasconsumed" : "11886950",
"vmstate" : "HALT",
"notifications" : [
{
"eventname" : "Transfer",
"contract" : "0xc36534b6b81621178980438c18796f23a463441a",
"state" : {
"type" : "Array",
"value" : [
{
"type" : "ByteString",
"value" : "rtjbO11mTKBL3XvtN5kv+VhWC0E="
},
{
"type" : "ByteString",
"value" : "50l6vFaauRKm8hPVkr3Aw2CeHQs="
},
{
"type" : "Integer",
"value" : "500000000"
}
]
}
}
],
"trigger" : "Application"
}
]
}
}
Заметьте, что поле stack
содержит true
, что означает, что токен был успешно переведен с нашего аккаунта.
Теперь давайте проверим баланс аккаунта, на который был совершен перевод (NgzuJWWGVEwFGsRrgzj8knswEYRJrTe7sm
), чтобы убедиться, что количество токена на нем = 5:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json c36534b6b81621178980438c18796f23a463441a balanceOf NgzuJWWGVEwFGsRrgzj8knswEYRJrTe7sm
Вызов RPC getapplicationlog
для этой транзакции возвращает следующий результат:
{
"id" : 1,
"jsonrpc" : "2.0",
"result" : {
"txid" : "0x40f3f3c12d3eeba7e282bbaf76af944310b82504dcb3c09db3ea6c6d8418bb6b",
"executions" : [
{
"gasconsumed" : "2745330",
"trigger" : "Application",
"vmstate" : "HALT",
"stack" : [
{
"type" : "Integer",
"value" : "500000000"
}
],
"notifications" : []
}
]
}
}
Как и ожидалось, мы видим ровно 5 токенов в поле stack
.
Вы можете самостоятельно убедиться, что с нашего аккаунта были списаны 5 токенов, выполнив метод balanceOf
с аргументом NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
.
В этой части подытожим наши знания о смарт-контрактах и исследуем смарт-контракт 4-domain.go. Данный контракт описывает операции регистрации, переноса и удаления доменов, а также операцию получения информации о зарегистрированном домене.
Начнем!
Давайте рассмотрим и исследуем смарт-контракт. В качестве первого параметра контракт принимает на вход строку - действие, одно из следующих значений:
register
проверяет, существует ли домен с указанным именем. В случае, если такого домена не существует, добавляет пару[domainName, owner]
в хранилище. Данная операция требудет дополнительных аргументов:domainName
- новое имя домена.owner
- 34-значный адрес аккаунта из нашего wallet, который будет использоваться для вызова контракта.
query
возвращает адрес аккаунта владельца запрашиваемого домена (или false, в случае, если домен с указанным именем не зарегистрирован). Требует дополнительных аргументов:domainName
- имя запрашиваемого домена.
transfer
переводит домен с указанным именем на другой адрес (в случае, если вы являетесь владельцем указанного домена). Требует следующих аргументов:domainName
- имя домена, который вы хотите перевести.toAddress
- адрес аккаунта, на который вы хотите перевести домен.
delete
удаляет домен из хранилища. Аргументы:domainName
имя домента, который вы хотите удалить.
В следующих шагах мы скомпилируем и развернем смарт-контракт. После этого мы зарегистрируем новый домен, переведем его на другой аккаунт и запросим информацию о нем.
Скомпилируйте смарт-контракт 4-domain.go с конфигурацией
$ ./bin/neo-go contract compile -i 4-domain/4-domain.go -c 4-domain/4-domain.yml -m 4-domain/4-domain.manifest.json
... и разверните его:
$ ./bin/neo-go contract deploy -i 4-domain/4-domain.nef -m 4-domain/4-domain.manifest.json -e http://localhost:20331 -w my_wallet.json
... введите пароль qwerty
:
Enter account NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB password >
Результат:
Network fee: 0.0303652
System fee: 10.0107577
Total fee: 10.0411229
Relay transaction (y|N)> y
Sent invocation transaction 69548dfecf70c190e2bc872aa210d53ff7faa7956074154c90a27d4c94420562
Contract: 9042814f07d65d2b835fa1f07d21c22c6e1cbdf7
Вы догадываетесь, что это значит :)
Вызовите контракт, чтобы зарегистрировать домен с именем my_first_domain
:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json 9042814f07d65d2b835fa1f07d21c22c6e1cbdf7 register my_first_domain NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB -- NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB:CalledByEntry
... пароль: qwerty
Enter account NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB password >
Результат:
Network fee: 0.0122052
System fee: 0.0894353
Total fee: 0.1016405
Relay transaction (y|N)> y
Sent invocation transaction d9f05af09ee497ceb523be3274483143f83f7f24038f37799a962c8dee640357
Также вы можете увидеть лог-сообщение в консоли, где запускали ноду neo-go:
2022-07-05T13:42:36.592+0300 INFO runtime log {"tx": "d9f05af09ee497ceb523be3274483143f83f7f24038f37799a962c8dee640357", "script": "9042814f07d65d2b835fa1f07d21c22c6e1cbdf7", "msg": "RegisterDomain: my_first_domain"}
Все получилось. Теперь проверим, был ли наш домен действительно зарегистрирован, с помощью вызова RPC getapplicationlog
:
curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["d9f05af09ee497ceb523be3274483143f83f7f24038f37799a962c8dee640357"] }' localhost:20331 | json_pp
Результат:
{
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"stack" : [
{
"type" : "Boolean",
"value" : true
}
],
"vmstate" : "HALT",
"notifications" : [
{
"state" : {
"type" : "Array",
"value" : [
{
"value" : "rtjbO11mTKBL3XvtN5kv+VhWC0E=",
"type" : "ByteString"
},
{
"value" : "bXlfZmlyc3RfZG9tYWlu",
"type" : "ByteString"
}
]
},
"contract" : "0x9042814f07d65d2b835fa1f07d21c22c6e1cbdf7",
"eventname" : "registered"
}
],
"trigger" : "Application",
"gasconsumed" : "8943530"
}
],
"txid" : "0xd9f05af09ee497ceb523be3274483143f83f7f24038f37799a962c8dee640357"
},
"id" : 1
}
Здесь мы в особенности заинтересованы в двух полях полученного json:
Первое поле - notifications
, оно содержит одно уведомление с именем registered
:
bXlfZmlyc3RfZG9tYWlu
- строка в base64, которая может быть декодирована в имя нашего домена -my_first_domain
,ecv/0NH0e0cStm0wWBgjCxMyaok=
- строка, которая декодируется в адрес нашего аккаунтаNbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
.
Второе поле - stack
, в котором лежит true
- значение, возвращенное смарт-контрактом.
Все эти значения дают нам понять, что наш домен был успешно зарегистрирован.
Вызовите контракт, чтобы запросить информацию об адресе аккаунта, зарегистрировавшего домен my_first_domain
:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json 9042814f07d65d2b835fa1f07d21c22c6e1cbdf7 query my_first_domain
... любимейший пароль qwerty
:
Enter account NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB password >
Результат:
Network fee: 0.0119552
System fee: 0.0412161
Total fee: 0.0531713
Relay transaction (y|N)> y
Sent invocation transaction 4d1b15e3c891b4e5efe6f093e54c1090476dc0ed0069b228f58578c9360ee1f2
и лог-сообщение в консоли запущенной ноды neo-go:
2022-07-05T13:44:21.681+0300 INFO runtime log {"tx": "4d1b15e3c891b4e5efe6f093e54c1090476dc0ed0069b228f58578c9360ee1f2", "script": "9042814f07d65d2b835fa1f07d21c22c6e1cbdf7", "msg": "QueryDomain: my_first_domain"}
Проверим транзакцию с помощью вызова RPC getapplicationlog
:
curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["4d1b15e3c891b4e5efe6f093e54c1090476dc0ed0069b228f58578c9360ee1f2"] }' localhost:20331 | json_pp
... что даст нам следующий результат:
{
"result" : {
"executions" : [
{
"stack" : [
{
"value" : "rtjbO11mTKBL3XvtN5kv+VhWC0E=",
"type" : "ByteString"
}
],
"trigger" : "Application",
"gasconsumed" : "4121610",
"notifications" : [],
"vmstate" : "HALT"
}
],
"txid" : "0x4d1b15e3c891b4e5efe6f093e54c1090476dc0ed0069b228f58578c9360ee1f2"
},
"id" : 1,
"jsonrpc" : "2.0"
}
с base64 представлением адреса аккаунта NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
на стеке и в уведомлениях, что означает, что домен my_first_domain
был зарегистрирован владельцем с полученным адресом аккаунта.
Вызовите контракт для передачи домена другому аккаунту (например, аккаунту с адресом NgzuJWWGVEwFGsRrgzj8knswEYRJrTe7sm
):
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json 9042814f07d65d2b835fa1f07d21c22c6e1cbdf7 transfer my_first_domain NgzuJWWGVEwFGsRrgzj8knswEYRJrTe7sm -- NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
... пароль: qwerty
Enter account NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB password >
Результат:
Network fee: 0.0122052
System fee: 0.0748064
Total fee: 0.0870116
Relay transaction (y|N)> y
Sent invocation transaction 937ec7539a31246ff88eb0bdda74cf7d9613d4a8ad1b7b33f0a785e458d76a14
и лог-сообщение:
2022-07-05T13:46:06.746+0300 INFO runtime log {"tx": "937ec7539a31246ff88eb0bdda74cf7d9613d4a8ad1b7b33f0a785e458d76a14", "script": "9042814f07d65d2b835fa1f07d21c22c6e1cbdf7", "msg": "TransferDomain: my_first_domain"}
Отлично. И getapplicationlog
вызов RPC...
curl -d '{ "jsonrpc": "2.0", "id": 1, "method": "getapplicationlog", "params": ["937ec7539a31246ff88eb0bdda74cf7d9613d4a8ad1b7b33f0a785e458d76a14"] }' localhost:20331 | json_pp
... говорит нам:
{
"jsonrpc" : "2.0",
"result" : {
"executions" : [
{
"vmstate" : "HALT",
"gasconsumed" : "7480640",
"trigger" : "Application",
"stack" : [
{
"value" : true,
"type" : "Boolean"
}
],
"notifications" : [
{
"contract" : "0x9042814f07d65d2b835fa1f07d21c22c6e1cbdf7",
"eventname" : "deleted",
"state" : {
"type" : "Array",
"value" : [
{
"type" : "ByteString",
"value" : "rtjbO11mTKBL3XvtN5kv+VhWC0E="
},
{
"type" : "ByteString",
"value" : "bXlfZmlyc3RfZG9tYWlu"
}
]
}
},
{
"eventname" : "registered",
"state" : {
"value" : [
{
"value" : "50l6vFaauRKm8hPVkr3Aw2CeHQs=",
"type" : "ByteString"
},
{
"value" : "bXlfZmlyc3RfZG9tYWlu",
"type" : "ByteString"
}
],
"type" : "Array"
},
"contract" : "0x9042814f07d65d2b835fa1f07d21c22c6e1cbdf7"
}
]
}
],
"txid" : "0x937ec7539a31246ff88eb0bdda74cf7d9613d4a8ad1b7b33f0a785e458d76a14"
},
"id" : 1
}
Поле notifications
содержит два события:
- первое с именем
deleted
и полями с дополнительной информацией (доменmy_first_domain
был удален с аккаунта с адресомNbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
), - второе с именем
registered
и полями с дополнительной информацией (доменmy_first_domain
был зарегистрирован аккаунтом с адресомNgzuJWWGVEwFGsRrgzj8knswEYRJrTe7sm
). Полеstack
содержитtrue
, что значит, что домен был успешно перемещен.
Оставшийся вызов - delete
, вы можете попробовать выполнить его самостоятельно, создав перед этим еще один домен, например, с именем my_second_domain
, а затем удалить его из хранилища с помощью:
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json 9042814f07d65d2b835fa1f07d21c22c6e1cbdf7 register my_second_domain NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB -- NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB:CalledByEntry
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json 9042814f07d65d2b835fa1f07d21c22c6e1cbdf7 delete my_second_domain -- NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
В этой части будет представлен пример децентрализованного приложения на языке Go, работающего в экосистеме Neo. Пример показывает варианты использования NeoGo RPC клиента и набора утилит для работы с кошельками, деплоя и вызва смарт контрактов, получения данных из блокчейна, обработки происходящих в блокчейне событий и автоматической генерации RPC биндингов для смарт контрактов.
Убедитесь, что локальная сеть из четырех узлов, описанная в подготовительной части
настроена и запущена. Нам также понадобится аккаунт NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB
из кошелька с некоторым количеством токена GAS на нём. Откройте и внимательно
просмотрите пример децентрализованного приложения: dApp.go. Пример включает
в себя различные варианты использования базового API, которые необходимы для
начала разработки своего собственного децентрализванного приложения в экосистеме
Neo:
- Создание клиента JSON-RPC, его инициализация и примеры вызовов базовых RPC методов.
- Управление кошельками и аккаунтами Neo с помощью пакета NeoGo
wallet
. - Расширения, предлагаемые NeoGo JSON RPC сервером и поддерживаемые NeoGo RPC клиентом.
- Веб-сокетное расширениеNeoGo JSON RPC сервера. Создание, инициализация и использование веб-сокетного JSON RPC клиента.
- Пакетная утилита
unwrap
, предназначенная для извлечения результатов тестовых RPC-вызовов. - Примеры использования подсистемы уведомлений NeoGo JSON RPC. Подписки на происходящие в блокчейне события и получение уведомлений по веб-сокетному каналу.
- Примеры использования пакета NeoGo
invoker
. Выполнение тестовых вызовов смарт контрактов, скриптов или верификационных методов с удобным интерфейсом Invoker. - Статья-введение в Neo witnesses и области их действия.
- Исторические вызовы, являющиеся еще одним расширением NeoGo RPC сервера и клиентский API для их использования.
- Примеры использования пакета NeoGo
actor
. Генерация, настройка, подпись, отправка и осуществление ожидания испонения транзаций с гибким интерфейсом Actor. - Работа с набором NEP-специфичных и контракт-специфичных пакетов:
- Деплой и вызовы примера контракта Storage с помощью пакета-актора, специфичного для нативного
ContractManagement
контракта. - Демонстрация API итераторов контрактного хранилища:
- Обход итератора в NeoVM-скрипте с использованием
CallAndExpandIterator
Invoker API. - Обход сессионного итератора с использованием
TraverseIterator
Invoker API.
- Обход итератора в NeoVM-скрипте с использованием
- Использование автогенерированных контрактных RPC биндингов.
Внимательно разберите пример децентрализованного приложения и прочитайте комментарии к блокам кода в нём. Перейдите по ссылкам, представленным в комментариях и прочитайте документацию к использованным API. Перед тем как запустить приложение склонируйте репозиторий NeoGo в ту же папку, где расположен репозиторий воркшопа и скомпилируйте демонстрационный пример контракта Storage из репозитория NeoGo:
$ git clone https://github.com/nspcc-dev/neo-go
$ cd neo-go
$ make build
$ ./bin/neo-go contract compile -i ./examples/storage/storage.go -c ./examples/storage/storage.yml -o examples/storage/storage.nef -m ./examples/storage/storage.manifest.json
В качестве последнего подготовительного шага проверьте, что хэш транзакции,
переводящей GAS с мультисигового аккаунта на аккаунт из my_wallet.json
, верно
указан в переменной transferTxH
примера ./dApp/dApp.go
В консоли перейдите в директорию приложения dApp
и запустите приложение:
$ cd ./dApp
$ go run dApp.go
Изучите вывод приложения в консоль, просмотрите примеры кода, представленного в приложении и используйте их для разработки своего собственного децентрализованного приложения для экосистемы Neo!
Спасибо!
- Наш воркшоп на Medium
- Разработка смарт контрактов на Go (YouTube, в рамках события Neo Polaris Launchpad)
- Разработка смарт контрактов на Go (YouTube, в рамках события Neo Asia-Pacific Tour)
- Разработка децентрализованных приложений на Go (YouTube)
- Использование Neo Blockchain Toolkit
- Документация Neo
- Neo github
- NeoGo github