Запуск приложений Fabric

Примечание

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

Вы также должны быть знакомы с шлюзом Fabric (службой Fabric Gateway) и его ролью в исполнении транзакций приложения. Этой службе посвящен раздел Шлюз Fabric.

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

Пример Asset Transfer

Пример Asset Transfer (basic) демонстрирует, как создавать, обновлять и запрашивать активы. Он включает следующие два компонента:

1. Пример приложения, которое обращается к блокчейн-сети, выполняя транзакции, реализованные в умном контракте. Приложение расположено в следующем каталоге проекта fabric-samples:

asset-transfer-basic/application-gateway-typescript

2. Умный контракт, который реализует транзакции, взаимодействующие с реестром. Умный контракт расположен в следующем каталоге проекта fabric-samples:

asset-transfer-basic/chaincode-(typescript, go, java)

В этом примере мы будем использовать умный контракт, реализованный на языке TypeScript.

Данное руководство состоит из двух основных частей:

1. Настройка блокчейн-сети. Нашему приложению нужна сеть блокчейн, с которой оно будет взаимодействовать. Поэтому мы запустим базовую сеть и развернем в ней умный контракт для нашего приложения.

_images/AppConceptsOverview.png

2. Запуск приложения из примера для взаимодействия с умным контактом. Наше приложение будет использовать умный контракт assetTransfer для создания, запроса и обновления активов в реестре. Мы рассмотрим код приложения и транзакций, которые оно вызывает, включая создание начальных активов, запрос актива, запрос группы активов, создание нового актива и передачу актива новому владельцу.

После прохождения данного руководства вы должны получить начальное представление о том, как приложения Fabric и умные контракты работают вместе для управления данными в распределенном реестре сети блокчейн.

Предисловие

Перед тем как запустил приложение из примера, вам необходимо установить примеры Fabric в вашем окружении. Следуйте инструкциям из статьи Приступая к работе для установки необходимого программного обеспечения. Пример приложения в этом руководстве используется клиентский API шлюза Fabric для Node. Актуальный список поддерживаемых языков программирования и зависимостей смотрите в документации шлюза Fabric.

  • Убедитесь, что у вас установлена подходящая версия Node. Инструкции по установке Node можно найти в документации Node.js.

Настройка сети блокчейн

Если вы уже проходили шаги руководства Использование примера сети Fabric и запустили сеть, то при прохождении шагов данного руководства она будет остановлена. Затем будет запущена новая сеть, в которой будет пустой реестр.

Запуск сети блокчейн

Перейдите в подкаталог test-network вашей локальной копии репозитория fabric-samples.

cd fabric-samples/test-network

Если тестовая сеть уже запущена, остановите ее, чтобы очистить окружение.

./network.sh down

Запустите тестовую сеть Fabric с помощью сценария network.sh.

./network.sh up createChannel -c mychannel -ca

Эта команда запустит тестовую сеть, состоящую из двух одноранговых узлов, службы упорядочения и трех удостоверяющих центров (Orderer, Org1 и Org2). Вместо использования инструмента cryptogen мы запускаем удостоверяющие центры в сети, на что указывает флаг -ca в команде выше. Дополнительно при старте удостоверяющих центров выполняется регистрация администраторов организаций.

Развертывание умного контракта

Примечание

Это руководство демонстрирует работу умного контракта и приложения из примера Asset Transfer, написанных на языке TypeScript, но вы можете использовать умный контракт, написанный на любом языке программирования, вместе с приложением на TypeScript (например, вызывать из приложения на TypeScript функции умного контракта, написанного на Go или Java). Чтобы использовать версии умного контракта на Go или Java, замените аргумент typescript в команде ./network.sh deployCC -ccl typescript на go или java и следуйте инструкциям, которые будут печататься в терминале.

Далее давайте развернем пакет чейнкода, содержащий умный контракт, вызовом сценария ./network.sh с указанием имени чейнкода и языка, на котором он написан.

./network.sh deployCC -ccn basic -ccp ../asset-transfer-basic/chaincode-typescript/ -ccl typescript

Этот сценарий использует команды жизненного цикла чейнкода для упаковки, установки, запроса установленных чейнкодов, одобрения чейнкода обеими организациями Org1 и Org2, а также финальной записи определения чейнкода.

Если пакет чейнкода будет успешно развернут, в вашем терминале должно появиться примерно следующее:

Committed chaincode definition for chaincode 'basic' on channel 'mychannel':
Version: 1.0, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc, Approvals: [Org1MSP: true, Org2MSP: true]
Query chaincode definition successful on peer0.org2 on channel 'mychannel'
Chaincode initialization is not required

Подготовка примера приложения

Теперь давайте подготовим приложение на TypeScript Asset Transfer, которое будет взаимодействовать с развернутым умным контрактом.

Откройте терминал и перейдите в каталог application-gateway-typescript.

cd asset-transfer-basic/application-gateway-typescript

В этом каталоге находится пример приложения, разработанного с использованием клиентского API шлюза Fabric для Node.

Для установки зависимостей и сборки приложения выполните следующую команду. Ее выполнение займет некоторое время.

npm install

Зависимости, которые будут установлены, определены в файле package.json. Наиболее важная из них - пакет для Node.js @hyperledger/fabric-gateway; она обеспечивает соединение со шлюзом Fabric через его клиентский API, а также, используя учетные данные клиента, отправку транзакций, чтение результата их выполнения и получение событий.

После завершения команды npm install все готово к запуску приложения.

Давайте посмотрим на файлы примера приложения на TypeScript, которое используется в этом руководстве. Выполните следующую команду для вывода списка файлов каталога на экран:

ls

Вы должны увидеть следующее:

dist
node_modules
package-lock.json
package.json
src
tsconfig.json

Каталог src содержит исходный код клиентского приложения. Файлы JavaScript, сгенерированные из этого исходного кода в процессе сборки, расположены в каталоге dist и могут быть игнорированы.

Запуск примера приложения

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

Давайте запустим приложение и посмотрим на взаимодействие с каждой из функций умного контракта. В каталоге asset-transfer-basic/application-gateway-typescript выполните следующую команду:

npm start

Шаг 1: установление соединения gRPC с шлюзом

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

Примечание

Установление соединений gRPC требует значительных накладных расходов, поэтому установленное соединение должно быть сохранено приложением и использовано при всех взаимодействиях с шлюзом.

Предупреждение

Для обеспечения безопасности любых приватных данных, используемых в транзакциях, приложение должно подключаться к шлюзу Fabric той же организации, к которой относятся учетные данные клиента. Если в организации нет шлюзов, то следует использовать доверенный шлюз в другой организации.

Приложение, написанное на TypeScript, создает соединение gRPC, используя сертификат TLS удостоверяющего центра, чтобы можно было проверить подлинность сертификата TLS шлюза.

Чтобы соединение TLS было успешно установлено, адрес конечной точки, используемый клиентом, должен совпадать с адресом в сертификате TLS шлюза. Поскольку клиент обращается к Docker-контейнеру шлюза по адресу localhost, настройка gRPC указывает, что адрес конечной точки должен интерпретироваться как сконфигурированное имя узла шлюза.

const peerEndpoint = 'localhost:7051';

async function newGrpcConnection(): Promise<grpc.Client> {
    const tlsRootCert = await fs.readFile(tlsCertPath);
    const tlsCredentials = grpc.credentials.createSsl(tlsRootCert);
    return new grpc.Client(peerEndpoint, tlsCredentials, {
        'grpc.ssl_target_name_override': 'peer0.org1.example.com',
    });
}

Шаг 2: создание соединение с шлюзом

Затем приложение создает соединение с шлюзом (Gateway), которое будет использоваться для доступа к сетям (Networks - аналогу каналов), доступных для шлюза Fabric, и далее - к умным контрактам (Contracts), развернутым в этих сетях. У соединения Gateway есть три требования:

  1. Соединение gRPC к шлюзу Fabric.
  2. Учетные данные клиента, используемые для работы с сетью.
  3. Реализация создания цифровых подписей на основе учетных данных клиента.

Данный пример приложения использует сертификат X.509 пользователя из организации Org1 в качестве учетных данных клиента и создание подписи на основе закрытого ключа этого пользователя.

const client = await newGrpcConnection();

const gateway = connect({
    client,
    identity: await newIdentity(),
    signer: await newSigner(),
});

async function newIdentity(): Promise<Identity> {
    const credentials = await fs.readFile(certPath);
    return { mspId: 'Org1MSP', credentials };
}

async function newSigner(): Promise<Signer> {
    const privateKeyPem = await fs.readFile(keyPath);
    const privateKey = crypto.createPrivateKey(privateKeyPem);
    return signers.newPrivateKeySigner(privateKey);
}

Шаг 3: получение доступа к умному контракту, который будет вызван

Пример приложения использует соединение Gateway для получения ссылки на сеть (Network) и затем - на контракт по умолчанию (Contract) в чейнкоде, развернутом в этой сети.

const network = gateway.getNetwork(channelName);
const contract = network.getContract(chaincodeName);

Если пакет чейнкода включает в себя несколько умных контрактов, вы можете указать и имя чейнкода, и имя требуемого контракта в качестве аргументов при вызове метода getContract(). Например:

const contract = network.getContract(chaincodeName, smartContractName);

Шаг 4: заполнение реестра образцами активов

Сразу после первоначального развертывания пакета чейнкода, реестр пустой. Приложение вызывает метод submitTransaction(), чтобы выполнить функцию транзакции InitLedger, которая заполняет реестр образцами активов. Метод submitTransaction() использует шлюз Fabric для:

  1. Одобрения предложения транзакции.
  2. Отправки одобренной транзакции в службу упорядочения.
  3. Ожидания записи транзакции в реестр и обновление состояния реестра.

Вызов функции InitLedger в приложении выглядит так:

await contract.submitTransaction('InitLedger');

Шаг 5: вызов функций транзакции для чтения и записи активов

Теперь приложение готово выполнить бизнес-логику запросов, создания новых активов и обновления активов в реестре, вызывая функции транзакции в умном контракте.

Запрос всех активов

Приложение вызывает метод evaluateTransaction() для запроса данных из реестра, делая вызов транзакции только на чтение. Метод evaluateTransaction() использует шлюз Fabric для вызова функции транзакции и возврата ее результата. Транзакция не отправляется в службу упорядочения, обновление реестра в этом случае не происходит.

В примере ниже приложение получает все активы, созданные на одном из предыдущих шагов, когда заполнялся реестр.

Вызов функции GetAllAssets из приложения:

const resultBytes = await contract.evaluateTransaction('GetAllAssets');

const resultJson = utf8Decoder.decode(resultBytes);
const result = JSON.parse(resultJson);
console.log('*** Result:', result);

Примечание

Результаты функций транзакций всегда возвращаются в виде последовательности байтов, поскольку могут быть данными любого типа. Часто функции возвращают строковые значения или, как в примере выше, данные JSON в кодировке UTF-8. Ответственность за правильную интерпретацию этой байтовой последовательности лежит на приложении.

В терминале должен появиться примерно такой вывод:

*** Result: [
  {
    AppraisedValue: 300,
    Color: 'blue',
    ID: 'asset1',
    Owner: 'Tomoko',
    Size: 5,
    docType: 'asset'
  },
  {
    AppraisedValue: 400,
    Color: 'red',
    ID: 'asset2',
    Owner: 'Brad',
    Size: 5,
    docType: 'asset'
  },
  {
    AppraisedValue: 500,
    Color: 'green',
    ID: 'asset3',
    Owner: 'Jin Soo',
    Size: 10,
    docType: 'asset'
  },
  {
    AppraisedValue: 600,
    Color: 'yellow',
    ID: 'asset4',
    Owner: 'Max',
    Size: 10,
    docType: 'asset'
  },
  {
    AppraisedValue: 700,
    Color: 'black',
    ID: 'asset5',
    Owner: 'Adriana',
    Size: 15,
    docType: 'asset'
  },
  {
    AppraisedValue: 800,
    Color: 'white',
    ID: 'asset6',
    Owner: 'Michel',
    Size: 15,
    docType: 'asset'
  }
]

Создание нового актива

Приложение отправляет транзакцию для создания нового актива.

Вызов функции CreateAsset из приложения:

const assetId = `asset${Date.now()}`;

await contract.submitTransaction(
    'CreateAsset',
    assetId,
    'yellow',
    '5',
    'Tom',
    '1300',
);

Примечание

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

assetId, "yellow", "5", "Tom", "1300"

Соответствующая функция CreateAsset умного контракта ожидает следующую последовательность аргументов, определяющих объект актива:

ID, Color, Size, Owner, AppraisedValue

Обновление актива

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

Вызов функции TransferAsset из приложения:

const commit = await contract.submitAsync('TransferAsset', {
    arguments: [assetId, 'Saptha'],
});
const oldOwner = utf8Decoder.decode(commit.getResult());

console.log(`*** Successfully submitted transaction to transfer ownership from ${oldOwner} to Saptha`);
console.log('*** Waiting for transaction commit');

const status = await commit.getStatus();
if (!status.successful) {
    throw new Error(`Transaction ${status.transactionId} failed to commit with status code ${status.code}`);
}

console.log('*** Transaction committed successfully');

Вывод в терминале:

*** Successfully submitted transaction to transfer ownership from Tom to Saptha
*** Waiting for transaction commit
*** Transaction committed successfully

Запрос обновленного актива

Затем приложение оценивает результаты запроса обновленного актива, показывая, что он был создан с описанными свойствами, а потом передан новому владельцу.

Вызов функции ReadAsset из приложения:

const resultBytes = await contract.evaluateTransaction('ReadAsset', assetId);

const resultJson = utf8Decoder.decode(resultBytes);
const result = JSON.parse(resultJson);
console.log('*** Result:', result);

Вывод в терминале:

*** Result: {
    AppraisedValue: 1300,
    Color: 'yellow',
    ID: 'asset1639084597466',
    Owner: 'Saptha',
    Size: 5
}

Обработка ошибок транзакций

В конце давайте посмотрим на обработку ошибок при отправке транзакций. В примере ниже приложение осуществляет попытку отправить транзакцию с вызовом функции UpdateAsset, но указывает идентификатор несуществующего актива. Функция транзакции возвращает ошибку в ответе и вызов метода submitTransaction() завершается неудачей.

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

Неудачный вызов функции UpdateAsset из приложения:

try {
    await contract.submitTransaction(
        'UpdateAsset',
        'asset70',
        'blue',
        '5',
        'Tomoko',
        '300',
    );
    console.log('******** FAILED to return an error');
} catch (error) {
    console.log('*** Successfully caught the error: \n', error);
}

Вывод терминала (без трассировки стека для наглядности):

*** Successfully caught the error:
EndorseError: 10 ABORTED: failed to endorse transaction, see attached details for more info
    at ... {
  code: 10,
  details: [
    {
      address: 'peer0.org1.example.com:7051',
      message: 'error in simulation: transaction returned with failure: Error: The asset asset70 does not exist',
      mspId: 'Org1MSP'
    }
  ],
  cause: Error: 10 ABORTED: failed to endorse transaction, see attached details for more info
      at ... {
    code: 10,
    details: 'failed to endorse transaction, see attached details for more info',
    metadata: Metadata { internalRepr: [Map], options: {} }
  },
  transactionId: 'a92980d41eef1d6492d63acd5fbb6ef1db0f53252330ad28e548fedfdb9167fe'
}

Тип ошибки EndorseError означает, что сбой произошел на этапе одобрения, но приложению удалось успешно обратиться к шлюзу Fabric, о чем говорит код статуса gRPC ABORTED. Коды статуса gRPC UNAVAILABLE или DEADLINE_EXCEEDED означали бы недоступность шлюза Fabric или то, что ответ не был получен в определенный период ожидания и поэтому целесообразно повторить операцию.

Очистка

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

./network.sh down

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

Заключение

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