Изменяемый процесс подтверждения и проверки транзакций
======================================================
Мотивация
---------
Когда транзакция проверяется на этапе сохранения (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/