Fabex — block explorer для Hyperledger Fabric

Привет, я хочу рассказать про проект Fabex — block explorer для Hyperledger Fabric, недавно принятый в Hyperledger Labs и имеющий некоторые преимущества относительно официального эксплорера. Проект написан полностью на Golang (а не на Nodejs, как официальный), хранит данные о блокчейне в MongoDB или Cassandra по выбору (а не в PostgresSQL, как официальный), имеет как GRPC, так и REST API. Eго легко расширять, например, можно легко добавить больше реализаций хранилища. Детали под катом.

Дисклеймер
Инструмент отлично справляется со своими задачами, обрабатывая леджер с миллионами транзакций в проекте РРД КП, но вокруг него нет комьюнити и местами он сыроват. Текущая версия не совместима с HLF 2.0. Следует относиться к этому как к экспериментальной технологии в ранней стадии развития.

Я хочу привлечь внимание и дать минимальное введение для всех потенциально заинтересованных лиц. Fabex нуждается в пользователях и коммитерах.

Что такое block explorer и зачем нужен Fabex

Основная задача block explorer — извлекать, парсить блоки и находить в них нужную информацию.
Структура блока подробно описана в блоге у Senthil Nathan. Каждая отдельная запись в базе данных Fabex содержит данные о транзакции и блоке, в который данная транзакция попала:

type Tx struct {     ChannelId      string      Txid           string      Hash           string     PreviousHash   string      Blocknum       uint64      Payload        string     ValidationCode int32      Time           int64  } 

Здесь не все возможные данные, но лишь те, которые мне кажутся необходимыми. Txid и Payload содержат, соответственно, ID и write set транзакции. Имена остальных полей говорят сами за себя и относятся к блоку, к которому принадлежит транзакция, но особого внимания заслуживает ValidationCode — выраженный в int32 код валидации, или, иначе говоря, статус, который пир на этапе валидации присваивает транзакции. От присвоенного статуса зависит, повлияет транзакция на world state или нет (будет ли она не только записана, но и учтена в блокчейне). По большому счету, достаточно понимать, что любое ненулевое значение ValidationCode означает, что транзакция невалидна, но для более детального понимания, что произошло с транзакцией, можно обратиться к следующей таблице соответствия:

	"VALID":                        0, 	"NIL_ENVELOPE":                 1, 	"BAD_PAYLOAD":                  2, 	"BAD_COMMON_HEADER":            3, 	"BAD_CREATOR_SIGNATURE":        4, 	"INVALID_ENDORSER_TRANSACTION": 5, 	"INVALID_CONFIG_TRANSACTION":   6, 	"UNSUPPORTED_TX_PAYLOAD":       7, 	"BAD_PROPOSAL_TXID":            8, 	"DUPLICATE_TXID":               9, 	"ENDORSEMENT_POLICY_FAILURE":   10, 	"MVCC_READ_CONFLICT":           11, 	"PHANTOM_READ_CONFLICT":        12, 	"UNKNOWN_TX_TYPE":              13, 	"TARGET_CHAIN_NOT_FOUND":       14, 	"MARSHAL_TX_ERROR":             15, 	"NIL_TXACTION":                 16, 	"EXPIRED_CHAINCODE":            17, 	"CHAINCODE_VERSION_CONFLICT":   18, 	"BAD_HEADER_EXTENSION":         19, 	"BAD_CHANNEL_HEADER":           20, 	"BAD_RESPONSE_PAYLOAD":         21, 	"BAD_RWSET":                    22, 	"ILLEGAL_WRITESET":             23, 	"INVALID_WRITESET":             24, 	"NOT_VALIDATED":                254, 	"INVALID_OTHER_REASON":         255	 

Fabex получает с пира хеш последнего блока, ищет такой блок в базе данных, если его нет, то находит последний добавленный блок в базе и запрашивает с пира блоки по отсутсвующему промежутку. Это происходит в реальном времени и не требует специальных вмешательств. Базу, в которую всё сохраняется, можно выбрать.

Cassandra или MongoDB в качестве хранилища

Изначально Fabex поддерживал PostgresSQL и MongoDB, просто потому что это одни из самых популярных баз данных. Затем я стал искать подходящее решение для быстрых запросов на чтение, но не in-memory, потому что блоков может быть больше, чем доступной памяти. В итоге одна статья убедила меня, что Cassandra — это именно то, что мне нужно: хранение данных на диске, (относительно) быстрые чтения и удобная кластеризация.

Колоночная (на самом деле гибридная колоночно-ориентированная kv) Cassandra читает данные со скоростью, сравнимой с Redis:


www.sciencedirect.com/science/article/pii/S1319157816300453

Но при этом нулевая зависимость потребления памяти от количества данных, по которым производится выборка:


www.sciencedirect.com/science/article/pii/S1319157816300453

Но нужно понимать, что Cassandra — очень специфичный инструмент, и если вы точно не уверены, что вам нужно именно это решение (советую посмотреть этот доклад для ознакомления), лучше воспользоваться MongoDB. Выбрать хранилище можно при старте Fabex:
./fabex -configpath=configs -db=mongo
./fabex -configpath=configs -db=cassandra

PostgresSQL пришлось убрать ввиду сложности поддержки трех разных имплементаций хранилища. Но если у вас есть хорошие идеи, какое ещё хранилище можно использовать, то реализовать его будет не сложно, нужно лишь имплементировать следующий интерфейс:

type Manager interface {     Connect() error     Insert(tx Tx) error     QueryBlockByHash(hash string) ([]Tx, error)     GetByTxId(txid string) ([]Tx, error)     GetByBlocknum(blocknum uint64) ([]Tx, error)     GetBlockInfoByPayload(payload string) ([]Tx, error)     QueryAll() ([]Tx, error)     GetLastEntry() (Tx, error) } 

Можно использовать как микросервис

Fabex изначально создавался как сервис, с которым могут общаться любые другие компоненты системы для получения нужной информации о блокчейне. Он не пытается положить своё состояние в CouchDB, хотя это было бы проще, а хранит его изолированно. Он не пытается быть UI dashboard’ом с красивыми графиками, а предоставляет ясное GRPC API (а также REST API и CLI), позволящее компактно и предсказуемо передавать данные из блокчейна тем сервисам, которым это необходимо. Но минималистичный UI планируется.

GRPC API пока (и, надеюсь, в будущем будет) простое и описано в fabex.proto:

service Fabex {     rpc Explore(RequestRange) returns (stream Entry);     rpc Get(Entry) returns (stream Entry); } 

rpc Explore забирает транзакции из блоков в диапазоне RequestRange из базы данных и возвращает stream с этими транзакциями.

rpc Get позволяет выполнить фильтрующий запрос к БД для получения всех транзакций, удовлетворяющих определенным критериям (номер блока, ID транзакции, ключ транзакции).

Вы можете написать собственного GRPC-клиента либо воспользоваться готовым из пакета github.com/hyperledger-labs/fabex/client.

Например, так мы можем получить транзакции из блоков в диапазоне с 10 по 15 включительно:

txs, err := client.Explore(1, 15) 

А так можно выполнять фильрующие запросы:

— фильтр по ID транзакции:

txs, err := client.Get(&pb.Entry{Txid:"x"}) 

— фильтр по номеру блока:

txs, err := client.Get(&pb.Entry{Blocknum:42}) 

— по ключю транзакции:

txs, err := client.Get(&pb.Entry{Payload:"mykey"}) 

Чтобы сгруппировать транзакции по блокам, можно воспользоваться helper’ом PackTxsToBlocks:

blocks, err := helpers.PackTxsToBlocks(txs) 

Пример использования встроенного GRPC-клиента можно найти здесь.

Доступен также REST API.

Как быстро протестить

Чтобы быстро протестировать fabex, можете воспользоваться готовыми скриптами:
make mongo-test — запуск базы
make fabric-test — запуск тестовой сети
make fabex-test — запуск fabex

Теперь у вас поднята база данных (для тестов MongoDB), тестовая сеть Fabric и Fabex. Можете закомментить/раскомментить нужные строчки в client.go и запусть клиент, чтобы проверить взаимодействие с Fabex.

Например, давайте отправим транзакцию на создание HabrCar (make fabric-test поднимает тестовую сетку с чейнкодом fabcar):

docker exec cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{"Args":["createCar", "car0", "HabraCar", "V8", "black", "Habr"]}' --waitForEvent --tls --cafile /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --peerAddresses peer0.org1.example.com:7051 --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --tlsRootCertFiles /opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt

Теперь можно вызвать client/example/client.go, который запросит пятый блок:

go run client/example/client.go

Вывод будет таким:

Почему мы запросили 5 блок? Потому что первые четыре нерелевантны и неинформативны: создание канала, присоединение анчора первой организации, присоединение анчора второй организации и одна транзакция, отправляемая автоматически скриптом при поднятии сети для наполнения (и поскольку в тестовой сети block time равняется двум секундам, а между этими транзакциями проходит более двух секунд, каждая из них оказалась в разных блоках).

Режим CLI

Всё описанное выше можно повторить и без взаимодействия с API, используя CLI. Если мы только что запустили чистую базу, её нужно наполнить транзакциями:

./fabex -task=explore -configpath=tests -configname=config -enrolluser=true -db=mongo

Теперь можно запросить пятый блок:

./fabex -task=getblock -blocknum=5 -configpath=tests -configname=config -enrolluser=true -db=mongo

make stop-mongo-test — удаление тестовой базы
make stop-fabric-test — удаление тестовой сети

Очень просто. Можете посмотреть таргеты Makefile и readme для более детального понимания особенностей эксплуатации. Или напишите мне в телеграм (ссылка в профиле), постараюсь помочь.

FavoriteLoadingДобавить в избранное
Posted in Без рубрики

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *