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