Skip to content
This repository has been archived by the owner on Nov 16, 2018. It is now read-only.

Multy Back Golos Report

Vladimir Evgrafov edited this page Mar 16, 2018 · 3 revisions

by @vovapi

Введение

Передо мной была поставлена задача, описаная в MUL-966 и MUL-967 Заключается в следующем:

  • Исследовать ноду Голоса, подобрать необходимую конфигурацию, собрать докер-контейнер
  • Написать микросервис, который будет посредником между основным бекэндом и нодой Голоса rpc-вызовы можно найти здесь

Изначально общение между микросервисом и основным бекэндом планировалась через NSQ, но потом @vadimicus предложил общение через socket.io.

Golos Node

Некоторые исследования ноды голоса были уже проведены и описаны тут. Из-за нестабильности docker-репозитория golos (образы оттуда периодически пропадали), пара образов была перенесена в наш docker-репозиторий Оттуда я взял docker-compose.yml файл для тестнета, и далее с ним работал. Исследуя конфигурацию ноды, выяснилось:

  • Для наших целей достаточно функционала API-ноды
  • Моё предположение, что для того чтобы регистрировать новый аккоунт в сети, необходима witness-нода оказались ошибочны
  • Witness-нода всё же необходима для бизнес-задач (согласно @vadimicus)

На этом исследование ноды было закончено.

Microservice

В качестве основы для API-сервиса была взята библиотека asuleymanov/golos-go С ней сразу же возникли проблемы

Состояние библиотеки

Я не заподозрил неладное, когда пример из readme не работал. Также не обратил внимание на приписку:

This package is still under rapid development and it is by no means complete. For now there is no promise considering API stability. Some response objects maybe be typed incorrectly. The package is already usable, though. See the examples directory.

Как в дальнейшем оказалось, это вполне описывает состояние библиотеки. The package is already usable, though. - оказалось неправдой

Изменения в спецификации API

С вызовами, которые просто получали данные из сети, не случилось проблем, всё работало как надо. В результате исследования в список вызовов были сделаны изменения:

  • Оказался нужен вызов, который проверяет, занят ли юзернейм (AccountCheck)
  • Вызов ChangeKey реализовать на стороне микросервиса не получится: транзакция должна быть подписана ключами владельца, а значит должна быт сформирована на клиенте.

Создание аккаунта

Проблемы начались при реализации создания аккаунта. Helper-метода не оказалось, из-за структуры кода я не сразу нашёл код, который бы отвечал за эту операцию. В итоге потратил много времени на собственную реализацию этой операции. Зато научился читать код Голоса). Соответствующий кусок был найден и библиотеке и продолжилась работа с ним. Опираясь на код GolosChain/golos-js удалось сформировать нормальную операцию. Основная проблема была со структурой Authority. Также помогли ссылки:

2018/03/05 00:55:17 call failed: jsonrpc2: code 1 message: 7 bad_cast_exception: Bad Cast
Invalid cast from null_type to Array
    {"type":"null_type"}
    th_a  variant.cpp:537 get_array

    {"call.method":"call","call.params":[3,"broadcast_transaction_synchronous",[{"ref_block_num":10026,"ref_block_prefix":2000580126,"expiration":"2018-03-04T21:55:47","operations":[["account_create",{"fee":"0 GOLOS","creator":"cyberfounder","new_account_name":"test","owner":{"account_auths":[],"key_auths":[null,["GLS8UNBDvmm5PhMymHWEe4Y5jaXK6ciwAJTgBwB4iU89U3bAEFYb1",1]],"weight_threshold":1},"active":{"account_auths":[],"key_auths":[null,["GLS8XvfCpdwqJPd9F1pyJk2c5nefe4MpwcQKN53QUC3SrFmGBpaVa",1]],"weight_threshold":1},"posting":{"account_auths":[],"key_auths":[null,["GLS53WoQb1EF8xTTuuzxhW8hB6kbosbNZLHGiw2XKEMKks3jwkpwj",1]],"weight_threshold":1},"memo_key":"GLS5d2yRYA49dsNvgYQba74Fk595CfozPhC69kaqVSCyKpn8mgp7D","json_metadata":""}]],"signatures":["1f66c37acadf2d80f77be474175eb9027b166c81c7c8872309ac40fc83b75cfa490347b6a9448fb49fb538b42d9847a68caa27efbf70670bb0a86554fd22092a59"]}]]}
th_a websocket_api.cpp:124 on_message
...

Сообщение абсолютно не информативно. Документация Голоса мне не помогла, по причине отсутствия оной. Читая код появилось понимание и был найден баг в коде библиотеки (проблема была в json-marshaler структуры Authority и в незнании go-builtin'ов автором библиотеки). Наличие этого бага и отсутствие issues показало, что никто не использовал эту библиотеку для работы с операциями для которых нужны были ключи (если вообще ей кто-то пользуется). Результатом стал пулл-реквест Но на этом проблемы не закончились:

call failed: jsonrpc2: code 1 message: 3010000 tx_missing_active_auth: missing required active authority
Missing Active Authority cyberfounder
    {"id":"cyberfounder","auth":{"weight_threshold":1,"account_auths":[],"key_auths":[["GLS7hxgVPn9wEPzBenNSvLwuJ7adjZqwCocf8cyadJqnUhxmJE2H3",1]]},"owner":{"weight_threshold":1,"account_auths":[],"key_auths":[["GLS7hxgVPn9wEPzBenNSvLwuJ7adjZqwCocf8cyadJqnUhxmJE2H3",1]]}}
    th_a  transaction.cpp:149 verify_authority
...

Также не информативная ошибка. Благодаря исследованию кода и с помощью разработчика Голоса, понял что проблема с подписью. Оказалось что библиотека не умеет сериализовывать операцию создания аккаунта, а ошибка, говорящая об этом -- подавляется. Пришлось разбираться в сериализации транзакций Голоса (используя сериализатор из Steemit), и написать свою. Результат -- в пулл-реквесте Создание аккауна заработало!

Новый блок

В библиотеке оказался не реализованным метод подписи на новые блоки. Потратив некоторое время на исследования, я отказался от идеи реализации его. В рамках структуры библиотеки это оказалось проблематично, а переделывать её -- не было времени. Принял решение на начальном этапе ограничиться бесконечным циклом, который будет проверять, пришёл ли блок. Был слегка изменён формат данных о новом блоке:

  • Нет хэша: не очевидно откуда его брать, API такого не отдаёт.
  • Отдаю не транзакции, а операции: на сколько я понимаю, клиенту необходими операции а не транзакции (в транзакции хранится набор операций), если нужно можно и транзакции отдавать, но не понятно зачем.

Организация микросервиса

Изначальное желание было реализовать микросервис используя micro/go-micro -- развесистый фреймворк для микросервисов. Но отсутствие поодержки socket.io и общая сложность вхождения в него, заставили отказаться от этой идеи. В процессе исследования go-micro было написано строгое описание API в формате protobuf, которое потом было использовано в конечном продукте (api/messages.go)

CLI-флаги были сделаны при помощи micro/cli. Они поодерживают инициализацию через env-переменные (нужно для докер-контейнера)

Dockerfile был сделан на основе того что было написано в Multy-Back

Результат

Сервис работает локально, может быть запущен в docker-контейнере. Протестировал руками почти все эндпоинты. Мейннет на своей машине поднять не вышло. Формат сообщений описан в (api/messages.go), добавлю json формат в README.

Concerns

  • Package-wide хранение ключей для подписи в golos-go: пока в нашем использовании это не даёт проблем, но в будущем может
  • NewBlock: не понятно откуда начинать парсить новые блоки: откуда закончил (надо хранить state), с начала (при перезапуске с нуля будут высираться события), откуда скажут (пробрасывать параметр)
  • Авторизация операции создания аккаунтов. Создание аккаунта затрачивает fee (сейчас это 1.500 GOLOS), если api будет торчать наружу, будут проблемы.
  • Когда появляется новый блок, и в нём есть операции, которые меняют баланс наших пользователей, то я получаю обновлённый баланс. При ошибке получения баланса, событие изменения баланса не придёт, пока не изменится опять баланс пользователя.
  • Библиотека для socket.io, предложенная @vadimicus (googollee/go-socket.io), в версии master имеет приписку "Please use v1.4 branch I have no time to maintain master branch now". При этом v1.4 не юзабельна.
  • Хэш транзакции не выдаётся методом get_block. При этом сериализация блока в golos-go не написана. При этом префикс хэша последнего блока необходим для создания транзакции. Это значит, что клиент не сможет сформировать транзкцию у себя при текущем. Я вижу workaround я вижу в том чтобы броадкастить событие, в котором будет префикс блока (из get_dynamic_global_properties получается head_block_id, который содержит 4 байта префикса хэша)

TODO

  • Graceful shutdown
  • Tests
  • Deploy
Clone this wiki locally