fabric-samples/asset-transfer-private-data/application-javascript/app.js
David Enyeart b3bc39ae5f Re-enable CI tests for asset-transfer-private-data JS app
Re-enable CI tests for asset-transfer-private-data JavaScript application.

Also re-add the error when private asset details are not found in the collection,
so that the CLI instructions work as desired, and to demonstrate error
handing in the client application.

Signed-off-by: David Enyeart <enyeart@us.ibm.com>
2020-08-20 16:05:42 -04:00

258 lines
13 KiB
JavaScript

/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
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 myChannel = 'mychannel';
const myChaincodeName = 'private';
const memberAssetCollectionName = 'assetCollection';
const org1PrivateCollectionName = 'Org1MSPPrivateCollection';
const org2PrivateCollectionName = 'Org2MSPPrivateCollection';
const mspOrg1 = 'Org1MSP';
const mspOrg2 = 'Org2MSP';
const Org1UserId = 'appUser1';
const Org2UserId = 'appUser2';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function initContractFromOrg1Identity() {
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, mspOrg1);
// 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, mspOrg1, 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: ${error}`);
process.exit(1);
}
}
async function initContractFromOrg2Identity() {
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, mspOrg2);
await registerAndEnrollUser(caOrg2Client, walletOrg2, mspOrg2, 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: ${error}`);
process.exit(1);
}
}
// Main workflow : usecase details at asset-transfer-private-data/chaincode-go/README.md
// This app uses fabric-samples/test-network based setup and the companion chaincode
// For this usecase illustration, we will use both Org1 & Org2 client identity from this same app
// In real world the Org1 & Org2 identity will be used in different apps to achieve asset transfer.
async function main() {
try {
/** ******* Fabric client init: Using Org1 identity to Org1 Peer ********** */
const gatewayOrg1 = await initContractFromOrg1Identity();
const networkOrg1 = await gatewayOrg1.getNetwork(myChannel);
const contractOrg1 = networkOrg1.getContract(myChaincodeName);
// Since this sample chaincode uses, Private Data Collection level endorsement policy, addDiscoveryInterest
// scopes the discovery service further to use the endorsement policies of collections, if any
contractOrg1.addDiscoveryInterest({ name: myChaincodeName, collectionNames: [memberAssetCollectionName, org1PrivateCollectionName] });
/** ~~~~~~~ Fabric client init: Using Org2 identity to Org2 Peer ~~~~~~~ */
const gatewayOrg2 = await initContractFromOrg2Identity();
const networkOrg2 = await gatewayOrg2.getNetwork(myChannel);
const contractOrg2 = networkOrg2.getContract(myChaincodeName);
contractOrg2.addDiscoveryInterest({ name: myChaincodeName, collectionNames: [memberAssetCollectionName, org2PrivateCollectionName] });
try {
// Sample transactions are listed below
// Add few sample Assets & transfers one of the asset from Org1 to Org2 as the new owner
let assetID1 = 'asset1';
let assetID2 = 'asset2';
const assetType = 'ValuableAsset';
let result;
let asset1Data = { objectType: assetType, assetID: assetID1, color: 'green', size: 20, appraisedValue: 100 };
let asset2Data = { objectType: assetType, assetID: assetID2, color: 'blue', size: 35, appraisedValue: 727 };
console.log('\n**************** As Org1 Client ****************');
console.log('Adding Assets to work with:\n--> Submit Transaction: CreateAsset ' + assetID1);
let statefulTxn = contractOrg1.createTransaction('CreateAsset');
//if you need to customize endorsement to specific set of Orgs, use setEndorsingOrganizations
//statefulTxn.setEndorsingOrganizations(mspOrg1);
let tmapData = Buffer.from(JSON.stringify(asset1Data));
statefulTxn.setTransient({
asset_properties: tmapData
});
result = await statefulTxn.submit();
//Add asset2
console.log('\n--> Submit Transaction: CreateAsset ' + assetID2);
statefulTxn = contractOrg1.createTransaction('CreateAsset');
tmapData = Buffer.from(JSON.stringify(asset2Data));
statefulTxn.setTransient({
asset_properties: tmapData
});
result = await statefulTxn.submit();
console.log('\n--> Evaluate Transaction: GetAssetByRange asset0-asset9');
// GetAssetByRange returns assets on the ledger with ID in the range of startKey (inclusive) and endKey (exclusive)
result = await contractOrg1.evaluateTransaction('GetAssetByRange', 'asset0', 'asset9');
console.log(' result: ' + prettyJSONString(result.toString()));
console.log('\n--> Evaluate Transaction: ReadAssetPrivateDetails from ' + org1PrivateCollectionName);
// ReadAssetPrivateDetails reads data from Org's private collection. Args: collectionName, assetID
result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1);
console.log(' result: ' + prettyJSONString(result.toString()));
console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~');
console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID1);
result = await contractOrg2.evaluateTransaction('ReadAsset', assetID1);
console.log(' result: ' + prettyJSONString(result.toString()));
let assetOwner = JSON.parse(result.toString()).owner;
console.log(' Asset owner: ' + Buffer.from(assetOwner, 'base64').toString());
// Org2 cannot ReadAssetPrivateDetails from Org1's private collection due to Collection policy
// Will fail: await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1);
// Buyer from Org2 agrees to buy the asset assetID1 //
// To purchase the asset, the buyer needs to agree to the same value as the asset owner
let dataForAgreement = { assetID: assetID1, appraisedValue: 100 };
console.log('\n--> Submit Transaction: AgreeToTransfer payload ' + JSON.stringify(dataForAgreement));
statefulTxn = contractOrg2.createTransaction('AgreeToTransfer');
tmapData = Buffer.from(JSON.stringify(dataForAgreement));
statefulTxn.setTransient({
asset_value: tmapData
});
result = await statefulTxn.submit();
//Buyer can withdraw the Agreement, using DeleteTranferAgreement
/*statefulTxn = contractOrg2.createTransaction('DeleteTranferAgreement');
statefulTxn.setEndorsingOrganizations(mspOrg2);
let dataForDeleteAgreement = { assetID: assetID1 };
tmapData = Buffer.from(JSON.stringify(dataForDeleteAgreement));
statefulTxn.setTransient({
agreement_delete: tmapData
});
result = await statefulTxn.submit();*/
console.log('\n**************** As Org1 Client ****************');
// All members can send txn ReadTransferAgreement, set by Org2 above
console.log('\n--> Evaluate Transaction: ReadTransferAgreement ' + assetID1);
result = await contractOrg1.evaluateTransaction('ReadTransferAgreement', assetID1);
console.log(' result: ' + prettyJSONString(result.toString()));
// Transfer the asset to Org2 //
// To transfer the asset, the owner needs to pass the MSP ID of new asset owner, and initiate the transfer
console.log('\n--> Submit Transaction: TransferAsset ' + assetID1);
let buyerDetails = { assetID: assetID1, buyerMSP: mspOrg2 };
statefulTxn = contractOrg1.createTransaction('TransferAsset');
tmapData = Buffer.from(JSON.stringify(buyerDetails));
statefulTxn.setTransient({
asset_owner: tmapData
});
result = await statefulTxn.submit();
//Again ReadAsset : results will show that the buyer identity now owns the asset:
console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID1);
result = await contractOrg1.evaluateTransaction('ReadAsset', assetID1);
console.log(' result: ' + prettyJSONString(result.toString()));
assetOwner = JSON.parse(result.toString()).owner;
console.log(' Asset owner: ' + Buffer.from(assetOwner, 'base64').toString());
//Confirm that transfer removed the private details from the Org1 collection:
console.log('\n--> Evaluate Transaction: ReadAssetPrivateDetails');
// ReadAssetPrivateDetails reads data from Org's private collection: Should return empty
result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1);
console.log(' result: ' + prettyJSONString(result.toString()));
console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID2);
result = await contractOrg1.evaluateTransaction('ReadAsset', assetID2);
console.log(' result: ' + prettyJSONString(result.toString()));
console.log('\n********* Demo deleting asset **************');
// Delete Asset2
console.log('--> Submit Transaction: DeleteAsset ' + assetID2);
statefulTxn = contractOrg1.createTransaction('DeleteAsset');
let dataForDelete = { assetID: assetID2 };
tmapData = Buffer.from(JSON.stringify(dataForDelete));
statefulTxn.setTransient({
asset_delete: tmapData
});
result = await statefulTxn.submit();
console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID2);
result = await contractOrg1.evaluateTransaction('ReadAsset', assetID2);
console.log(' result: ' + prettyJSONString(result.toString()));
console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~');
// Org2 can ReadAssetPrivateDetails: Org2 is owner, and private details exist in new owner's Collection
console.log('\n--> Evaluate Transaction as Org2: ReadAssetPrivateDetails ' + assetID1 + ' from ' + org2PrivateCollectionName);
result = await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org2PrivateCollectionName, assetID1);
console.log(' result: ' + prettyJSONString(result.toString()));
} 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 transaction: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();