Изменяемый процесс подтверждения и проверки транзакций ====================================================== Мотивация --------- Когда транзакция проверяется на этапе сохранения (commit), пир выполняет набор проверок прежде чем принимать изменения состояния, представленные транзакцией: - Проверка identities, подписавших транзакцию. - Проверка подписей подтвердивших транзакцию пиров. - Проверка удовлетворения всех политик подтверждения из пространства имен данного чейнкода. В некоторых сценариях использования требуются проверки, отличающиеся от стандартных (в Fabric): - **UTXO (Unspent Transaction Output, неизрасходованных результат транзакции):** Когда происходит проверка, не потратила ли транзакция свои вводные ресурсы два раза. - **Анонимные транзакции:** Когда подтверждение включает в себя не identity пира, а подпись и публичный ключ, по которым нельзя понять identity пира. Изменяемая логика подтверждения и проверки ------------------------------------------ Fabric допускает реализацию и развертывание на пиры пользовательской логики подтверждения и проверки, связанной с обработкой чейнкода. Эта логика может быть скомпилирована в пир или собрана вместе с пиром и развертана с ним как `плагин Go `_. .. note:: Плагины Go имеют набор ограничений, требующих, чтобы плагин был скомпилирован и слинкован в той же среде сборки, что и пир. Различия в версиях пакетов Go, версиях компиляторов, тегов и даже GOPATH приведут к ошибкам в рантайме при загрузке или выполнении логики плагина. По умолчанию, чейнкод будет использовать встроенную логику подтверждения и проверки. Однако пользователи имеют возможность выбрать свои плагины подтверждения и проверки как часть определения чейнкода. Администратор может расиширить возможные логики, доступные пиру, настроив соответственно его локальную конфигурацию. Конфигурация ------------ Каждый пир имеет локальную конфигурацию (``core.yaml``), определяющая соответствие между именем логики подтверждения/проверки и реализацией, которая будет исполнена. Стандартные логики называются ``ESCC`` ("E" как в endorsement, подтверждении) и ``VSCC`` (validation, проверка), и они могут быть обнаружены в секции ``handlers`` локальной конфигурации пира: .. code-block:: YAML handlers: endorsers: escc: name: DefaultEndorsement validators: vscc: name: DefaultValidation Когда реализация подтверждения или проверки скомпилирована в пир, поле ``name`` представляет функцию инициализации, которую необходимо выполнить, для того, чтобы получить Factory (фабрику), которая создает объекты логики. Функция должна быть экземпляром метода интерфейса ``HandlerLibrary`` из ``core/handlers/library/library.go``, и для добавления дополнительной логики интерфейс должен быть расширен дополнительными методами. Если пользовательский код собран как плагин Go, поле ``library`` должно быть указано как путь к собранной библиотеке. Например, если мы имеем пользовательскую логику подтверждения и валидации, реализованную в виде плагина, нам потребуется указать следующие поля в конфигурации ``core.yaml``: .. code-block:: YAML handlers: endorsers: escc: name: DefaultEndorsement custom: name: customEndorsement library: /etc/hyperledger/fabric/plugins/customEndorsement.so validators: vscc: name: DefaultValidation custom: name: customValidation library: /etc/hyperledger/fabric/plugins/customValidation.so И нам потребуется расположить файлы плагина ``.so`` локальную файловую систему пира. Имя пользовательского плагина должно быть указано в определении чейнкода чтобы использоваться в чейнкоде. Если вы используйте CLI пира для одобрения определения чейнкода, используйте флаги ``--escc`` или ``--vscc`` для пользовательской библиотеки подтверждения или проверки. Если вы используйте Node.js SDK, смотрите `How to install and start your chaincode `__. Больше информации: :doc:`chaincode_lifecycle`. .. note:: Здесь и далее, пользовательская логика будет упоминатся как "плагин", даже если они скомпилированы в пир. Реализация плагина подтверждения -------------------------------- Чтобы реализовать такой плагин, необходимо реализовать интерфейс ``Plugin`` из ``core/handlers/endorsement/api/endorsement.go``: .. code-block:: Go // Plugin подтверждает ответ на proposal type Plugin interface { // Endorse подписывает данный payload(ProposalResponsePayload bytes), и, в зависимости от ситуации, изменяет его. // Возвращает: // Подтверждение: подпись над payload (полезной нагрузкой), и identity, используема для проверки подписи. // Данный payload может быть изменен этой функцией. // В случае неудачи возвращает ошибку. Endorse(payload []byte, sp *peer.SignedProposal) (*peer.Endorsement, []byte, error) // Init внедряет зависимости объекту Plugin Init(dependencies ...Dependency) error } Объект плагина подтверждения нужного типа (который указывается через имя метода как экземпляра метода ``HandlerLibrary`` или путем к ``.so`` файлу плагина) создается для каждого канала, для этого пир вызывает метод ``New`` интерфейса ``PluginFactory``, который, как ожидается, должен быть реализован разработчиком плагина: .. code-block:: Go // PluginFactory создает объект Plugin type PluginFactory interface { New() Plugin } Ожидается, что метод ``Init`` принимает в качестве входных параметров все зависимости, указанные в ``core/handlers/endorsement/api/``, identified as embedding the ``Dependency`` interface (определенные как embedding, встраивание, интерфейса ``Dependency``). После создания объектра ``Plugin``, пир вызывает его метод ``Init`` и передает ``dependencies`` в качестве параметров. В настоящий момент Fabric идет со следующими зависимостями для плагинов подтверждения: - ``SigningIdentityFetcher``: Возвращает объект типа ``SigningIdentity``, основанный на данном ему подписанном proposal: .. code-block:: Go // SigningIdentity подписывает сообщения и сериализует их публичные identity в байты type SigningIdentity interface { // Serialize возвращает байтовое представление identity, которая используется для проверки // сообщений, подписанных этой SigningIdentity Serialize() ([]byte, error) // Sign подписывает данное ему значение и возвращает подпись Sign([]byte) ([]byte, error) } - ``StateFetcher``: Выдает объект **State**, который взаимодействует с world state: .. code-block:: Go // State определяет взаимодействие с world state type State interface { // GetPrivateDataMultipleKeys получает значения для нескольких элементов конфиденциальных данных в одно обращение к world state GetPrivateDataMultipleKeys(namespace, collection string, keys []string) ([][]byte, error) // GetStateMultipleKeys получает значения для нескольких ключей в одно обращение к world state GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error) // GetTransientByTXID получает значения конфиденциальных данных, связанных с переданным txID GetTransientByTXID(txID string) ([]*rwset.TxPvtReadWriteSet, error) // Done освобождает ресурсы, занятые State Done() } Реализация плагина проверки -------------------------------- Чтобы реализовать плагин проверки, необходимо реализовать интерфейс ``Plugin`` из ``core/handlers/validation/api/validation.go``: .. code-block:: Go // Plugin проверяет транзакции type Plugin interface { // Validate возвращает nil, если действие в конкретной позиции внутри транзакции, стоящей на конкретном месте в конкретном блоке валидно, // в противном случае возвращает ошибку Validate(block *common.Block, namespace string, txPosition int, actionPosition int, contextData ...ContextDatum) error // Init внедряет зависимости объекту Plugin Init(dependencies ...Dependency) error } Каждый ``ContextDatum`` - это дополнительные метаданные, выводящиеся в рантайме, переданные пиром плагину проверки. В настоящий момент, единственный ``ContextDatum``, который передается чейнкоду, представляет собой политику подтверждения чейнкода: .. code-block:: Go // SerializedPolicy определяет сериализованную политику type SerializedPolicy interface { validation.ContextDatum // Bytes возварщает байты сериализованной политики Bytes() []byte } Объект плагина проверки нужного типа (который указывается через имя метода как экземпляра метода ``HandlerLibrary`` или путем к ``.so`` файлу плагина) создается для каждого канала, для этого пир вызывает метод ``New`` интерфейса ``PluginFactory``, который, как ожидается, должен быть реализован разработчиком плагина: .. code-block:: Go // PluginFactory создает объект Plugin type PluginFactory interface { New() Plugin } Ожидается, что метод ``Init`` принимает в качестве входных параметров все зависимости, указанные в ``core/handlers/validation/api/``, identified as embedding the ``Dependency`` interface (определенные как embedding, встраивание, интерфейса ``Dependency``). После создания объектра ``Plugin``, пир вызывает его метод ``Init`` и передает ``dependencies`` в качестве параметров. В настоящий момент Fabric идет со следующими зависимостями для плагинов проверки: - ``IdentityDeserializer``: конвертирует байтовое представление identity в объекты ``Identity``, которые могут использоваться для проверки подписей, ими совершенных, быть проверенными из соответствующим MSP и проверить, удовлетворяют ли они данному **MSP Principal**. Полная спецификация может быть найдена в ``core/handlers/validation/api/identities/identities.go``. - ``PolicyEvaluator``: Выполняет политику и определяет, была ли она удовлетворена: .. code-block:: Go // PolicyEvaluator выполняет политики type PolicyEvaluator interface { validation.Dependency // Evaluate принимает набор из SignedData, и проверяет, удовлетворяет ли данный ему набор подписей // сериализованной политике (policyBytes) Evaluate(policyBytes []byte, signatureSet []*common.SignedData) error } - ``StateFetcher``: Выдает объект **State**, который взаимодействует с world state: .. code-block:: Go // State определяет взаимодействие с world state type State interface { // GetStateMultipleKeys получает значения для нескольких ключей в одно обращение к world state GetStateMultipleKeys(namespace string, keys []string) ([][]byte, error) // GetStateRangeScanIterator возвращает итератор, содержащий все пары ключ-значение между данным ему диапазоном ключей. // startKey - включительно, endKey - не включительно. Пустой startKey указывает на первый доступный ключ, // а пустой endKey - на последний доступный ключ. Для сканирования всех ключей, и startKey, и endKey // могут быть пустыми строками. Однако сканирование всех ключей должно использоваться осмотрительно по причинам производительности. // Возвращенный ResultsIterator содержит результаты типа *KV, определенногов fabric-protos/ledger/queryresult. GetStateRangeScanIterator(namespace string, startKey string, endKey string) (ResultsIterator, error) // GetStateMetadata возвращает метаданные для указанного namespace (пространства имен) и key. GetStateMetadata(namespace, key string) (map[string][]byte, error) // GetPrivateDataMetadata возвращает метаданные элемента коллекции конфиденциальных данных, данного в виде GetPrivateDataMetadata(namespace, collection, key string) (map[string][]byte, error) // Done освобождает ресурсы, занятые State Done() } Важная информация ----------------- - **Согласованность версии плагина между пирами:** В будущих релизах, инфраструктура канала Fabric будет гарантировать, что одна и таже логика проверки используется для конкретного чейнкода на всех пирах канала на данной высоте блокчейна для того, чтобы убрать возможность любой неправильной настройки, которая может привести к расхождению состояния у пиров, которые, по случайности, исполнили разные реализации логики. Однако сейчас это ответственность операторов системы и администраторов. - **Обработка ошибок плагина проверки:** Всегда, когда плагин проверки не может понять, валидна ли транзакция, он должен вернуть ошибку типа **ExecutionFailureError**, определенную в ``core/handlers/validation/api/validation.go``. Любая другая возвращенная ошибка интерпретируется как ошибка политики подтверждения и отмечает транзакцию как признаную недействительной логикой проверки. Однако если возвращена ``ExecutionFailureError`` транзакция не помечается как недействительная, но процесс ее обработки прерывается. Это сделано для того, чтобы предотвратить рахождение в состоянии между пирами. - **Обработка ошибок при извлечении конфиденциальных метаданных**: В случае, когда плагин извлекает метаданные конфиденциальных данных, используя интерфейс``StateFetcher``, важно, чтобы ошибки обрабатывались следующим образом: ``CollConfigNotDefinedError`` и ``InvalidCollNameError``, указывающие, что коллекции не существует, должны быть обработаны как детерминированные (для всех пиров) ошибки и не должны приводить к ``ExecutionFailureError``. - **Импортирование кода Fabric плагином**: Импортирование кода, принадлежащего Fabric, кроме как protobuf, как часть плагина крайне не рекомендуется, и может приводить к проблемам, когда код Fabric изменился с релизом, или приводить к выходу из строя при использовании пиров разных версий. В идеале, код плагина должен использовать только поданные ему зависимости, и должен импортировать минимальное число кода, не считая protobuf'ы. .. Licensed under Creative Commons Attribution 4.0 International License https://creativecommons.org/licenses/by/4.0/