Философия и архитектура
Весь hlf-core стоит на нескольких принципах, и если понять их, остальное читается уже как следствие. Поэтому прежде чем переходить к отдельным пакетам, стоит разобрать саму идею.
Принцип 1. Одна точка входа и конверт команды
В обычном chaincode каждая операция — это отдельная функция контракта, и добавление новой
операции меняет его интерфейс. Мы пошли иначе: у контракта одна функция (transportFabricExecute),
а любая операция упаковывается в конверт команды. Что именно вызвано — определяет поле name
в конверте, а не имя функции.
Отсюда два следствия. Во-первых, клиентский код пользуется единым API — transport.send(...)
и transport.sendListen(...), одинаково и для блокчейна, и для обычных сервисов, потому что
модель команд общая (она приходит из @ts-core). Во-вторых, чтение и запись разделены флагом
isReadonly: readonly-команды идут через evaluate, без записи в ledger, а остальные — через
submit, с полным циклом endorsement.
Принцип 2. Подпись на уровне приложения
Fabric аутентифицирует организацию — идентичность gateway и MSP, но не конечного пользователя.
В enterprise этого мало: нам нужно знать, кто именно инициировал операцию, и защититься от
повторного воспроизведения транзакции. Поэтому hlf-core кладёт в конверт подпись приложения
(userId и signature) и nonce, который строго возрастает и хранится в состоянии. Так поверх
стандартной модели Fabric появляется сквозная идентификация пользователя и защита от replay.
Принцип 3. ORM поверх key-value
Состояние Fabric — это key-value с диапазонными запросами по префиксу. hlf-core даёт поверх него
ORM-подобный слой: сущности, связи parent-child через пару прямого и обратного ключей, пагинацию,
автоматическую сериализацию и валидацию. Ключи строятся через StateKey так, чтобы вторичный
поиск по левому префиксу составного ключа оставался предсказуемым, а не превращался в случайный
обход соседних записей.
Слои
common to the domain pair coin.Логика разбиения простая. В common вынесено всё платформо-нейтральное — то, что нужно и
клиенту, и chaincode. Дальше идёт ветвление на две среды: клиентскую (api, затем transport)
и среду chaincode (chaincode, затем transport-chaincode). А transport-common держит их
протокол синхронным — один и тот же код на обеих сторонах не даёт конвертам «разъехаться».
Пара coin и coin-chaincode повторяет ту же симметрию уже на предметном уровне, поверх
транспорта.
Путь типичной команды
Чтобы принципы сложились в целое, проследим команду от клиента до chaincode и обратно.
- Клиент создаёт команду и зовёт
transport.sendListen(cmd, {userId, signature}). Перед этим nonce запрашивается readonly-командой, увеличивается и подписывается. - Собирается конверт запроса, и из него выкидываются значения по умолчанию — чтобы не раздувать ledger лишними данными.
- Конверт уходит через единственную функцию контракта; по
isReadonlyвыбираетсяevaluateилиsubmit. - На стороне chaincode конверт парсится, проверяется подпись — алгоритм, публичный ключ, nonce,
криптографическая верификация, и команда диспетчеризуется обработчику по полю
name. - Обработчик выполняет бизнес-логику: пишет и читает состояние через менеджеры, изменения копятся
в
StateProxyи фиксируются атомарно, а события отправляются черезstub.dispatch. - Для readonly-команды ответ возвращается сразу. Для записи он попадает в блок — клиент
разбирает блок и сопоставляет ответ с исходной командой по
requestId.
Подробнее о проверке подписи — в разделе «Безопасность», а о самом
протоколе — в @hlf-core/transport.