Изменяемый процесс подтверждения и проверки транзакций

Мотивация

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