Приложения¶
Аудитория: Архитекторы приложений, разработчики смарт-контрактов и приложений.
Приложение взаимодействует с блокчейн-сетью посредством записи транзакций в реестр или запросов к содержанию реестра. В этом разделе рассмотрим механизм того, как это делается; в нашем сценарии организации взаимодействуют с сетью PaperNet при помощи приложений, которые вызывают транзакции issue, buy и redeem, определенные в смарт-контракте ценной бумаги. Несмотря на то, что приложение организации MagnetoCorp по выпуску ценной бумаги достаточно простое, в нем содержатся все ключевые для понимания пункты.
В этом разделе мы разберем:
- Поток приложения при вызове смарт-контракта
- Как приложение пользуется кошельком и идентификатором
- Как приложение соединяется через шлюз
- Как получить доступ в конкретную сеть
- Как составить запрос на транзакцию
- Как послать транзакцию
- Как обработать ответ на транзакцию
Для пояснения, мы приведем в пример приложение коммерческой ценной бумаги, входящее в состав Hyperledger Fabric. Его можно загрузить и запустить на локальной машине. Оно написано как на JavaScript, так и на Java, но внутренняя логика не зависит от языка реализации, так что вам будет легко следить за происходящим (скоро будет вариант и на языке Go).
Основной поток¶
Приложение взаимодействует с сетью блокчейн при помощи набора разработчика Fabric SDK. Взгляните на упрощенную диаграмму того, как приложение вызывает смарт-контракт коммерческой ценной бумаги:
Приложение PaperNet application вызывает
смарт-контракт коммерческой ценной бумаги, чтобы записать запрос на транзакцию выпуска/эмиссии
ценной бумаги.
Чтобы записать транзакцию, приложение должно совершить шесть основных действий:
- Выбрать идентификатор из кошелька
- Соединиться со шлюзом
- Войти в нужную сеть
- Составить для смарт-контракта запрос на транзакцию
- Послать транзакцию в сеть
- Обработать ответ
Вам предстоит увидеть, как при помощи Fabric SDK эти шесть действий
совершает типичное приложение. Код приложения находится в файле issue.js
. Файл по ссылке
можно открыть в браузере или в привычном вам редакторе после загрузки.
Обратите внимание на общую структуру приложения; несмотря на комментарии и пустые строки,
оно занимает лишь 100 строчек кода!
Кошелек¶
В самом начале issue.js
можно видеть, как вводятся два класса Fabric:
const { Wallets, Gateway } = require('fabric-network');
Информацию о классах fabric-network
можно получить из
документации node SDK,
а сейчас мы посмотрим, как ими пользуются для связи приложения MagnetoCorp с
PaperNet. Это приложение использует класс Fabric Wallet следующим образом:
const wallet = await Wallets.newFileSystemWallet('../identity/user/isabella/wallet');
Обратите внимание, как wallet
обнаруживает кошелек в локальной
файловой системе.
Идентификатор, извлеченный из кошелька, явным образом соответствует пользователю
по имени Изабелла, который использует приложение issue
. В кошельке содержится набор
идентификаторов – цифровые сертификаты X.509 – и они могут быть использованы для
доступа в PaperNet или другую сеть Fabric. Если вы запустите руководство и посмотрите
в эту директорию, то увидите там описание идентификатора для пользователя Изабелла.
Представьте себе кошелек, в котором содержатся цифровые аналоги вашего
паспорта, водительского удостоверения или банковской карточки. Цифровые сертификаты X.509,
содержащиеся в нем, сопоставляют их держателя с организацией, наделяя его таким образом
правами в сетевом канале. К примеру, Isabella
может являться администратором MagnetoCorp,
и это дает ей больше прав, чем другому пользователю – Balaji
из DigiBank.
Более того, смарт-контракт может извлечь её идентификатор при обработке смарт-контракта,
используя контекст транзакции.
Просим заметить, что эти кошельки не содержат денег в какой-либо форме или токенов – в них хранятся идентификаторы.
Шлюз¶
Второй ключевой класс это класс Fabric Gateway. В первую очередь,
шлюз определяет один или более одноранговых узлов, предоставляющих
доступ к сети – в нашем случае это PaperNet. Так issue.js
связывается с шлюзом:
await gateway.connect(connectionProfile, connectionOptions);
У gateway.connect()
есть два важных параметра:
- connectionProfile: местонахождение в файловой системе профиля соединения, который идентифицирует набор узлов, являющихся шлюзом к PaperNet
- connectionOptions: набор управляющих параметров взаимодействия
issue.js
с PaperNet
Теперь посмотрите, как клиентское приложение пользуется шлюзом, чтобы изолировать себя от топологии сети, которая может и измениться. Шлюз берет на себя отправку предлагаемой транзакции нужным одноранговым узлам сети, используя профиль соединения и параметры соединения.
Взгляните на подробности
профиля соединения
./gateway/connectionProfile.yaml
. Он написан в формате
YAML и легко читается.
Профиль соединения загружается и конвертируется в объект JSON:
let connectionProfile = yaml.safeLoad(file.readFileSync('./gateway/connectionProfile.yaml', 'utf8'));
В настоящий момент нам нужны лишь разделы профиля channels:
и peers:
:
(Подробности немного изменены для наглядности)
channels:
papernet:
peers:
peer1.magnetocorp.com:
endorsingPeer: true
eventSource: true
peer2.digibank.com:
endorsingPeer: true
eventSource: true
peers:
peer1.magnetocorp.com:
url: grpcs://localhost:7051
grpcOptions:
ssl-target-name-override: peer1.magnetocorp.com
request-timeout: 120
tlsCACerts:
path: certificates/magnetocorp/magnetocorp.com-cert.pem
peer2.digibank.com:
url: grpcs://localhost:8051
grpcOptions:
ssl-target-name-override: peer1.digibank.com
tlsCACerts:
path: certificates/digibank/digibank.com-cert.pem
Взгляните, как channel:
идентифицирует сетевой канал PaperNet:
и два его
одноранговых узла. У MagnetoCorp есть peer1.magenetocorp.com
и у DigiBank есть
peer2.digibank.com
, и оба они — одобряющие одноранговые узлы. Ключ peers:
содержит
подробности о том, как соединиться с этими узлами, включая их сетевые адреса.
Профиль соединения содержит много информации, и не только об узлах, но и о сетевых каналах, упорядочивающих узлах сети, организациях и УЦ, поэтому — на данном этапе, если вы еще не все понимаете, ничего страшного!
Теперь обратим внимание на объект connectionOptions
:
let connectionOptions = {
identity: userName,
wallet: wallet,
discovery: { enabled:true, asLocalhost: true }
};
Как видно, в нем определены идентификатор userName
и кошелек wallet
для связи
со шлюзом. Эти значения переменных были присвоены ранее в коде.
Есть и другие параметры соединения, которые приложение может использовать для передачи в SDK, чтобы действовать от его имени. Например:
let connectionOptions = {
identity: userName,
wallet: wallet,
eventHandlerOptions: {
commitTimeout: 100,
strategy: EventStrategies.MSPID_SCOPE_ANYFORTX
},
}
Здесь commitTimeout
устанавливает для SDK интервал ожидания в 100 секунд, пока транзакция не будет записана.
В то же время strategy: EventStrategies.MSPID_SCOPE_ANYFORTX
определяет для SDK необходимость
оповещать приложение о том, что один одноранговый узел MagnetoCorp подтвердил транзакцию,
и, напротив,strategy: EventStrategies.NETWORK_SCOPE_ALLFORTX
требует подтверждения
транзакции от всех узлов MagnetoCorp и DigiBank.
Если хотите, почитайте о том, как параметры соединения дают приложениям возможность задавать цели без того, чтобы заботиться о тех средствах, какими они будут выполнены.
Сетевой канал¶
Одноранговые узлы, что записаны в connectionProfile.yaml
, дают
issue.js
доступ к PaperNet. Поскольку эти одноранговые узлы могут быть присоединены к нескольким
сетевым каналам, то и шлюз дает приложению доступ к этим каналам!
Вот как приложение выбирает конкретный канал:
const network = await gateway.getNetwork('PaperNet');
И только после этого network
дает доступ к PaperNet. Ну, а если приложение
попробует одновременно получить доступ к другой сети - BondNet
, то это легко реализуется
таким образом:
const network2 = await gateway.getNetwork('BondNet');
Теперь у нашего приложения есть доступ одновременно и к сети BondNet
и PaperNet
!
Таков мощный механизм в Hyperledger Fabric – приложения могут участвовать
в сети сетей - они присоединяются через шлюзы к нескольким одноранговым узлам,
каждый из которых в свою очередь соединен с несколькими сетевыми каналами.
В разных каналах у них будут разные права - в соответствии с их идентификаторами из
кошелька, записанными в gateway.connect()
.
Составить запрос¶
И вот — приложение готово к тому, чтобы выпустить коммерческую ценную бумагу. Для
этого оно применит CommercialPaperContract
и это будет довольно просто:
const contract = await network.getContract('papercontract', 'org.papernet.commercialpaper');
Заметьте, что приложение сообщает имя – papercontract
– и явным образом
имя контракта: org.papernet.commercialpaper
! Посредством имени контракта
выбирается один контракт из файла чейнкода papercontract.js
, содержащего и другие контракты.
papercontract.js
был установлен и развернут в канале сети PaperNet с именем
papercontract
, и, если поинтересуетесь, можете почитать как
развернуть чейнкод, содержащий несколько смарт-контрактов.
Если бы наше приложение одновременно попробовало бы подключиться к другому контракту в сетях PaperNet или BondNet, это легко было бы сделать так:
const euroContract = await network.getContract('EuroCommercialPaperContract');
const bondContract = await network2.getContract('BondContract');
Следуя этим примерам, обратите внимание, что мы не нашли нужным использовать
конкретное имя контракта - в каждом файле лишь один смарт-контракт, и getContract()
берет первый, который найдет.
Теперь вспомним ту транзакцию, которой организация MagnetoCorp выпускает свою первую ценную коммерческую ценную бумагу:
Txn = issue
Issuer = MagnetoCorp
Paper = 00001
Issue time = 31 May 2020 09:00:00 EST
Maturity date = 30 November 2020
Face value = 5M USD
Настало время послать эту транзакцию в PaperNet!
Послать транзакцию¶
Послать транзакцию можно простым вызовом метода из SDK:
const issueResponse = await contract.submitTransaction('issue', 'MagnetoCorp', '00001', '2020-05-31', '2020-11-30', '5000000');
Параметры submitTransaction()
соответствуют параметрам запроса транзакции.
Именно эти значения будут переданы в метод issue()
смарт-контракта и с их
помощью будет создана коммерческая ценная бумага. Вот его подпись:
async issue(ctx, issuer, paperNumber, issueDateTime, maturityDateTime, faceValue) {...}
Может показаться, что смарт-контракт вступает в действие сразу же после того, как
приложение запускает submitTransaction()
, но это не так. Для того чтобы это сделать,
SDK сначала посылает информацию из connectionOptions
и connectionProfile
назначенным
узлам в сети, от которых может получить необходимые одобрения. Но приложению видеть
этого и не нужно — оно всего лишь запускает submitTransaction
и SDK сделает всё за него само!
Важно заметить, что в API для submitTransaction
входит и процесс
получения подтверждений о записи транзакций. Получение подтверждений о записи транзакций
необходимо — транзакция должна быть прежде успешно упорядочена, подтверждена и записана
в реестр.
Обратим теперь внимание на то, как приложение обращается с ответами!
Обработать ответ¶
Так транзакция issue возвращает ответ на коммерческую ценную бумагу (известно из
вспомним papercontract.js
):
return paper.toBuffer();
Вы можете заметить небольшую особенность – каждая новая ценная бумага - paper
,
должна быть сконвертирована в буфер для ответа приложению. Обратите внимание, как issue.js
использует метод CommercialPaper.fromBuffer()
для восстановления коммерческой ценной бумаги
из буфера ответа:
let paper = CommercialPaper.fromBuffer(issueResponse);
Таким образом paper
естественным образом используется как содержательное сообщение
о завершении:
console.log(`${paper.issuer} commercial paper : ${paper.paperNumber} successfully issued for value ${paper.faceValue}`);
Видно, как один и тот же класс paper
может быть использован и в приложении, и в смарт-контракте
– если вы будете структурировать свой код так же, он будет и читаемым и пригодным к повторному
использованию.
Что же касается запроса на одобрение транзакции, может показаться, что
приложению даются права на управление сразу же после завершения работы смарт-контракта,
но это не так. Внутренний механизм работы подразумевает, что всем процессом консенсуса
заведует SDK, который и уведомляет приложение о завершении процесса - в соответствии
с параметрами соединения strategy
connectionOption. Если интересны подробности —
почитайте здесь - транзакционный поток.
Вот и всё! В этой главе вы разобрались в том, как вызывать смарт-контракт из приложения, перед тем рассмотрев как приложение MagnetoCorp выпускает новую ценную бумагу в сети PaperNet. Теперь же предлагаем рассмотреть принципы структур данных реестра и смарт-контрактов в этом разделе.