From 1a79d131b4eb715a302942b4d9f9b94b0483c8b1 Mon Sep 17 00:00:00 2001 From: sapthasurendran <48531319+sapthasurendran@users.noreply.github.com> Date: Wed, 2 Feb 2022 22:31:42 +0530 Subject: [PATCH] Asset-Transfer-Events Migration to Use Fabric-Gateway (#565) * Gateway Migration for events application Signed-off-by: sapthasurendran * Documentation Error Fix Signed-off-by: sapthasurendran * Updated ci pipelines to include the app Readme update Wait for events to complete Refactor code for events replay Signed-off-by: sapthasurendran --- asset-transfer-events/README.md | 11 +- .../.eslintrc.json | 45 +++++ .../application-gateway-typescript/.gitignore | 14 ++ .../package.json | 32 +++ .../application-gateway-typescript/src/app.ts | 184 ++++++++++++++++++ .../src/connect.ts | 49 +++++ .../tsconfig.json | 18 ++ .../application-javascript/app.js | 6 +- ci/scripts/run-test-network-events.sh | 14 +- 9 files changed, 362 insertions(+), 11 deletions(-) create mode 100644 asset-transfer-events/application-gateway-typescript/.eslintrc.json create mode 100644 asset-transfer-events/application-gateway-typescript/.gitignore create mode 100755 asset-transfer-events/application-gateway-typescript/package.json create mode 100755 asset-transfer-events/application-gateway-typescript/src/app.ts create mode 100644 asset-transfer-events/application-gateway-typescript/src/connect.ts create mode 100755 asset-transfer-events/application-gateway-typescript/tsconfig.json diff --git a/asset-transfer-events/README.md b/asset-transfer-events/README.md index a91568dc..9f8d4feb 100644 --- a/asset-transfer-events/README.md +++ b/asset-transfer-events/README.md @@ -43,12 +43,12 @@ Note that the asset transfer implemented by the smart contract is a simplified s Like other samples, the Fabric test network is used to deploy and run this sample. Follow these steps in order: 1. Create the test network and a channel (from the `test-network` folder). - ``` + ``` ./network.sh up createChannel -c mychannel -ca ``` 1. Deploy one of the smart contract implementations (from the `test-network` folder). - ``` + ``` # To deploy the JavaScript chaincode implementation ./network.sh deployCC -ccn events -ccp ../asset-transfer-events/chaincode-javascript/ -ccl javascript -ccep "OR('Org1MSP.peer','Org2MSP.peer')" @@ -61,6 +61,11 @@ Like other samples, the Fabric test network is used to deploy and run this sampl # To run the Go sample application cd application-gateway-go go run . + + # To run the Typescript sample application + cd application-gateway-typescript + npm install + npm start ``` ## Clean up @@ -69,4 +74,4 @@ When you are finished, you can bring down the test network (from the `test-netwo ``` ./network.sh down -``` +``` \ No newline at end of file diff --git a/asset-transfer-events/application-gateway-typescript/.eslintrc.json b/asset-transfer-events/application-gateway-typescript/.eslintrc.json new file mode 100644 index 00000000..cc7230a8 --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/.eslintrc.json @@ -0,0 +1,45 @@ +{ + "env": { + "node": true, + "es6": true + }, + "root": true, + "ignorePatterns": [ + "dist/" + ], + "extends": [ + "eslint:recommended" + ], + "rules": { + "indent": [ + "error", + 4 + ], + "quotes": [ + "error", + "single" + ] + }, + "overrides": [ + { + "files": [ + "**/*.ts" + ], + "parser": "@typescript-eslint/parser", + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "impliedStrict": true + } + }, + "plugins": [ + "@typescript-eslint" + ], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ] + } + ] + } \ No newline at end of file diff --git a/asset-transfer-events/application-gateway-typescript/.gitignore b/asset-transfer-events/application-gateway-typescript/.gitignore new file mode 100644 index 00000000..99e5af9f --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/.gitignore @@ -0,0 +1,14 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + + +# Coverage directory used by tools like istanbul +coverage + +# Dependency directories +node_modules/ +jspm_packages/ + +# Compiled TypeScript files +dist diff --git a/asset-transfer-events/application-gateway-typescript/package.json b/asset-transfer-events/application-gateway-typescript/package.json new file mode 100755 index 00000000..c766be23 --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/package.json @@ -0,0 +1,32 @@ +{ + "name": "asset-transfer-basic", + "version": "1.0.0", + "description": "Asset Transfer Basic Application implemented in typeScript using fabric-gateway", + "main": "dist/index.js", + "typings": "dist/index.d.ts", + "engines": { + "node": ">=14" + }, + "scripts": { + "build": "tsc", + "build:watch": "tsc -w", + "lint": "eslint . --ext .ts", + "prepare": "npm run build", + "pretest": "npm run lint", + "start": "node dist/app.js" + }, + "engineStrict": true, + "author": "Hyperledger", + "license": "Apache-2.0", + "dependencies": { + "@grpc/grpc-js": "^1.5.0", + "@hyperledger/fabric-gateway": "^1.0.0" + }, + "devDependencies": { + "@tsconfig/node14": "^1.0.1", + "@typescript-eslint/eslint-plugin": "^5.6.0", + "@typescript-eslint/parser": "^5.6.0", + "eslint": "^8.4.1", + "typescript": "~4.5.2" + } +} diff --git a/asset-transfer-events/application-gateway-typescript/src/app.ts b/asset-transfer-events/application-gateway-typescript/src/app.ts new file mode 100755 index 00000000..36128004 --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/src/app.ts @@ -0,0 +1,184 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as grpc from '@grpc/grpc-js'; +import { ChaincodeEvent, CloseableAsyncIterable, connect, Contract, GatewayError, Network } from '@hyperledger/fabric-gateway'; +import { TextDecoder } from 'util'; + +import { newGrpcConnection, newIdentity, newSigner } from './connect'; + +const channelName = 'mychannel'; +const chaincodeName = 'events'; + +const utf8Decoder = new TextDecoder(); +const now = Date.now(); +const assetId = `asset${now}`; + + +async function main(): Promise { + // The gRPC client connection should be shared by all Gateway connections to this endpoint. + const client = await newGrpcConnection(); + + const gateway = connect({ + client, + identity: await newIdentity(), + signer: await newSigner(), + evaluateOptions: () => { + return { deadline: Date.now() + 5000 }; // 5 seconds + }, + endorseOptions: () => { + return { deadline: Date.now() + 15000 }; // 15 seconds + }, + submitOptions: () => { + return { deadline: Date.now() + 5000 }; // 5 seconds + }, + commitStatusOptions: () => { + return { deadline: Date.now() + 60000 }; // 1 minute + }, + }); + + let events: CloseableAsyncIterable | undefined; + + try { + // Get a network instance representing the channel where the smart contract is deployed. + const network = gateway.getNetwork(channelName); + + // Get the smart contract from the network. + const contract = network.getContract(chaincodeName); + + //Start Listening to events. + events = await startEventListening(network); + + // Create a new asset on the ledger. + const firstBlockNumber = await createAsset(contract); + + // Update an existing asset. + await updateAsset(contract) + + // Transfer an existing asset. + await transferAsset(contract); + + // Delete asset by assetID. + await deleteAssetByID(contract); + + // Replay all the events received. + await replayChaincodeEvents(network,firstBlockNumber) + + } finally { + events?.close(); + gateway.close(); + client.close(); + } +} + +main().catch(error => { + console.error('******** FAILED to run the application:', error); + process.exitCode = 1; +}); + +/** + * Start listening to events. + */ +async function startEventListening(network: Network): Promise> { + console.log('\n*** Start chaincode event listening\n'); + + const events = await network.getChaincodeEvents(chaincodeName); + + readEvents(events); + return events; +} + +/** + * Read events. + */ +async function readEvents(events: CloseableAsyncIterable): Promise { + try { + for await (const event of events) { + const payload = utf8Decoder.decode(event.payload); + console.log(`\n<-- Chaincode event received, name: ${event.eventName}, payload: ${payload}, txID: ${event.transactionId}, blockNumber:${event.blockNumber}`); + } + } catch (error: unknown) { + if (!(error instanceof GatewayError) || error.code !== grpc.status.CANCELLED) { + throw error; + } + } +} + +/** + * Submit a transaction asynchronously to create a new asset. + */ +async function createAsset(contract: Contract): Promise { + console.log(`\n --> Submit Transaction: CreateAsset, creates ${assetId} owned by Tom with appraised value 100`); + + const result = await contract.submitAsync('CreateAsset', + {arguments: [assetId,'yellow','5','Tom','100',]}); + + const status = await result.getStatus(); + if (!status.successful) { + throw new Error(`failed to commit transaction ${status.transactionId} with status code ${status.code}`); + } + + console.log('\n*** CreateAsset committed successfully\n') + + return status.blockNumber; +} +/** + * Submit transaction synchronously, to updateAsset the asset appraised value. + */ +async function updateAsset(contract: Contract): Promise { + console.log(`\n--> Submit transaction: UpdateAsset, ${assetId} update appraised value to 200`); + + await contract.submitTransaction('UpdateAsset', + assetId,'yellow','5','Tom','200',); + + console.log('\n*** UpdateAsset committed successfully\n') +} + +/** + * Submit transaction synchronously, to transfer the asset. + */ +async function transferAsset(contract: Contract): Promise { + console.log(`\n--> Submit transaction: TransferAsset, ${assetId} to Saptha\n`); + + await contract.submitTransaction('TransferAsset', + assetId, 'Saptha',); + + console.log('\n*** TransferAsset committed successfully\n') +} + +/** + * Submit a transaction synchronously to delete an asset by ID. + */ +async function deleteAssetByID(contract:Contract): Promise{ + console.log(`\n--> Submit transaction: DeleteAsset ${assetId}`); + + await contract.submitTransaction('DeleteAsset', + assetId + ); + + console.log('\n*** DeleteAsset committed successfully\n') +} + +/** + * Replay all the events from the start block. + */ +async function replayChaincodeEvents(network:Network,startBlock:bigint):Promise{ + + const events = await network.getChaincodeEvents(chaincodeName, { + startBlock + }); + try { + for await (const event of events) { + const payload = utf8Decoder.decode(event.payload); + console.log(`<-- Chaincode event replayed: ${event.eventName}, payload: ${payload}`); + if(event.eventName === 'DeleteAsset'){ + break + } + } + }finally { + events.close() + } +} \ No newline at end of file diff --git a/asset-transfer-events/application-gateway-typescript/src/connect.ts b/asset-transfer-events/application-gateway-typescript/src/connect.ts new file mode 100644 index 00000000..9290c7db --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/src/connect.ts @@ -0,0 +1,49 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as grpc from '@grpc/grpc-js'; +import { Identity, Signer, signers } from '@hyperledger/fabric-gateway'; +import * as crypto from 'crypto'; +import { promises as fs } from 'fs'; +import * as path from 'path'; + +const mspId = 'Org1MSP'; + +// Path to crypto materials. +const cryptoPath = path.resolve(__dirname, '..', '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com'); + +// Path to user private key directory. +const keyDirectoryPath = path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'keystore'); + +// Path to user certificate. +const certPath = path.resolve(cryptoPath, 'users', 'User1@org1.example.com', 'msp', 'signcerts', 'cert.pem'); + +// Path to peer tls certificate. +const tlsCertPath = path.resolve(cryptoPath, 'peers', 'peer0.org1.example.com', 'tls', 'ca.crt'); + +// Gateway peer endpoint. +const peerEndpoint = 'localhost:7051'; + +export async function newGrpcConnection(): Promise { + 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', + }); +} + +export async function newIdentity(): Promise { + const credentials = await fs.readFile(certPath); + return { mspId, credentials }; +} + +export async function newSigner(): Promise { + const files = await fs.readdir(keyDirectoryPath); + const keyPath = path.resolve(keyDirectoryPath, files[0]); + const privateKeyPem = await fs.readFile(keyPath); + const privateKey = crypto.createPrivateKey(privateKeyPem); + return signers.newPrivateKeySigner(privateKey); +} diff --git a/asset-transfer-events/application-gateway-typescript/tsconfig.json b/asset-transfer-events/application-gateway-typescript/tsconfig.json new file mode 100755 index 00000000..2052fb6e --- /dev/null +++ b/asset-transfer-events/application-gateway-typescript/tsconfig.json @@ -0,0 +1,18 @@ +{ + "extends":"@tsconfig/node14/tsconfig.json", + "compilerOptions": { + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "outDir": "dist", + "moduleResolution": "node", + "declaration": true, + "sourceMap": true, + "noImplicitAny": true + }, + "include": [ + "./src/**/*" + ], + "exclude": [ + "./src/**/*.spec.ts" + ] +} diff --git a/asset-transfer-events/application-javascript/app.js b/asset-transfer-events/application-javascript/app.js index 8774264a..10e4f367 100644 --- a/asset-transfer-events/application-javascript/app.js +++ b/asset-transfer-events/application-javascript/app.js @@ -47,13 +47,13 @@ // ./network.sh deployCC -ccn events -ccp ../asset-transfer-events/chaincode-javascript/ -ccl javascript -ccep "OR('Org1MSP.peer','Org2MSP.peer')" // // - Be sure that node.js is installed -// ===> from directory asset-transfer-sbe/application-javascript +// ===> from directory asset-transfer-events/application-javascript // node -v // - npm installed code dependencies -// ===> from directory asset-transfer-sbe/application-javascript +// ===> from directory asset-transfer-events/application-javascript // npm install // - to run this test application -// ===> from directory asset-transfer-sbe/application-javascript +// ===> from directory asset-transfer-events/application-javascript // node app.js // NOTE: If you see an error like these: diff --git a/ci/scripts/run-test-network-events.sh b/ci/scripts/run-test-network-events.sh index 01c656f1..e91c3db4 100755 --- a/ci/scripts/run-test-network-events.sh +++ b/ci/scripts/run-test-network-events.sh @@ -35,11 +35,15 @@ stopNetwork print "Remove wallet storage" rm -R ../asset-transfer-events/application-javascript/wallet -# Run Go gateway application + +# Run typescript gateway application createNetwork -print "Initializing Go gateway application" -pushd ../asset-transfer-events/application-gateway-go -print "Executing application" -go run . +print "Initializing typescript application" +pushd ../asset-transfer-events/application-gateway-typescript +npm install +print "Build app" +npm run build +print "Executing dist/app.js" +npm start popd stopNetwork