Контекст транзакции

Аудитория: Архитекторы, разработчики смарт-контрактов и приложений.

Контекст транзакции исполняет две функции. Во-первых, он позволяет разработчику определять и поддерживать пользовательские переменные во время вызовов транзакций в рамках смарт-контракта. Во-вторых, он предоставляет доступ к широкому спектру API Fabric, которые позволяют разработчикам смарт-контрактов выполнять операции, связанные с детальной обработкой транзакций: от запросов или обновления реестра, как неизменяемого блокчейна, так и изменяемого состояния, до получения цифрового идентификатора приложения, отправляющего транзакцию.

Контекст транзакции создается, когда смарт-контракт развертывается в канале и становится доступным для каждого последующего вызова в транзакции. Контекст транзакции позволяет разработчикам смарт-контрактов писать функциональные, эффективные и легкие для понимания программы.

Сценарий

В примере коммерческих ценных бумаг, контракт papercontract изначально задает имя списка коммерческих ценных бумаг, за которые отвечает. Каждая транзакция впоследствии ссылается на этот список; транзакция выпуска добавляет в него новые бумаги, транзакция покупки меняет владельца бумаги, а транзакция погашения помечает ее как завершенную. Это общий шаблон; при написании смарт-контракта часто полезно инициализировать переменные и обращаться к ним в последовательных транзакциях.

transaction.scenario Контекст транзакции позволяет смарт-контрактам задавать и поддерживать пользовательские переменные в различных вызовах транзакций. Подробное пояснение см. в тексте.

Программирование

При конструировании смарт-контракта разработчик может по желанию переопределить метод createContext встроенного класса Context, чтобы создать собственный контекст:

createContext() {
    new CommercialPaperContext();
}

В нашем примере контекст CommercialPaperContext определен для контракта CommercialPaperContract. Посмотрите, как пользовательский контекст, адресованный через this, добавляет в себя определенную переменную PaperList:

CommercialPaperContext extends Context {
    constructor () {
        this.paperList = new PaperList(this);
    }
}

Когда метод createContext() возвращается в точку (1) на диаграмме выше, создается пользовательский контекст ctx, содержащий paperList как одну из своих переменных.

Впоследствии, когда бы ни вызывалась транзакция смарт-контракта, будь то выпуск, покупка или погашение, ей будет передан этот контекст. Ниже покажем, как в точках (2), (3) и (4) тот же контекст коммерческой ценной бумаги передается в метод при помощи переменной ctx.

Посмотрите, как контекст используется в точке (5):

ctx.paperList.addPaper(...);
ctx.stub.putState(...);

Обратите внимание, что paperList, созданный в CommercialPaperContext, доступен в транзакции выпуска. Аналогичным образом paperList используется и для транзакций погашения и покупки; ctx таким образом делает смарт-контракты эффективными и понятными.

Также обратите внимание, что в контексте есть и другой элемент – ctx.stub – который не был явным образом добавлен CommercialPaperContext. Это оттого, что stub и прочие переменные являются частью встроенного контекста. Давайте теперь изучим структуру этого встроенного контекста, эти неявные переменные и как их использовать.

Структура

Как мы видели в примере, контекст транзакции может содержать сколько угодно пользовательских переменных, таких как paperList.

Контекст транзакции также содержит два встроенных элемента, которые предоставляют доступ к широкому спектру функциональности Fabric – от клиентского приложения, посылающего транзакцию, до доступа к реестру.

  • ctx.stub используется для доступа к API, которые предоставляют широкий диапазон операций обработки транзакций, от putState() и getState() для доступа к реестру, до getTxID() для извлечения идентификатора текущей транзакции.
  • ctx.clientIdentity используется для получения информацию об идентификаторе пользователя, пославшего транзакцию.

На следующей диаграмме показано, что может сделать смарт-контракт, используя stub and clientIdentity, с помощью доступных ему API:

context.apis Смарт-контракт может получить доступ к ряду возможностей через stub и clientIdentity из контекста транзакции. Подробное пояснение - далее в тексте.

Stub

Функционал stub можно поделить на несколько категорий:

  • API базы состояния. См. точку взаимодействия (1). Эти функции дают смарт-контрактам возможность получать, записывать и удалять состояния, соответствующее отдельным объектам в глобальном состоянии, используя их ключ:


    Эти основные функции дополняются запросами, при помощи которых контракты могут получать набор состояний, а не только отдельные состояния. См. точку взаимодействия (2). Набор можно задавать или диапазоном значений ключей, полных или частичных, или запросом в соответствии со значениями соответствующей базы данных состояний.
    При больших запросах, результат можно делить на страницы из соображений уменьшения занимаемого места:

  • API приватных данных. См. точку взаимодействия (3). При помощи этих функций смарт-контракты взаимодействуют с коллекциями приватных данных. Они аналогичны API для взаимодействия с глобальными состояниями и позволяют получать, записывать и удалять приватные данные, обращаясь к ним по их ключу:


    Эти основные функции дополняются запросами приватных данных (4). С их помощью смарт-контракты извлекают набор состояний из коллекций приватных данных по диапазону значений ключей (полных или частичных) или запросом в соответствии со значениями соответствующей базы данных состояния. В настоящее время нет возможности разделения на страницы для коллекций приватных данных.

  • API транзакций. См. точку взаимодействия (5). Этими функциями смарт-контракты извлекают подробные данные о предложении транзакции, обрабатываемом смарт-контрактом в настоящее время. Они включают и идентификатор транзакции и время создания предложения транзакции.

    • getTxID() возвращает идентификатор предложения транзакции (5).
    • getTxTimestamp() возвращает метку времени создания приложением предложения транзакции (5).
    • getCreator() возвращает необработанный идентификатор (X.509 или др.) создателя предложения транзакции. Если это сертификат X.509, тогда бывает удобнее использовать ctx.ClientIdentity.
    • getSignedProposal() возвращает подписанную копию текущего предложения транзакции, находящегося в обработке у смарт-контракта.
    • getBinding() предотвращает случайный или злонамеренный повторный вызов транзакции при помощи одноразового кода (nonce). (На практике, одноразовый код - это случайно сгенерированное клиентским приложением число, включенное в криптографический хэш). К примеру, эта функция может использоваться смарт-контрактом в точке (1), чтобы обнаружить повторный вызов транзакции (5).
    • getTransient() позволяет смарт-контракту обращаться к транзитным данным, переданным приложением. См. точки взаимодействия (9) и (10). Транзитные данные являются приватными данными взаимодействия «приложение-смарт-контракт». Они не записываются в реестр и часто используются вместе с коллекциями приватных данных (3).

  • API ключей используется смарт-контрактами для операций с ключами состояния в глобальном состоянии или коллекции приватных данных. См. точки взаимодействия 2 и 4.

    Простейшие из функций этого API позволяют смарт-контрактам формировать и разделять композитные ключи на их отдельные компоненты. Немного более продвинутыми являются API ValidationParameter(), которые получают и устанавливают основанные на состоянии правила одобрения для глобального состояния (2) и приватных данных (4). И, наконец, getHistoryForKey() получает историю состояния, возвращая набор хранимых значений, включающих идентификаторы транзакции, с помощью которых было проведено изменение состояния, что позволяет прочитать транзакцию из блокчейна (10).


  • API событий используются для работы с событиями в смарт-контракте.

    • setEvent()

      Смарт-контракты при помощи этого API добавляют пользовательские события в ответ транзакции. См. точку взаимодействия (5). Эти события в итоге записываются в блокчейн и посылаются в ожидающие приложения в точке взаимодействия (11).


  • Вспомогательные API – это набор полезных API, которые не поместились в другие категории. С их помощью можно, в частности, получать имя текущего канала и передавать управление другому чейнкоду на одном и том же узле.

    • getChannelID()

      См. точку взаимодействия (13). Смарт-контракт, запущенный на любом одноранговом узле может при помощи этого API определить, на каком канале данное приложение вызвало смарт-контракт.

    • invokeChaincode()

      См. точку взаимодействия (14). На Peer3 (одноранговом узле 3), принадлежащем MagnetoCorp, установлено несколько смарт-контрактов. Эти смарт-контракты способны вызывать друг друга с помощью этого API. Смарт-контракты должны быть расположены в одном и том же узле, вызвать смарт-контракт с другого узла не получится.


    Некоторые из вспомогательных API используются только в низкоуровневом чейкоде, а не в смарт-контрактах. Эти API предназначены прежде всего для операций с вводными данными чейнкода; класс Contract смарт-контракта делает автоматический разбор этих параметров для разработчиков.

ClientIdentity

В большинстве случаев, приложение, которое посылает транзакцию, использует сертификат X.509. В нашем примере, сертификат X.509 (6), выпущенный УЦ CA1 (7), используется пользователем Isabella (8) в ее приложении для подписывания транзакции t6 (5).

ClientIdentity принимает информацию, которую возвращает ему getCreator(), и помещает поверх ее набор вспомогательных API X.509, чтобы облегчить ее использование в данном часто встречающемся сценарии.

  • getX509Certificate() возвращает полный сертификат X.509 того, кто послал транзакцию, включая все его атрибуты и их значения. См. точку взаимодействия (6).
  • getAttributeValue() возвращает значение конкретного атрибута X.509, например, название организационной единицы OU или выделенное имя DN. См. точку взаимодействия (6).
  • assertAttributeValue() возвращает TRUE, если определенный атрибут X.509 имеет заданное значение. См. точку взаимодействия (6).
  • getID() возвращает уникальный идентификатор того, кто послал транзакцию в соответствии с его выделенным именем и выделенным именем выпускающего УЦ в формате x509::{subject DN}::{issuer DN}. См. точку взаимодействия (6).
  • getMSPID() возвращает MSP канала того, кто послал транзакцию. Это позволяет смарт-контракту принимать решения об обработке решений на основе идентификатора организации пославшего. См. точку взаимодействия (15) or (16).