mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 23:45:10 +00:00
Ensure that all sample applications return exit code 1 upon failure so that github actions can report the failure. Signed-off-by: David Enyeart <enyeart@us.ibm.com>
356 lines
15 KiB
JavaScript
356 lines
15 KiB
JavaScript
/*
|
|
* Copyright IBM Corp. All Rights Reserved.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
/**
|
|
* A test application to show state based endorsements operations with a running
|
|
* asset-transfer-sbe chaincode with discovery.
|
|
* -- How to submit a transaction
|
|
* -- How to query
|
|
* -- How to limit the organizations involved in a transaction
|
|
*
|
|
* To see the SDK workings, try setting the logging to show on the console before running
|
|
* export HFC_LOGGING='{"debug":"console"}'
|
|
*/
|
|
|
|
// pre-requisites:
|
|
// - fabric-sample two organization test-network setup with two peers, ordering service,
|
|
// and 2 certificate authorities
|
|
// ===> from directory /fabric-samples/test-network
|
|
// ./network.sh up createChannel -ca
|
|
// - Use any of the asset-transfer-sbe chaincodes deployed on the channel "mychannel"
|
|
// with the chaincode name of "sbe". 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 /fabric-samples/test-network
|
|
// ./network.sh deployCC -ccn sbe -ccp ../asset-transfer-sbe/chaincode-typescript/ -ccl typescript
|
|
// - Be sure that node.js is installed
|
|
// ===> from directory /fabric-samples/asset-transfer-sbe/application-javascript
|
|
// node -v
|
|
// - npm installed code dependencies
|
|
// ===> from directory /fabric-samples/asset-transfer-sbe/application-javascript
|
|
// npm install
|
|
// - to run this test application
|
|
// ===> from directory /fabric-samples/asset-transfer-sbe/application-javascript
|
|
// node app.js
|
|
|
|
// NOTE: If you see an error like these:
|
|
/*
|
|
|
|
Error in setup: Error: DiscoveryService: mychannel error: access denied
|
|
|
|
OR
|
|
|
|
Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]]
|
|
|
|
*/
|
|
// Delete the /fabric-samples/asset-transfer-sbe/application-javascript/wallet directory
|
|
// and retry this application.
|
|
//
|
|
// The certificate authority must have been restarted and the saved certificates for the
|
|
// admin and application user are not valid. Deleting the wallet store will force these to be reset
|
|
// with the new certificate authority.
|
|
//
|
|
|
|
const { Gateway, Wallets } = require('fabric-network');
|
|
const FabricCAServices = require('fabric-ca-client');
|
|
const path = require('path');
|
|
const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
|
|
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
|
|
|
|
const channelName = 'mychannel';
|
|
const chaincodeName = 'sbe';
|
|
|
|
const org1 = 'Org1MSP';
|
|
const org2 = 'Org2MSP';
|
|
const Org1UserId = 'appUser1';
|
|
const Org2UserId = 'appUser2';
|
|
|
|
async function initGatewayForOrg1() {
|
|
console.log('\n--> Fabric client user & Gateway init: Using Org1 identity to Org1 Peer');
|
|
// build an in memory object with the network configuration (also known as a connection profile)
|
|
const ccpOrg1 = buildCCPOrg1();
|
|
|
|
// build an instance of the fabric ca services client based on
|
|
// the information in the network configuration
|
|
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
|
|
|
|
// setup the wallet to cache the credentials of the application user, on the app server locally
|
|
const walletPathOrg1 = path.join(__dirname, 'wallet', 'org1');
|
|
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
|
|
|
|
// in a real application this would be done on an administrative flow, and only once
|
|
// stores admin identity in local wallet, if needed
|
|
await enrollAdmin(caOrg1Client, walletOrg1, org1);
|
|
// register & enroll application user with CA, which is used as client identify to make chaincode calls
|
|
// and stores app user identity in local wallet
|
|
// In a real application this would be done only when a new user was required to be added
|
|
// and would be part of an administrative flow
|
|
await registerAndEnrollUser(caOrg1Client, walletOrg1, org1, Org1UserId, 'org1.department1');
|
|
|
|
try {
|
|
// Create a new gateway for connecting to Org's peer node.
|
|
const gatewayOrg1 = new Gateway();
|
|
//connect using Discovery enabled
|
|
await gatewayOrg1.connect(ccpOrg1,
|
|
{ wallet: walletOrg1, identity: Org1UserId, discovery: { enabled: true, asLocalhost: true } });
|
|
|
|
return gatewayOrg1;
|
|
} catch (error) {
|
|
console.error(`Error in connecting to gateway for Org1: ${error}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
async function initGatewayForOrg2() {
|
|
console.log('\n--> Fabric client user & Gateway init: Using Org2 identity to Org2 Peer');
|
|
const ccpOrg2 = buildCCPOrg2();
|
|
const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com');
|
|
|
|
const walletPathOrg2 = path.join(__dirname, 'wallet', 'org2');
|
|
const walletOrg2 = await buildWallet(Wallets, walletPathOrg2);
|
|
|
|
await enrollAdmin(caOrg2Client, walletOrg2, org2);
|
|
await registerAndEnrollUser(caOrg2Client, walletOrg2, org2, Org2UserId, 'org2.department1');
|
|
|
|
try {
|
|
// Create a new gateway for connecting to Org's peer node.
|
|
const gatewayOrg2 = new Gateway();
|
|
await gatewayOrg2.connect(ccpOrg2,
|
|
{ wallet: walletOrg2, identity: Org2UserId, discovery: { enabled: true, asLocalhost: true } });
|
|
|
|
return gatewayOrg2;
|
|
} catch (error) {
|
|
console.error(`Error in connecting to gateway for Org2: ${error}`);
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
function checkAsset(org, assetKey, resultBuffer, value, ownerOrg) {
|
|
let asset;
|
|
if (resultBuffer) {
|
|
asset = JSON.parse(resultBuffer.toString('utf8'));
|
|
}
|
|
|
|
if (asset && value) {
|
|
if (asset.Value === value && asset.OwnerOrg === ownerOrg) {
|
|
console.log(`*** Result from ${org} - asset ${asset.ID} has value of ${asset.Value} and owned by ${asset.OwnerOrg}`);
|
|
} else {
|
|
console.log(`*** Failed from ${org} - asset ${asset.ID} has value of ${asset.Value} and owned by ${asset.OwnerOrg}`);
|
|
}
|
|
} else if (!asset && value === 0 ) {
|
|
console.log(`*** Success from ${org} - asset ${assetKey} does not exist`);
|
|
} else {
|
|
console.log('*** Failed - asset read failed');
|
|
}
|
|
}
|
|
|
|
async function readAssetByBothOrgs(assetKey, value, ownerOrg, contractOrg1, contractOrg2) {
|
|
if (value) {
|
|
console.log(`\n--> Evaluate Transaction: ReadAsset, - ${assetKey} should have a value of ${value} and owned by ${ownerOrg}`);
|
|
} else {
|
|
console.log(`\n--> Evaluate Transaction: ReadAsset, - ${assetKey} should not exist`);
|
|
}
|
|
let resultBuffer;
|
|
resultBuffer = await contractOrg1.evaluateTransaction('ReadAsset', assetKey);
|
|
checkAsset('Org1', assetKey, resultBuffer, value, ownerOrg);
|
|
resultBuffer = await contractOrg2.evaluateTransaction('ReadAsset', assetKey);
|
|
checkAsset('Org2', assetKey, resultBuffer, value, ownerOrg);
|
|
}
|
|
|
|
// This application uses fabric-samples/test-network based setup and the companion chaincode
|
|
// For this illustration, both Org1 & Org2 client identities will be used, however
|
|
// notice they are used by two different "gateway"s to simulate two different running
|
|
// applications from two different organizations.
|
|
async function main() {
|
|
try {
|
|
// use a random key so that we can run multiple times
|
|
const assetKey = `asset-${Math.floor(Math.random() * 100) + 1}`;
|
|
|
|
/** ******* Fabric client init: Using Org1 identity to Org1 Peer ******* */
|
|
const gatewayOrg1 = await initGatewayForOrg1();
|
|
const networkOrg1 = await gatewayOrg1.getNetwork(channelName);
|
|
const contractOrg1 = networkOrg1.getContract(chaincodeName);
|
|
|
|
/** ******* Fabric client init: Using Org2 identity to Org2 Peer ******* */
|
|
const gatewayOrg2 = await initGatewayForOrg2();
|
|
const networkOrg2 = await gatewayOrg2.getNetwork(channelName);
|
|
const contractOrg2 = networkOrg2.getContract(chaincodeName);
|
|
|
|
try {
|
|
let transaction;
|
|
|
|
try {
|
|
// Create an asset by organization Org1, this will require that both organization endorse.
|
|
// The endorsement will be handled by Discovery, since the gateway was connected with discovery enabled.
|
|
console.log(`\n--> Submit Transaction: CreateAsset, ${assetKey} as Org1 - endorsed by Org1 and Org2`);
|
|
await contractOrg1.submitTransaction('CreateAsset', assetKey, '100', 'Tom');
|
|
console.log('*** Result: committed, now asset will only require Org1 to endorse');
|
|
} catch (createError) {
|
|
console.log(`*** Failed: create - ${createError}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
await readAssetByBothOrgs(assetKey, 100, org1, contractOrg1, contractOrg2);
|
|
|
|
try {
|
|
// Since the gateway is using discovery we should limit the organizations used by
|
|
// discovery to endorse. This way we only have to know the organization and not
|
|
// the actual peers that may be active at any given time.
|
|
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org1 - endorse by Org1`);
|
|
transaction = contractOrg1.createTransaction('UpdateAsset');
|
|
transaction.setEndorsingOrganizations(org1);
|
|
await transaction.submit(assetKey, '200');
|
|
console.log('*** Result: committed');
|
|
} catch (updateError) {
|
|
console.log(`*** Failed: update - ${updateError}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
await readAssetByBothOrgs(assetKey, 200, org1, contractOrg1, contractOrg2);
|
|
|
|
try {
|
|
// Submit a transaction to make an update to the asset that has a key-level endorsement policy
|
|
// set to only allow Org1 to make updates. The following example will not use the "setEndorsingOrganizations"
|
|
// to limit the organizations that will do the endorsement, this means that it will be sent to all
|
|
// organizations in the chaincode endorsement policy. When Org1 endorses, the transaction will be committed
|
|
// if Org2 endorses or not.
|
|
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org1 - endorse by Org1 and Org2`);
|
|
transaction = contractOrg1.createTransaction('UpdateAsset');
|
|
await transaction.submit(assetKey, '300');
|
|
console.log('*** Result: committed - because Org1 and Org2 both endorsed, while only the Org1 endorsement was required and checked');
|
|
} catch (updateError) {
|
|
console.log(`*** Failed: update - ${updateError}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
await readAssetByBothOrgs(assetKey, 300, org1, contractOrg1, contractOrg2);
|
|
|
|
try {
|
|
// Again submit the change to both Organizations by not using "setEndorsingOrganizations". Since only
|
|
// Org1 is required to approve, the transaction will be committed.
|
|
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org2 - endorse by Org1 and Org2`);
|
|
transaction = contractOrg2.createTransaction('UpdateAsset');
|
|
await transaction.submit(assetKey, '400');
|
|
console.log('*** Result: committed - because Org1 was on the discovery list, Org2 did not endorse');
|
|
} catch (updateError) {
|
|
console.log(`*** Failed: update - ${updateError}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
await readAssetByBothOrgs(assetKey, 400, org1, contractOrg1, contractOrg2);
|
|
|
|
try {
|
|
// Try to update by sending only to Org2, since the state-based-endorsement says that
|
|
// Org1 is the only organization allowed to update, the transaction will fail.
|
|
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org2 - endorse by Org2`);
|
|
transaction = contractOrg2.createTransaction('UpdateAsset');
|
|
transaction.setEndorsingOrganizations(org2);
|
|
await transaction.submit(assetKey, '500');
|
|
console.log('*** Failed: committed - this should have failed to endorse and commit');
|
|
} catch (updateError) {
|
|
console.log(`*** Successfully caught the error: \n ${updateError}`);
|
|
}
|
|
|
|
await readAssetByBothOrgs(assetKey, 400, org1, contractOrg1, contractOrg2);
|
|
|
|
try {
|
|
// Make a change to the state-based-endorsement policy making Org2 the owner.
|
|
console.log(`\n--> Submit Transaction: TransferAsset ${assetKey}, as Org1 - endorse by Org1`);
|
|
transaction = contractOrg1.createTransaction('TransferAsset');
|
|
transaction.setEndorsingOrganizations(org1);
|
|
await transaction.submit(assetKey, 'Henry', org2);
|
|
console.log('*** Result: committed');
|
|
} catch (transferError) {
|
|
console.log(`*** Failed: transfer - ${transferError}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
await readAssetByBothOrgs(assetKey, 400, org2, contractOrg1, contractOrg2);
|
|
|
|
try {
|
|
// Make sure that Org2 can now make updates, notice how the transaction has limited the
|
|
// endorsement to only Org2.
|
|
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org2 - endorse by Org2`);
|
|
transaction = contractOrg2.createTransaction('UpdateAsset');
|
|
transaction.setEndorsingOrganizations(org2);
|
|
await transaction.submit(assetKey, '600');
|
|
console.log('*** Result: committed');
|
|
} catch (updateError) {
|
|
console.log(`*** Failed: update - ${updateError}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
await readAssetByBothOrgs(assetKey, 600, org2, contractOrg1, contractOrg2);
|
|
|
|
try {
|
|
// With Org2 now the owner and the state-based-endorsement policy only allowing organization Org2
|
|
// to make updates, a transaction only to Org1 will fail.
|
|
console.log(`\n--> Submit Transaction: UpdateAsset ${assetKey}, as Org1 - endorse by Org1`);
|
|
transaction = contractOrg1.createTransaction('UpdateAsset');
|
|
transaction.setEndorsingOrganizations(org1);
|
|
await transaction.submit(assetKey, '700');
|
|
console.log('*** Failed: committed - this should have failed to endorse and commit');
|
|
} catch (updateError) {
|
|
console.log(`*** Successfully caught the error: \n ${updateError}`);
|
|
}
|
|
|
|
await readAssetByBothOrgs(assetKey, 600, org2, contractOrg1, contractOrg2);
|
|
|
|
try {
|
|
// With Org2 the owner and the state-based-endorsement policy only allowing organization Org2
|
|
// to make updates, a transaction to delete by Org1 will fail.
|
|
console.log(`\n--> Submit Transaction: DeleteAsset ${assetKey}, as Org1 - endorse by Org1`);
|
|
transaction = contractOrg1.createTransaction('DeleteAsset');
|
|
transaction.setEndorsingOrganizations(org1);
|
|
await transaction.submit(assetKey);
|
|
console.log('*** Failed: committed - this should have failed to endorse and commit');
|
|
} catch (updateError) {
|
|
console.log(`*** Successfully caught the error: \n ${updateError}`);
|
|
}
|
|
|
|
try {
|
|
// With Org2 the owner and the state-based-endorsement policy only allowing organization Org2
|
|
// to make updates, a transaction to delete by Org2 will succeed.
|
|
console.log(`\n--> Submit Transaction: DeleteAsset ${assetKey}, as Org2 - endorse by Org2`);
|
|
transaction = contractOrg2.createTransaction('DeleteAsset');
|
|
transaction.setEndorsingOrganizations(org2);
|
|
await transaction.submit(assetKey);
|
|
console.log('*** Result: committed');
|
|
} catch (deleteError) {
|
|
console.log(`*** Failed: delete - ${deleteError}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
// The asset should now be deleted, both orgs should not be able to read it
|
|
try {
|
|
await readAssetByBothOrgs(assetKey, 0, org2, contractOrg1, contractOrg2);
|
|
} catch (readDeleteError) {
|
|
console.log(`*** Successfully caught the error: ${readDeleteError}`);
|
|
}
|
|
|
|
} catch (runError) {
|
|
console.error(`Error in transaction: ${runError}`);
|
|
if (runError.stack) {
|
|
console.error(runError.stack);
|
|
}
|
|
process.exit(1);
|
|
} finally {
|
|
// Disconnect from the gateway peer when all work for this client identity is complete
|
|
gatewayOrg1.disconnect();
|
|
gatewayOrg2.disconnect();
|
|
}
|
|
} catch (error) {
|
|
console.error(`Error in setup: ${error}`);
|
|
if (error.stack) {
|
|
console.error(error.stack);
|
|
}
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
main();
|