fabric-samples/asset-transfer-private-data/application-javascript/app.js
Sijo Cherian 36a2d3a7d0
Switched private data JS app to commons util (#294)
* Switched private data JS app to commons util

Reusing JS app & ca utils
Refactored for Org1 & Org2
assettransfer-basic JS app update for commons util refactor

Signed-off-by: Sijo Cherian <sijo@ibm.com>

* fixed assettransfer-ledgerqueries & private usage of commons util refactor

Signed-off-by: Sijo Cherian <sijo@ibm.com>

Co-authored-by: Sijo Cherian <sijo@ibm.com>
2020-08-13 08:22:05 -04:00

258 lines
No EOL
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: 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();