Asset-Transfer-Events Migration to Use Fabric-Gateway (#565)

* Gateway Migration for events application

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* Documentation Error Fix

Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>

* Updated ci pipelines to include the app
Readme update
Wait for events to complete
Refactor code for events replay
Signed-off-by: sapthasurendran <saptha.surendran@ibm.com>
This commit is contained in:
sapthasurendran 2022-02-02 22:31:42 +05:30 committed by GitHub
parent 980da6f8aa
commit 1a79d131b4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 362 additions and 11 deletions

View file

@ -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
```
```

View file

@ -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"
]
}
]
}

View file

@ -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

View file

@ -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"
}
}

View file

@ -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<void> {
// 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<ChaincodeEvent> | 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<CloseableAsyncIterable<ChaincodeEvent>> {
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<ChaincodeEvent>): Promise<void> {
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<bigint> {
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<void> {
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<void> {
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<void>{
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<void>{
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()
}
}

View file

@ -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<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',
});
}
export async function newIdentity(): Promise<Identity> {
const credentials = await fs.readFile(certPath);
return { mspId, credentials };
}
export async function newSigner(): Promise<Signer> {
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);
}

View file

@ -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"
]
}

View file

@ -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:

View file

@ -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