Изменяемый процесс подтверждения и проверки транзакций¶
Мотивация¶
Когда транзакция проверяется на этапе сохранения (commit), пир выполняет набор проверок прежде чем принимать изменения состояния, представленные транзакцией:
- Проверка identities, подписавших транзакцию.
- Проверка подписей подтвердивших транзакцию пиров.
- Проверка удовлетворения всех политик подтверждения из пространства имен данного чейнкода.
В некоторых сценариях использования требуются проверки, отличающиеся от стандартных (в Fabric):
- UTXO (Unspent Transaction Output, неизрасходованных результат транзакции): Когда происходит проверка, не потратила ли транзакция свои вводные ресурсы два раза.
- Анонимные транзакции: Когда подтверждение включает в себя не identity пира, а подпись и публичный ключ, по которым нельзя понять identity пира.
Изменяемая логика подтверждения и проверки¶
Fabric допускает реализацию и развертывание на пиры пользовательской логики подтверждения и проверки, связанной с обработкой чейнкода. Эта логика может быть скомпилирована в пир или собрана вместе с пиром и развертана с ним как плагин Go.
Примечание
Плагины Go имеют набор ограничений, требующих, чтобы плагин был скомпилирован и слинкован в той же среде сборки, что и пир. Различия в версиях пакетов Go, версиях компиляторов, тегов и даже GOPATH приведут к ошибкам в рантайме при загрузке или выполнении логики плагина.
По умолчанию, чейнкод будет использовать встроенную логику подтверждения и проверки. Однако пользователи имеют возможность выбрать свои плагины подтверждения и проверки как часть определения чейнкода. Администратор может расиширить возможные логики, доступные пиру, настроив соответственно его локальную конфигурацию.
Конфигурация¶
Каждый пир имеет локальную конфигурацию (core.yaml
), определяющая соответствие между
именем логики подтверждения/проверки и реализацией, которая будет исполнена.
Стандартные логики называются ESCC
(«E» как в endorsement, подтверждении) и
VSCC
(validation, проверка), и они могут быть обнаружены в секции handlers
локальной конфигурации пира:
handlers:
endorsers:
escc:
name: DefaultEndorsement
validators:
vscc:
name: DefaultValidation
Когда реализация подтверждения или проверки скомпилирована в пир, поле name
представляет функцию инициализации, которую необходимо выполнить, для того, чтобы
получить Factory (фабрику), которая создает объекты логики.
Функция должна быть экземпляром метода интерфейса HandlerLibrary
из
core/handlers/library/library.go
, и для добавления дополнительной логики интерфейс должен быть расширен дополнительными методами.
Если пользовательский код собран как плагин Go, поле library
должно быть указано как
путь к собранной библиотеке.
Например, если мы имеем пользовательскую логику подтверждения и валидации, реализованную в виде плагина,
нам потребуется указать следующие поля в конфигурации core.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.
Больше информации: Жизненный цикл чейнкода Fabric.
Примечание
Здесь и далее, пользовательская логика будет упоминатся как «плагин», даже если они скомпилированы в пир.
Реализация плагина подтверждения¶
Чтобы реализовать такой плагин, необходимо реализовать интерфейс Plugin
из core/handlers/endorsement/api/endorsement.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
,
который, как ожидается, должен быть реализован разработчиком плагина:
// 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:
// SigningIdentity подписывает сообщения и сериализует их публичные identity в байты
type SigningIdentity interface {
// Serialize возвращает байтовое представление identity, которая используется для проверки
// сообщений, подписанных этой SigningIdentity
Serialize() ([]byte, error)
// Sign подписывает данное ему значение и возвращает подпись
Sign([]byte) ([]byte, error)
}
StateFetcher
: Выдает объект State, который взаимодействует с world state:
// 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
:
// 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
, который передается чейнкоду, представляет собой политику подтверждения чейнкода:
// SerializedPolicy определяет сериализованную политику
type SerializedPolicy interface {
validation.ContextDatum
// Bytes возварщает байты сериализованной политики
Bytes() []byte
}
Объект плагина проверки нужного типа (который указывается через имя метода как экземпляра метода HandlerLibrary
или путем к .so
файлу плагина)
создается для каждого канала, для этого пир вызывает метод New
интерфейса PluginFactory
,
который, как ожидается, должен быть реализован разработчиком плагина:
// 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
: Выполняет политику и определяет, была ли она удовлетворена:
// PolicyEvaluator выполняет политики
type PolicyEvaluator interface {
validation.Dependency
// Evaluate принимает набор из SignedData, и проверяет, удовлетворяет ли данный ему набор подписей
// сериализованной политике (policyBytes)
Evaluate(policyBytes []byte, signatureSet []*common.SignedData) error
}
StateFetcher
: Выдает объект State, который взаимодействует с world state:
// 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 возвращает метаданные элемента коллекции конфиденциальных данных, данного в виде <namespace, collection, key>
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’ы.