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 2021-12-17 17:08:51 +05:30
parent e3b52cda75
commit 37ead7dd25
5 changed files with 164 additions and 286 deletions

View file

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

View file

@ -19,6 +19,7 @@
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"@grpc/grpc-js": "^1.5.0",
"@hyperledger/fabric-gateway": "^1.0.0"
},
"devDependencies": {

View file

@ -4,60 +4,19 @@
* SPDX-License-Identifier: Apache-2.0
*/
// pre-requisites:
// - fabric-sample two organization test-network setup with two peers, ordering service,
// and 2 certificate authorities
// ===> from directory test-network
// ./network.sh up createChannel -ca
//
// - Use the asset-transfer-events/chaincode-javascript chaincode deployed on
// the channel "mychannel". The following deploy command will package, install,
// approve, and commit the javascript chaincode, all the actions it takes
// to deploy a chaincode to a channel.
// ===> from directory test-network
// ./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-events/application-javascript
// node -v
// - npm installed code dependencies
// ===> from directory asset-transfer-events/application-javascript
// npm install
// - to build this test application
// ===> from directory asset-transfer-events/application-javascript
// npm prepare
// - to run this test application
// ===> from directory asset-transfer-events/application-javascript
// npm start
import * as grpc from '@grpc/grpc-js';
import { connect, Contract, Identity, Network, Signer, signers } from '@hyperledger/fabric-gateway';
import * as crypto from 'crypto';
import { promises as fs } from 'fs';
import * as path from 'path';
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 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';
const utf8Decoder = new TextDecoder();
let assetId = `asset${Date.now()}`;
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.
@ -67,10 +26,23 @@ async function main(): Promise<void> {
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
},
});
try {
let events: CloseableAsyncIterable<ChaincodeEvent> | undefined;
try {
// Get a network instance representing the channel where the smart contract is deployed.
const network = gateway.getNetwork(channelName);
@ -78,288 +50,135 @@ async function main(): Promise<void> {
const contract = network.getContract(chaincodeName);
//Start Listening to events.
startEventListening(network)
events = await startEventListening(network);
// Create a new asset on the ledger.
await createAsset(contract);
const firstBlockNumber = await createAsset(contract);
// Update an existing asset asynchronously.
await transferAssetAsync(contract);
// Update an existing asset.
await updateAsset(contract)
// Get the asset details by assetID.
await readAssetByID(contract);
// Transfer an existing asset.
await transferAsset(contract);
// Delete asset by assetID.
await deleteAssetByID(contract);
// Update an asset which does not exist.
await updateNonExistentAsset(contract)
console.log('************* BLOCK EVENTS with PRIVATE DATA **************');
//Generate new assetID
assetId = `asset${Date.now()}`;
// Create a new asset with private data on the ledger.
await createAssetPrivate(contract);
// Update an existing asset with private data asynchronously.
await transferAssetAsyncPrivate(contract);
// Get the asset details along with the private data by assetID.
await readAssetByIDPrivate(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));
main().catch(error => {
console.error('******** FAILED to run the application:', error);
process.exitCode = 1;
});
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',
});
}
/**
* Start listening to events.
*/
async function startEventListening(network: Network): Promise<CloseableAsyncIterable<ChaincodeEvent>> {
console.log('\n*** Start chaincode event listening\n');
async function newIdentity(): Promise<Identity> {
const credentials = await fs.readFile(certPath);
return { mspId, credentials };
}
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);
}
async function startEventListening(network: Network) {
console.log('Read chaincode events');
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(`Received event name: ${event.eventName}, payload: ${payload}, txID: ${event.transactionId}, blockNumber:${event.blockNumber}`);
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;
}
} finally {
// Ensure event iterator is closed when done reading.
events.close();
}
}
/**
* Submit a transaction synchronously, blocking until it has been committed to the ledger.
* Submit a transaction asynchronously to create a new asset.
*/
async function createAsset(contract: Contract): Promise<void> {
console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments');
async function createAsset(contract: Contract): Promise<bigint> {
console.log(`\n --> Submit Transaction: CreateAsset, creates ${assetId} owned by Tom with appraised value 100`);
await contract.submitTransaction(
'CreateAsset',
assetId,
'yellow',
'5',
'Tom',
'1300',
);
const result = await contract.submitAsync('CreateAsset',
{arguments: [assetId,'yellow','5','Tom','100',]});
console.log('*** Transaction committed successfully');
}
/**
* Submit transaction asynchronously, allowing the application to process the smart contract response (e.g. update a UI)
* while waiting for the commit notification.
*/
async function transferAssetAsync(contract: Contract): Promise<void> {
console.log('\n--> Async Submit Transaction: TransferAsset, updates existing asset owner');
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();
const status = await result.getStatus();
if (!status.successful) {
throw new Error(`Transaction ${status.transactionId} failed to commit with status code ${status.code}`);
throw new Error(`failed to commit transaction ${status.transactionId} with status code ${status.code}`);
}
console.log('*** Transaction committed successfully');
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')
}
// Evaluate a transaction to query ledger state by given ID.
async function readAssetByID(contract: Contract): Promise<void> {
console.log('\n--> Evaluate Transaction: ReadAsset, function returns asset attributes');
/**
* Submit transaction synchronously, to transfer the asset.
*/
async function transferAsset(contract: Contract): Promise<void> {
console.log(`\n--> Submit transaction: TransferAsset, ${assetId} to Saptha\n`);
const resultBytes = await contract.evaluateTransaction('ReadAsset', assetId);
await contract.submitTransaction('TransferAsset',
assetId, 'Saptha',);
const resultJson = utf8Decoder.decode(resultBytes);
const result = JSON.parse(resultJson);
checkAsset(mspId, result,'yellow','5','Saptha','1300')
console.log('*** Result:', result);
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 asset70');
console.log(`\n--> Submit transaction: DeleteAsset ${assetId}`);
await contract.submitTransaction(
'DeleteAsset',
await contract.submitTransaction('DeleteAsset',
assetId
);
console.log('*** Transaction committed successfully');
console.log('\n*** DeleteAsset committed successfully\n')
}
/**
* submitTransaction() will throw an error containing details of any error responses from the smart contract.
* Replay all the events from the start block.
*/
async function updateNonExistentAsset(contract: Contract): Promise<void>{
console.log('\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error');
async function replayChaincodeEvents(network:Network,startBlock:bigint):Promise<void>{
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);
}
}
/**
* Verify asset details.
*/
function checkAsset(mspId:string, asset:{
ID: string,
Color: string,
Size: string,
Owner: string,
AppraisedValue: string,
asset_properties:{
object_type: string,
asset_id: string,
Price: string,
salt: string
}
}, color:string, size:string, owner:string, appraisedValue:string, price?:string) {
console.log(`<-- Query results from ${mspId}`);
console.log(`*** verify asset ${asset.ID}`);
if (asset) {
if (asset.Color === color) {
console.log(`*** asset ${asset.ID} has color ${asset.Color}`);
} else {
console.log(`*** asset ${asset.ID} has color of ${asset.Color}`);
}
if (asset.Size === size) {
console.log(`*** asset ${asset.ID} has size ${asset.Size}`);
} else {
console.log(`*** Failed size check from ${mspId} - asset ${asset.ID} has size of ${asset.Size}`);
}
if (asset.Owner === owner) {
console.log(`*** asset ${asset.ID} owned by ${asset.Owner}`);
} else {
console.log(`*** Failed owner check from ${mspId} - asset ${asset.ID} owned by ${asset.Owner}`);
}
if (asset.AppraisedValue === appraisedValue) {
console.log(`*** asset ${asset.ID} has appraised value ${asset.AppraisedValue}`);
} else {
console.log(`*** Failed appraised value check from ${mspId} - asset ${asset.ID} has appraised value of ${asset.AppraisedValue}`);
}
if (price) {
if (asset.asset_properties && asset.asset_properties.Price === price) {
console.log(`*** asset ${asset.ID} has price ${asset.asset_properties.Price}`);
} else {
console.log(`*** Failed price check from ${mspId} - asset ${asset.ID} has price of ${asset.asset_properties.Price}`);
}
}
}
}
/**
* Submit transaction, blocking until the transaction has been committed on the ledger.
The 'transient' data will not get written to the ledger, and is used to send sensitive data to the trusted endorsing peers.
The gateway will only send this to peers that are included in the ownership policy of all collections accessed by the chaincode function.
*/
async function createAssetPrivate(contract: Contract): Promise<void> {
console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, Color, Size, Owner and AppraisedValue arguments');
// create the private data with salt and assign to the transaction
const asset_properties = {
object_type: 'asset_properties',
asset_id: assetId,
Price: '90',
salt: Buffer.from(Date.now().toString()).toString('hex')
};
await contract.submit(
'CreateAsset',
{ arguments:[assetId, 'blue', '10', 'James', '100'],
transientData: asset_properties,
endorsingOrganizations: [mspId]
}
);
console.log('*** Transaction committed successfully');
}
/**
* Submit transaction asynchronously, allowing the application to process the smart contract response (e.g. update a UI)
* while waiting for the commit notification.
*/
async function transferAssetAsyncPrivate(contract: Contract): Promise<void> {
console.log('\n--> Async Submit Transaction: TransferAsset, updates existing asset owner');
// update the private data with new salt and assign to the transaction
const asset_properties = {
object_type: 'asset_properties',
asset_id: assetId,
Price: '90',
salt: Buffer.from(Date.now().toString()).toString('hex')
};
const commit = await contract.submitAsync('TransferAsset', {
arguments: [assetId, 'David'],
transientData: asset_properties,
endorsingOrganizations: [mspId]
const events = await network.getChaincodeEvents(chaincodeName, {
startBlock
});
const oldOwner = utf8Decoder.decode(commit.getResult());
console.log(`*** Successfully submitted transaction to transfer ownership from ${oldOwner} to Davids`);
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}`);
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()
}
console.log('*** Transaction committed successfully');
}
// Evaluate a transaction to query ledger state by given ID.
async function readAssetByIDPrivate(contract: Contract): Promise<void> {
console.log('\n--> Evaluate Transaction: ReadAsset, function returns asset attributes');
const resultBytes = await contract.evaluateTransaction('ReadAsset', assetId);
const resultJson = utf8Decoder.decode(resultBytes);
const result = JSON.parse(resultJson);
checkAsset(mspId, result,'blue','10','David','100')
console.log('*** Result:', result);
}

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

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