mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
Add Asset-Transfer-Events (#325)
Add new chaincode and javascript application to demostrate the use of chaincode events and block events with private data. Signed-off-by: Bret Harrison <beharrison@nc.rr.com>
This commit is contained in:
parent
0088222df5
commit
9f245809bb
15 changed files with 1143 additions and 1 deletions
|
|
@ -0,0 +1,5 @@
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
coverage
|
||||||
36
asset-transfer-events/application-javascript/.eslintrc.js
Normal file
36
asset-transfer-events/application-javascript/.eslintrc.js
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
mocha: true
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 8,
|
||||||
|
sourceType: 'script'
|
||||||
|
},
|
||||||
|
extends: 'eslint:recommended',
|
||||||
|
rules: {
|
||||||
|
indent: ['error', 'tab'],
|
||||||
|
'linebreak-style': ['error', 'unix'],
|
||||||
|
quotes: ['error', 'single'],
|
||||||
|
semi: ['error', 'always'],
|
||||||
|
'no-unused-vars': ['error', { args: 'none' }],
|
||||||
|
'no-console': 'off',
|
||||||
|
curly: 'error',
|
||||||
|
eqeqeq: 'error',
|
||||||
|
'no-throw-literal': 'error',
|
||||||
|
'no-var': 'error',
|
||||||
|
'dot-notation': 'error',
|
||||||
|
'no-trailing-spaces': 'error',
|
||||||
|
'no-use-before-define': 'error',
|
||||||
|
'no-useless-call': 'error',
|
||||||
|
'no-with': 'error',
|
||||||
|
'operator-linebreak': 'error',
|
||||||
|
yoda: 'error',
|
||||||
|
'quote-props': ['error', 'as-needed']
|
||||||
|
}
|
||||||
|
};
|
||||||
14
asset-transfer-events/application-javascript/.gitignore
vendored
Normal file
14
asset-transfer-events/application-javascript/.gitignore
vendored
Normal 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/
|
||||||
|
package-lock.json
|
||||||
|
|
||||||
|
wallet
|
||||||
|
!wallet/.gitkeep
|
||||||
545
asset-transfer-events/application-javascript/app.js
Normal file
545
asset-transfer-events/application-javascript/app.js
Normal file
|
|
@ -0,0 +1,545 @@
|
||||||
|
/*
|
||||||
|
* Copyright IBM Corp. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Application that shows events when creating and updating an asset
|
||||||
|
* -- How to register a contract listener for chaincode events
|
||||||
|
* -- How to get the chaincode event name and value from the chaincode event
|
||||||
|
* -- How to retrieve the transaction and block information from the chaincode event
|
||||||
|
* -- How to register a block listener for full block events
|
||||||
|
* -- How to retrieve the transaction and block information from the full block event
|
||||||
|
* -- How to register to recieve private data associated with transactions when
|
||||||
|
* registering a block listener
|
||||||
|
* -- How to retreive the private data from the full block event
|
||||||
|
* -- The listener will be notified of an event at anytime. Notice that events will
|
||||||
|
* be posted by the listener after the application activity causing the ledger change
|
||||||
|
* and during other application activity unrelated to the event
|
||||||
|
* -- How to connect to a Gateway that will not use events when submitting transactions.
|
||||||
|
* This may be useful when the application does not want to wait for the peer to commit
|
||||||
|
* blocks and notify the application.
|
||||||
|
*
|
||||||
|
* To see the SDK workings, try setting the logging to be displayed on the console
|
||||||
|
* before executing this application.
|
||||||
|
* export HFC_LOGGING='{"debug":"console"}'
|
||||||
|
* See the following on how the SDK is working with the Peer's Event Services
|
||||||
|
* https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html
|
||||||
|
*
|
||||||
|
* See the following for more details on using the Node SDK
|
||||||
|
* https://hyperledger.github.io/fabric-sdk-node/release-2.2/module-fabric-network.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 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 -ccl javascript -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
|
||||||
|
//
|
||||||
|
// - Be sure that node.js is installed
|
||||||
|
// ===> from directory asset-transfer-sbe/application-javascript
|
||||||
|
// node -v
|
||||||
|
// - npm installed code dependencies
|
||||||
|
// ===> from directory asset-transfer-sbe/application-javascript
|
||||||
|
// npm install
|
||||||
|
// - to run this test application
|
||||||
|
// ===> from directory 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.
|
||||||
|
//
|
||||||
|
|
||||||
|
// use this to set logging, must be set before the require('fabric-network');
|
||||||
|
process.env.HFC_LOGGING = '{"debug": "./debug.log"}';
|
||||||
|
|
||||||
|
const { Gateway, Wallets } = require('fabric-network');
|
||||||
|
const EventStrategies = require('fabric-network/lib/impl/event/defaulteventhandlerstrategies');
|
||||||
|
const FabricCAServices = require('fabric-ca-client');
|
||||||
|
const path = require('path');
|
||||||
|
const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
|
||||||
|
const { buildCCPOrg1, buildWallet } = require('../../test-application/javascript/AppUtil.js');
|
||||||
|
|
||||||
|
const channelName = 'mychannel';
|
||||||
|
const chaincodeName = 'events';
|
||||||
|
|
||||||
|
const org1 = 'Org1MSP';
|
||||||
|
const Org1UserId = 'appUser1';
|
||||||
|
|
||||||
|
const RED = '\x1b[31m\n';
|
||||||
|
const GREEN = '\x1b[32m\n';
|
||||||
|
const BLUE = '\x1b[34m';
|
||||||
|
const RESET = '\x1b[0m';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a sleep -- asynchronous wait
|
||||||
|
* @param ms the time in milliseconds to sleep for
|
||||||
|
*/
|
||||||
|
function sleep(ms) {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function initGatewayForOrg1(useCommitEvents) {
|
||||||
|
console.log(`${GREEN}--> Fabric client user & Gateway init: Using Org1 identity to Org1 Peer${RESET}`);
|
||||||
|
// 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();
|
||||||
|
|
||||||
|
if (useCommitEvents) {
|
||||||
|
await gatewayOrg1.connect(ccpOrg1, {
|
||||||
|
wallet: walletOrg1,
|
||||||
|
identity: Org1UserId,
|
||||||
|
discovery: { enabled: true, asLocalhost: true }
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await gatewayOrg1.connect(ccpOrg1, {
|
||||||
|
wallet: walletOrg1,
|
||||||
|
identity: Org1UserId,
|
||||||
|
discovery: { enabled: true, asLocalhost: true },
|
||||||
|
eventHandlerOptions: EventStrategies.NONE
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return gatewayOrg1;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error in connecting to gateway for Org1: ${error}`);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkAsset(org, resultBuffer, color, size, owner, appraisedValue, price) {
|
||||||
|
console.log(`${GREEN}<-- Query results from ${org}${RESET}`);
|
||||||
|
|
||||||
|
let asset;
|
||||||
|
if (resultBuffer) {
|
||||||
|
asset = JSON.parse(resultBuffer.toString('utf8'));
|
||||||
|
} else {
|
||||||
|
console.log(`${RED}*** Failed to read asset${RESET}`);
|
||||||
|
}
|
||||||
|
console.log(`*** verify asset ${asset.ID}`);
|
||||||
|
|
||||||
|
if (asset) {
|
||||||
|
if (asset.Color === color) {
|
||||||
|
console.log(`*** asset ${asset.ID} has color ${asset.Color}`);
|
||||||
|
} else {
|
||||||
|
console.log(`${RED}*** asset ${asset.ID} has color of ${asset.Color}${RESET}`);
|
||||||
|
}
|
||||||
|
if (asset.Size === size) {
|
||||||
|
console.log(`*** asset ${asset.ID} has size ${asset.Size}`);
|
||||||
|
} else {
|
||||||
|
console.log(`${RED}*** Failed size check from ${org} - asset ${asset.ID} has size of ${asset.Size}${RESET}`);
|
||||||
|
}
|
||||||
|
if (asset.Owner === owner) {
|
||||||
|
console.log(`*** asset ${asset.ID} owned by ${asset.Owner}`);
|
||||||
|
} else {
|
||||||
|
console.log(`${RED}*** Failed owner check from ${org} - asset ${asset.ID} owned by ${asset.Owner}${RESET}`);
|
||||||
|
}
|
||||||
|
if (asset.AppraisedValue === appraisedValue) {
|
||||||
|
console.log(`*** asset ${asset.ID} has appraised value ${asset.AppraisedValue}`);
|
||||||
|
} else {
|
||||||
|
console.log(`${RED}*** Failed appraised value check from ${org} - asset ${asset.ID} has appraised value of ${asset.AppraisedValue}${RESET}`);
|
||||||
|
}
|
||||||
|
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(`${RED}*** Failed price check from ${org} - asset ${asset.ID} has price of ${asset.asset_properties.Price}${RESET}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTransactionData(transactionData) {
|
||||||
|
const creator = transactionData.actions[0].header.creator;
|
||||||
|
console.log(` - submitted by: ${creator.mspid}-${creator.id_bytes.toString('hex')}`);
|
||||||
|
for (const endorsement of transactionData.actions[0].payload.action.endorsements) {
|
||||||
|
console.log(` - endorsed by: ${endorsement.endorser.mspid}-${endorsement.endorser.id_bytes.toString('hex')}`);
|
||||||
|
}
|
||||||
|
const chaincode = transactionData.actions[0].payload.chaincode_proposal_payload.input.chaincode_spec;
|
||||||
|
console.log(` - chaincode:${chaincode.chaincode_id.name}`);
|
||||||
|
console.log(` - function:${chaincode.input.args[0].toString()}`);
|
||||||
|
for (let x = 1; x < chaincode.input.args.length; x++) {
|
||||||
|
console.log(` - arg:${chaincode.input.args[x].toString()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
console.log(`${BLUE} **** START ****${RESET}`);
|
||||||
|
try {
|
||||||
|
let randomNumber = Math.floor(Math.random() * 1000) + 1;
|
||||||
|
// use a random key so that we can run multiple times
|
||||||
|
let assetKey = `item-${randomNumber}`;
|
||||||
|
|
||||||
|
/** ******* Fabric client init: Using Org1 identity to Org1 Peer ******* */
|
||||||
|
const gateway1Org1 = await initGatewayForOrg1(true); // transaction handling uses commit events
|
||||||
|
const gateway2Org1 = await initGatewayForOrg1();
|
||||||
|
|
||||||
|
try {
|
||||||
|
//
|
||||||
|
// - - - - - - C H A I N C O D E E V E N T S
|
||||||
|
//
|
||||||
|
console.log(`${BLUE} **** CHAINCODE EVENTS ****${RESET}`);
|
||||||
|
let transaction;
|
||||||
|
let listener;
|
||||||
|
const network1Org1 = await gateway1Org1.getNetwork(channelName);
|
||||||
|
const contract1Org1 = network1Org1.getContract(chaincodeName);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// first create a listener to be notified of chaincode code events
|
||||||
|
// coming from the chaincode ID "events"
|
||||||
|
listener = async (event) => {
|
||||||
|
// The payload of the chaincode event is the value place there by the
|
||||||
|
// chaincode. Notice it is a byte data and the application will have
|
||||||
|
// to know how to deserialize.
|
||||||
|
// In this case we know that the chaincode will always place the asset
|
||||||
|
// being worked with as the payload for all events produced.
|
||||||
|
const asset = JSON.parse(event.payload.toString());
|
||||||
|
console.log(`${GREEN}<-- Contract Event Received: ${event.eventName} - ${JSON.stringify(asset)}${RESET}`);
|
||||||
|
// show the information available with the event
|
||||||
|
console.log(`*** Event: ${event.eventName}:${asset.ID}`);
|
||||||
|
// notice how we have access to the transaction information that produced this chaincode event
|
||||||
|
const eventTransaction = event.getTransactionEvent();
|
||||||
|
console.log(`*** transaction: ${eventTransaction.transactionId} status:${eventTransaction.status}`);
|
||||||
|
showTransactionData(eventTransaction.transactionData);
|
||||||
|
// notice how we have access to the full block that contains this transaction
|
||||||
|
const eventBlock = eventTransaction.getBlockEvent();
|
||||||
|
console.log(`*** block: ${eventBlock.blockNumber.toString()}`);
|
||||||
|
};
|
||||||
|
// now start the client side event service and register the listener
|
||||||
|
console.log(`${GREEN}--> Start contract event stream to peer in Org1${RESET}`);
|
||||||
|
await contract1Org1.addContractListener(listener);
|
||||||
|
} catch (eventError) {
|
||||||
|
console.log(`${RED}<-- Failed: Setup contract events - ${eventError}${RESET}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// C R E A T E
|
||||||
|
console.log(`${GREEN}--> Submit Transaction: CreateAsset, ${assetKey} owned by Sam${RESET}`);
|
||||||
|
transaction = contract1Org1.createTransaction('CreateAsset');
|
||||||
|
await transaction.submit(assetKey, 'blue', '10', 'Sam', '100');
|
||||||
|
console.log(`${GREEN}<-- Submit CreateAsset Result: committed, asset ${assetKey}${RESET}`);
|
||||||
|
} catch (createError) {
|
||||||
|
console.log(`${RED}<-- Submit Failed: CreateAsset - ${createError}${RESET}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// R E A D
|
||||||
|
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should be owned by Sam${RESET}`);
|
||||||
|
const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey);
|
||||||
|
checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '100');
|
||||||
|
} catch (readError) {
|
||||||
|
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// U P D A T E
|
||||||
|
console.log(`${GREEN}--> Submit Transaction: UpdateAsset ${assetKey} update appraised value to 200`);
|
||||||
|
transaction = contract1Org1.createTransaction('UpdateAsset');
|
||||||
|
await transaction.submit(assetKey, 'blue', '10', 'Sam', '200');
|
||||||
|
console.log(`${GREEN}<-- Submit UpdateAsset Result: committed, asset ${assetKey}${RESET}`);
|
||||||
|
} catch (updateError) {
|
||||||
|
console.log(`${RED}<-- Failed: UpdateAsset - ${updateError}${RESET}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// R E A D
|
||||||
|
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now have appraised value of 200${RESET}`);
|
||||||
|
const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey);
|
||||||
|
checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '200');
|
||||||
|
} catch (readError) {
|
||||||
|
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// T R A N S F E R
|
||||||
|
console.log(`${GREEN}--> Submit Transaction: TransferAsset ${assetKey} to Mary`);
|
||||||
|
transaction = contract1Org1.createTransaction('TransferAsset');
|
||||||
|
await transaction.submit(assetKey, 'Mary');
|
||||||
|
console.log(`${GREEN}<-- Submit TransferAsset Result: committed, asset ${assetKey}${RESET}`);
|
||||||
|
} catch (transferError) {
|
||||||
|
console.log(`${RED}<-- Failed: TransferAsset - ${transferError}${RESET}`);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// R E A D
|
||||||
|
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be owned by Mary${RESET}`);
|
||||||
|
const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey);
|
||||||
|
checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200');
|
||||||
|
} catch (readError) {
|
||||||
|
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// D E L E T E
|
||||||
|
console.log(`${GREEN}--> Submit Transaction: DeleteAsset ${assetKey}`);
|
||||||
|
transaction = contract1Org1.createTransaction('DeleteAsset');
|
||||||
|
await transaction.submit(assetKey);
|
||||||
|
console.log(`${GREEN}<-- Submit DeleteAsset Result: committed, asset ${assetKey}${RESET}`);
|
||||||
|
} catch (deleteError) {
|
||||||
|
console.log(`${RED}<-- Failed: DeleteAsset - ${deleteError}${RESET}`);
|
||||||
|
if (deleteError.toString().includes('ENDORSEMENT_POLICY_FAILURE')) {
|
||||||
|
console.log(`${RED}Be sure that chaincode was deployed with the endorsement policy "OR('Org1MSP.peer','Org2MSP.peer')"${RESET}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
// R E A D
|
||||||
|
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be deleted${RESET}`);
|
||||||
|
const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey);
|
||||||
|
checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200');
|
||||||
|
console.log(`${RED}<-- Failed: ReadAsset - should not have read this asset${RESET}`);
|
||||||
|
} catch (readError) {
|
||||||
|
console.log(`${GREEN}<-- Success: ReadAsset - ${readError}${RESET}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// all done with this listener
|
||||||
|
contract1Org1.removeContractListener(listener);
|
||||||
|
|
||||||
|
//
|
||||||
|
// - - - - - - B L O C K E V E N T S with P R I V A T E D A T A
|
||||||
|
//
|
||||||
|
console.log(`${BLUE} **** BLOCK EVENTS with PRIVATE DATA ****${RESET}`);
|
||||||
|
const network2Org1 = await gateway2Org1.getNetwork(channelName);
|
||||||
|
const contract2Org1 = network2Org1.getContract(chaincodeName);
|
||||||
|
|
||||||
|
randomNumber = Math.floor(Math.random() * 1000) + 1;
|
||||||
|
assetKey = `item-${randomNumber}`;
|
||||||
|
|
||||||
|
let firstBlock = true; // simple indicator to track blocks
|
||||||
|
|
||||||
|
try {
|
||||||
|
let listener;
|
||||||
|
|
||||||
|
// create a block listener
|
||||||
|
listener = async (event) => {
|
||||||
|
if (firstBlock) {
|
||||||
|
console.log(`${GREEN}<-- Block Event Received - block number: ${event.blockNumber.toString()}` +
|
||||||
|
'\n### Note:' +
|
||||||
|
'\n This block event represents the current top block of the ledger.' +
|
||||||
|
`\n All block events after this one are events that represent new blocks added to the ledger${RESET}`);
|
||||||
|
firstBlock = false;
|
||||||
|
} else {
|
||||||
|
console.log(`${GREEN}<-- Block Event Received - block number: ${event.blockNumber.toString()}${RESET}`);
|
||||||
|
}
|
||||||
|
const transEvents = event.getTransactionEvents();
|
||||||
|
for (const transEvent of transEvents) {
|
||||||
|
console.log(`*** transaction event: ${transEvent.transactionId}`);
|
||||||
|
if (transEvent.privateData) {
|
||||||
|
for (const namespace of transEvent.privateData.ns_pvt_rwset) {
|
||||||
|
console.log(` - private data: ${namespace.namespace}`);
|
||||||
|
for (const collection of namespace.collection_pvt_rwset) {
|
||||||
|
console.log(` - collection: ${collection.collection_name}`);
|
||||||
|
if (collection.rwset.reads) {
|
||||||
|
for (const read of collection.rwset.reads) {
|
||||||
|
console.log(` - read set - ${BLUE}key:${RESET} ${read.key} ${BLUE}value:${read.value.toString()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (collection.rwset.writes) {
|
||||||
|
for (const write of collection.rwset.writes) {
|
||||||
|
console.log(` - write set - ${BLUE}key:${RESET}${write.key} ${BLUE}is_delete:${RESET}${write.is_delete} ${BLUE}value:${RESET}${write.value.toString()}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (transEvent.transactionData) {
|
||||||
|
showTransactionData(transEvent.transactionData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// now start the client side event service and register the listener
|
||||||
|
console.log(`${GREEN}--> Start private data block event stream to peer in Org1${RESET}`);
|
||||||
|
await network2Org1.addBlockListener(listener, {type: 'private'});
|
||||||
|
} catch (eventError) {
|
||||||
|
console.log(`${RED}<-- Failed: Setup block events - ${eventError}${RESET}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// C R E A T E
|
||||||
|
console.log(`${GREEN}--> Submit Transaction: CreateAsset, ${assetKey} owned by Sam${RESET}`);
|
||||||
|
transaction = contract2Org1.createTransaction('CreateAsset');
|
||||||
|
|
||||||
|
// create the private data with salt and assign to the transaction
|
||||||
|
const randomNumber = Math.floor(Math.random() * 100) + 1;
|
||||||
|
const asset_properties = {
|
||||||
|
object_type: 'asset_properties',
|
||||||
|
asset_id: assetKey,
|
||||||
|
Price: '90',
|
||||||
|
salt: Buffer.from(randomNumber.toString()).toString('hex')
|
||||||
|
};
|
||||||
|
const asset_properties_string = JSON.stringify(asset_properties);
|
||||||
|
transaction.setTransient({
|
||||||
|
asset_properties: Buffer.from(asset_properties_string)
|
||||||
|
});
|
||||||
|
// With the addition of private data to the transaction
|
||||||
|
// We must only send this to the organization that will be
|
||||||
|
// saving the private data or we will get an endorsement policy failure
|
||||||
|
transaction.setEndorsingOrganizations(org1);
|
||||||
|
// endorse and commit - private data (transient data) will be
|
||||||
|
// saved to the implicit collection on the peer
|
||||||
|
await transaction.submit(assetKey, 'blue', '10', 'Sam', '100');
|
||||||
|
console.log(`${GREEN}<-- Submit CreateAsset Result: committed, asset ${assetKey}${RESET}`);
|
||||||
|
} catch (createError) {
|
||||||
|
console.log(`${RED}<-- Failed: CreateAsset - ${createError}${RESET}`);
|
||||||
|
}
|
||||||
|
await sleep(5000); // need to wait for event to be committed
|
||||||
|
try {
|
||||||
|
// R E A D
|
||||||
|
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should be owned by Sam${RESET}`);
|
||||||
|
const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey);
|
||||||
|
checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '100', '90');
|
||||||
|
} catch (readError) {
|
||||||
|
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// U P D A T E
|
||||||
|
console.log(`${GREEN}--> Submit Transaction: UpdateAsset ${assetKey} update appraised value to 200`);
|
||||||
|
transaction = contract2Org1.createTransaction('UpdateAsset');
|
||||||
|
|
||||||
|
// update the private data with new salt and assign to the transaction
|
||||||
|
const randomNumber = Math.floor(Math.random() * 100) + 1;
|
||||||
|
const asset_properties = {
|
||||||
|
object_type: 'asset_properties',
|
||||||
|
asset_id: assetKey,
|
||||||
|
Price: '90',
|
||||||
|
salt: Buffer.from(randomNumber.toString()).toString('hex')
|
||||||
|
};
|
||||||
|
const asset_properties_string = JSON.stringify(asset_properties);
|
||||||
|
transaction.setTransient({
|
||||||
|
asset_properties: Buffer.from(asset_properties_string)
|
||||||
|
});
|
||||||
|
transaction.setEndorsingOrganizations(org1);
|
||||||
|
|
||||||
|
await transaction.submit(assetKey, 'blue', '10', 'Sam', '200');
|
||||||
|
console.log(`${GREEN}<-- Submit UpdateAsset Result: committed, asset ${assetKey}${RESET}`);
|
||||||
|
} catch (updateError) {
|
||||||
|
console.log(`${RED}<-- Failed: UpdateAsset - ${updateError}${RESET}`);
|
||||||
|
}
|
||||||
|
await sleep(5000); // need to wait for event to be committed
|
||||||
|
try {
|
||||||
|
// R E A D
|
||||||
|
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now have appraised value of 200${RESET}`);
|
||||||
|
const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey);
|
||||||
|
checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '200', '90');
|
||||||
|
} catch (readError) {
|
||||||
|
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// T R A N S F E R
|
||||||
|
console.log(`${GREEN}--> Submit Transaction: TransferAsset ${assetKey} to Mary`);
|
||||||
|
transaction = contract2Org1.createTransaction('TransferAsset');
|
||||||
|
|
||||||
|
// update the private data with new salt and assign to the transaction
|
||||||
|
const randomNumber = Math.floor(Math.random() * 100) + 1;
|
||||||
|
const asset_properties = {
|
||||||
|
object_type: 'asset_properties',
|
||||||
|
asset_id: assetKey,
|
||||||
|
Price: '180',
|
||||||
|
salt: Buffer.from(randomNumber.toString()).toString('hex')
|
||||||
|
};
|
||||||
|
const asset_properties_string = JSON.stringify(asset_properties);
|
||||||
|
transaction.setTransient({
|
||||||
|
asset_properties: Buffer.from(asset_properties_string)
|
||||||
|
});
|
||||||
|
transaction.setEndorsingOrganizations(org1);
|
||||||
|
|
||||||
|
await transaction.submit(assetKey, 'Mary');
|
||||||
|
console.log(`${GREEN}<-- Submit TransferAsset Result: committed, asset ${assetKey}${RESET}`);
|
||||||
|
} catch (transferError) {
|
||||||
|
console.log(`${RED}<-- Failed: TransferAsset - ${transferError}${RESET}`);
|
||||||
|
}
|
||||||
|
await sleep(5000); // need to wait for event to be committed
|
||||||
|
try {
|
||||||
|
// R E A D
|
||||||
|
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be owned by Mary${RESET}`);
|
||||||
|
const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey);
|
||||||
|
checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200', '180');
|
||||||
|
} catch (readError) {
|
||||||
|
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// D E L E T E
|
||||||
|
console.log(`${GREEN}--> Submit Transaction: DeleteAsset ${assetKey}`);
|
||||||
|
transaction = contract2Org1.createTransaction('DeleteAsset');
|
||||||
|
await transaction.submit(assetKey);
|
||||||
|
console.log(`${GREEN}<-- Submit DeleteAsset Result: committed, asset ${assetKey}${RESET}`);
|
||||||
|
} catch (deleteError) {
|
||||||
|
console.log(`${RED}<-- Failed: DeleteAsset - ${deleteError}${RESET}`);
|
||||||
|
}
|
||||||
|
await sleep(5000); // need to wait for event to be committed
|
||||||
|
try {
|
||||||
|
// R E A D
|
||||||
|
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be deleted${RESET}`);
|
||||||
|
const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey);
|
||||||
|
checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200');
|
||||||
|
console.log(`${RED}<-- Failed: ReadAsset - should not have read this asset${RESET}`);
|
||||||
|
} catch (readError) {
|
||||||
|
console.log(`${GREEN}<-- Success: ReadAsset - ${readError}${RESET}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// all done with this listener
|
||||||
|
network2Org1.removeBlockListener(listener);
|
||||||
|
|
||||||
|
} catch (runError) {
|
||||||
|
console.error(`Error in transaction: ${runError}`);
|
||||||
|
if (runError.stack) {
|
||||||
|
console.error(runError.stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Error in setup: ${error}`);
|
||||||
|
if (error.stack) {
|
||||||
|
console.error(error.stack);
|
||||||
|
}
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
await sleep(5000);
|
||||||
|
console.log(`${BLUE} **** END ****${RESET}`);
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
main();
|
||||||
16
asset-transfer-events/application-javascript/package.json
Normal file
16
asset-transfer-events/application-javascript/package.json
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "asset-transfer-events",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Javascript application that uses chaincode events and block events with private data",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"npm": ">=5"
|
||||||
|
},
|
||||||
|
"engineStrict": true,
|
||||||
|
"author": "Hyperledger",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"fabric-ca-client": "^2.2.2",
|
||||||
|
"fabric-network": "^2.2.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
5
asset-transfer-events/chaincode-javascript/.eslintignore
Normal file
5
asset-transfer-events/chaincode-javascript/.eslintignore
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
coverage
|
||||||
39
asset-transfer-events/chaincode-javascript/.eslintrc.js
Normal file
39
asset-transfer-events/chaincode-javascript/.eslintrc.js
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
env: {
|
||||||
|
node: true,
|
||||||
|
mocha: true,
|
||||||
|
es6: true
|
||||||
|
},
|
||||||
|
parserOptions: {
|
||||||
|
ecmaVersion: 8,
|
||||||
|
sourceType: 'script'
|
||||||
|
},
|
||||||
|
extends: 'eslint:recommended',
|
||||||
|
rules: {
|
||||||
|
indent: ['error', 'tab'],
|
||||||
|
'linebreak-style': ['error', 'unix'],
|
||||||
|
quotes: ['error', 'single'],
|
||||||
|
semi: ['error', 'always'],
|
||||||
|
'no-unused-vars': ['error', { args: 'none' }],
|
||||||
|
'no-console': 'off',
|
||||||
|
curly: 'error',
|
||||||
|
eqeqeq: 'error',
|
||||||
|
'no-throw-literal': 'error',
|
||||||
|
strict: 'error',
|
||||||
|
'no-var': 'error',
|
||||||
|
'dot-notation': 'error',
|
||||||
|
'no-trailing-spaces': 'error',
|
||||||
|
'no-use-before-define': 'error',
|
||||||
|
'no-useless-call': 'error',
|
||||||
|
'no-with': 'error',
|
||||||
|
'operator-linebreak': 'error',
|
||||||
|
yoda: 'error',
|
||||||
|
'quote-props': ['error', 'as-needed'],
|
||||||
|
'no-constant-condition': ['error', { checkLoops: false }]
|
||||||
|
}
|
||||||
|
};
|
||||||
15
asset-transfer-events/chaincode-javascript/.gitignore
vendored
Normal file
15
asset-transfer-events/chaincode-javascript/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# Report cache used by istanbul
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
package-lock.json
|
||||||
12
asset-transfer-events/chaincode-javascript/index.js
Normal file
12
asset-transfer-events/chaincode-javascript/index.js
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
/*
|
||||||
|
* Copyright IBM Corp. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const assetTransferEvents = require('./lib/assetTransferEvents');
|
||||||
|
|
||||||
|
module.exports.AssetTransferEvents = assetTransferEvents;
|
||||||
|
module.exports.contracts = [assetTransferEvents];
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
/*
|
||||||
|
* Copyright IBM Corp. All Rights Reserved.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
const { Contract } = require('fabric-contract-api');
|
||||||
|
|
||||||
|
async function savePrivateData(ctx, assetKey) {
|
||||||
|
const clientOrg = ctx.clientIdentity.getMSPID();
|
||||||
|
const peerOrg = ctx.stub.getMspID();
|
||||||
|
const collection = '_implicit_org_' + peerOrg;
|
||||||
|
|
||||||
|
if (clientOrg === peerOrg) {
|
||||||
|
const transientMap = ctx.stub.getTransient();
|
||||||
|
if (transientMap) {
|
||||||
|
const properties = transientMap.get('asset_properties');
|
||||||
|
if (properties) {
|
||||||
|
await ctx.stub.putPrivateData(collection, assetKey, properties);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function removePrivateData(ctx, assetKey) {
|
||||||
|
const clientOrg = ctx.clientIdentity.getMSPID();
|
||||||
|
const peerOrg = ctx.stub.getMspID();
|
||||||
|
const collection = '_implicit_org_' + peerOrg;
|
||||||
|
|
||||||
|
if (clientOrg === peerOrg) {
|
||||||
|
const propertiesBuffer = await ctx.stub.getPrivateData(collection, assetKey);
|
||||||
|
if (propertiesBuffer && propertiesBuffer.length > 0) {
|
||||||
|
await ctx.stub.deletePrivateData(collection, assetKey);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function addPrivateData(ctx, assetKey, asset) {
|
||||||
|
const clientOrg = ctx.clientIdentity.getMSPID();
|
||||||
|
const peerOrg = ctx.stub.getMspID();
|
||||||
|
const collection = '_implicit_org_' + peerOrg;
|
||||||
|
|
||||||
|
if (clientOrg === peerOrg) {
|
||||||
|
const propertiesBuffer = await ctx.stub.getPrivateData(collection, assetKey);
|
||||||
|
if (propertiesBuffer && propertiesBuffer.length > 0) {
|
||||||
|
const properties = JSON.parse(propertiesBuffer.toString());
|
||||||
|
asset.asset_properties = properties;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function readState(ctx, id) {
|
||||||
|
const assetBuffer = await ctx.stub.getState(id); // get the asset from chaincode state
|
||||||
|
if (!assetBuffer || assetBuffer.length === 0) {
|
||||||
|
throw new Error(`The asset ${id} does not exist`);
|
||||||
|
}
|
||||||
|
const assetString = assetBuffer.toString();
|
||||||
|
const asset = JSON.parse(assetString);
|
||||||
|
|
||||||
|
return asset;
|
||||||
|
}
|
||||||
|
|
||||||
|
class AssetTransferEvents extends Contract {
|
||||||
|
|
||||||
|
// CreateAsset issues a new asset to the world state with given details.
|
||||||
|
async CreateAsset(ctx, id, color, size, owner, appraisedValue) {
|
||||||
|
const asset = {
|
||||||
|
ID: id,
|
||||||
|
Color: color,
|
||||||
|
Size: size,
|
||||||
|
Owner: owner,
|
||||||
|
AppraisedValue: appraisedValue,
|
||||||
|
};
|
||||||
|
await savePrivateData(ctx, id);
|
||||||
|
const assetBuffer = Buffer.from(JSON.stringify(asset));
|
||||||
|
|
||||||
|
ctx.stub.setEvent('CreateAsset', assetBuffer);
|
||||||
|
return ctx.stub.putState(id, assetBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TransferAsset updates the owner field of an asset with the given id in
|
||||||
|
// the world state.
|
||||||
|
async TransferAsset(ctx, id, newOwner) {
|
||||||
|
const asset = await readState(ctx, id);
|
||||||
|
asset.Owner = newOwner;
|
||||||
|
const assetBuffer = Buffer.from(JSON.stringify(asset));
|
||||||
|
await savePrivateData(ctx, id);
|
||||||
|
|
||||||
|
ctx.stub.setEvent('TransferAsset', assetBuffer);
|
||||||
|
return ctx.stub.putState(id, assetBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadAsset returns the asset stored in the world state with given id.
|
||||||
|
async ReadAsset(ctx, id) {
|
||||||
|
const asset = await readState(ctx, id);
|
||||||
|
await addPrivateData(ctx, asset.ID, asset);
|
||||||
|
|
||||||
|
return JSON.stringify(asset);
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateAsset updates an existing asset in the world state with provided parameters.
|
||||||
|
async UpdateAsset(ctx, id, color, size, owner, appraisedValue) {
|
||||||
|
const asset = await readState(ctx, id);
|
||||||
|
asset.Color = color;
|
||||||
|
asset.Size = size;
|
||||||
|
asset.Owner = owner;
|
||||||
|
asset.AppraisedValue = appraisedValue;
|
||||||
|
const assetBuffer = Buffer.from(JSON.stringify(asset));
|
||||||
|
await savePrivateData(ctx, id);
|
||||||
|
|
||||||
|
ctx.stub.setEvent('UpdateAsset', assetBuffer);
|
||||||
|
return ctx.stub.putState(id, assetBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteAsset deletes an given asset from the world state.
|
||||||
|
async DeleteAsset(ctx, id) {
|
||||||
|
const asset = await readState(ctx, id);
|
||||||
|
const assetBuffer = Buffer.from(JSON.stringify(asset));
|
||||||
|
await removePrivateData(ctx, id);
|
||||||
|
|
||||||
|
ctx.stub.setEvent('DeleteAsset', assetBuffer);
|
||||||
|
return ctx.stub.deleteState(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = AssetTransferEvents;
|
||||||
49
asset-transfer-events/chaincode-javascript/package.json
Normal file
49
asset-transfer-events/chaincode-javascript/package.json
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
{
|
||||||
|
"name": "asset-transfer-events",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Asset-Transfer-Events contract implemented in JavaScript",
|
||||||
|
"main": "index.js",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"npm": ">=5"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"lint": "eslint .",
|
||||||
|
"pretest": "npm run lint",
|
||||||
|
"test": "nyc mocha --recursive",
|
||||||
|
"start": "fabric-chaincode-node start"
|
||||||
|
},
|
||||||
|
"engineStrict": true,
|
||||||
|
"author": "Hyperledger",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"fabric-contract-api": "^2.0.0",
|
||||||
|
"fabric-shim": "^2.0.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"chai": "^4.1.2",
|
||||||
|
"eslint": "^4.19.1",
|
||||||
|
"mocha": "^8.0.1",
|
||||||
|
"nyc": "^14.1.1",
|
||||||
|
"sinon": "^6.0.0",
|
||||||
|
"sinon-chai": "^3.2.0"
|
||||||
|
},
|
||||||
|
"nyc": {
|
||||||
|
"exclude": [
|
||||||
|
"coverage/**",
|
||||||
|
"test/**",
|
||||||
|
"index.js",
|
||||||
|
".eslintrc.js"
|
||||||
|
],
|
||||||
|
"reporter": [
|
||||||
|
"text-summary",
|
||||||
|
"html"
|
||||||
|
],
|
||||||
|
"all": true,
|
||||||
|
"check-coverage": true,
|
||||||
|
"statements": 100,
|
||||||
|
"branches": 100,
|
||||||
|
"functions": 100,
|
||||||
|
"lines": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,224 @@
|
||||||
|
'use strict';
|
||||||
|
const sinon = require('sinon');
|
||||||
|
const chai = require('chai');
|
||||||
|
const sinonChai = require('sinon-chai');
|
||||||
|
const expect = chai.expect;
|
||||||
|
|
||||||
|
const { Context } = require('fabric-contract-api');
|
||||||
|
const { ChaincodeStub, ClientIdentity } = require('fabric-shim');
|
||||||
|
|
||||||
|
const AssetTransfer = require('../lib/assetTransferEvents.js');
|
||||||
|
|
||||||
|
let assert = sinon.assert;
|
||||||
|
chai.use(sinonChai);
|
||||||
|
|
||||||
|
describe('Asset Transfer Events Tests', () => {
|
||||||
|
let transactionContext, chaincodeStub, clientIdentity, asset;
|
||||||
|
let transientMap, asset_properties;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
transactionContext = new Context();
|
||||||
|
|
||||||
|
chaincodeStub = sinon.createStubInstance(ChaincodeStub);
|
||||||
|
chaincodeStub.getMspID.returns('org1');
|
||||||
|
transactionContext.setChaincodeStub(chaincodeStub);
|
||||||
|
|
||||||
|
clientIdentity = sinon.createStubInstance(ClientIdentity);
|
||||||
|
clientIdentity.getMSPID.returns('org1');
|
||||||
|
transactionContext.clientIdentity = clientIdentity;
|
||||||
|
|
||||||
|
chaincodeStub.putState.callsFake((key, value) => {
|
||||||
|
if (!chaincodeStub.states) {
|
||||||
|
chaincodeStub.states = {};
|
||||||
|
}
|
||||||
|
chaincodeStub.states[key] = value;
|
||||||
|
});
|
||||||
|
|
||||||
|
chaincodeStub.getState.callsFake(async (key) => {
|
||||||
|
let ret;
|
||||||
|
if (chaincodeStub.states) {
|
||||||
|
ret = chaincodeStub.states[key];
|
||||||
|
}
|
||||||
|
return Promise.resolve(ret);
|
||||||
|
});
|
||||||
|
|
||||||
|
chaincodeStub.deleteState.callsFake(async (key) => {
|
||||||
|
if (chaincodeStub.states) {
|
||||||
|
delete chaincodeStub.states[key];
|
||||||
|
}
|
||||||
|
return Promise.resolve(key);
|
||||||
|
});
|
||||||
|
|
||||||
|
chaincodeStub.getStateByRange.callsFake(async () => {
|
||||||
|
function* internalGetStateByRange() {
|
||||||
|
if (chaincodeStub.states) {
|
||||||
|
// Shallow copy
|
||||||
|
const copied = Object.assign({}, chaincodeStub.states);
|
||||||
|
|
||||||
|
for (let key in copied) {
|
||||||
|
yield {value: copied[key]};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve(internalGetStateByRange());
|
||||||
|
});
|
||||||
|
|
||||||
|
asset = {
|
||||||
|
ID: 'asset1',
|
||||||
|
Color: 'blue',
|
||||||
|
Size: 5,
|
||||||
|
Owner: 'Tomoko',
|
||||||
|
AppraisedValue: 300,
|
||||||
|
};
|
||||||
|
const randomNumber = Math.floor(Math.random() * 100) + 1;
|
||||||
|
asset_properties = {
|
||||||
|
object_type: 'asset_properties',
|
||||||
|
asset_id: 'asset1',
|
||||||
|
Price: '90',
|
||||||
|
salt: Buffer.from(randomNumber.toString()).toString('hex')
|
||||||
|
};
|
||||||
|
transientMap = {
|
||||||
|
asset_properties: Buffer.from(JSON.stringify(asset_properties))
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test CreateAsset', () => {
|
||||||
|
it('should return error on CreateAsset', async () => {
|
||||||
|
chaincodeStub.putState.rejects('failed inserting key');
|
||||||
|
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
try {
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
assert.fail('CreateAsset should have failed');
|
||||||
|
} catch(err) {
|
||||||
|
expect(err.name).to.equal('failed inserting key');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return success on CreateAsset', async () => {
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
|
||||||
|
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
|
||||||
|
expect(ret).to.eql(asset);
|
||||||
|
});
|
||||||
|
it('should return success on CreateAsset with transient data', async () => {
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
chaincodeStub.getTransient.returns(transientMap);
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
|
||||||
|
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
|
||||||
|
expect(ret).to.eql(asset);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test ReadAsset', () => {
|
||||||
|
it('should return error on ReadAsset', async () => {
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await assetTransfer.ReadAsset(transactionContext, 'asset2');
|
||||||
|
assert.fail('ReadAsset should have failed');
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).to.equal('The asset asset2 does not exist');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return success on ReadAsset', async () => {
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
const assetString = await assetTransfer.ReadAsset(transactionContext, 'asset1');
|
||||||
|
const readAsset = JSON.parse(assetString);
|
||||||
|
expect(readAsset).to.eql(asset);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return success on ReadAsset with private data', async () => {
|
||||||
|
asset.asset_properties = asset_properties;
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
chaincodeStub.getPrivateData.returns(Buffer.from(JSON.stringify(asset_properties)));
|
||||||
|
const assetString = await assetTransfer.ReadAsset(transactionContext, 'asset1');
|
||||||
|
const readAsset = JSON.parse(assetString);
|
||||||
|
expect(readAsset).to.eql(asset);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test UpdateAsset', () => {
|
||||||
|
it('should return error on UpdateAsset', async () => {
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await assetTransfer.UpdateAsset(transactionContext, 'asset2', 'orange', 10, 'Me', 500);
|
||||||
|
assert.fail('UpdateAsset should have failed');
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).to.equal('The asset asset2 does not exist');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return success on UpdateAsset', async () => {
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
|
||||||
|
await assetTransfer.UpdateAsset(transactionContext, 'asset1', 'orange', 10, 'Me', 500);
|
||||||
|
let ret = JSON.parse(await chaincodeStub.getState(asset.ID));
|
||||||
|
let expected = {
|
||||||
|
ID: 'asset1',
|
||||||
|
Color: 'orange',
|
||||||
|
Size: 10,
|
||||||
|
Owner: 'Me',
|
||||||
|
AppraisedValue: 500
|
||||||
|
};
|
||||||
|
expect(ret).to.eql(expected);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test DeleteAsset', () => {
|
||||||
|
it('should return error on DeleteAsset', async () => {
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await assetTransfer.DeleteAsset(transactionContext, 'asset2');
|
||||||
|
assert.fail('DeleteAsset should have failed');
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).to.equal('The asset asset2 does not exist');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return success on DeleteAsset', async () => {
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
|
||||||
|
await assetTransfer.DeleteAsset(transactionContext, asset.ID);
|
||||||
|
let ret = await chaincodeStub.getState(asset.ID);
|
||||||
|
expect(ret).to.equal(undefined);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Test TransferAsset', () => {
|
||||||
|
it('should return error on TransferAsset', async () => {
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await assetTransfer.TransferAsset(transactionContext, 'asset2', 'Me');
|
||||||
|
assert.fail('DeleteAsset should have failed');
|
||||||
|
} catch (err) {
|
||||||
|
expect(err.message).to.equal('The asset asset2 does not exist');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return success on TransferAsset', async () => {
|
||||||
|
let assetTransfer = new AssetTransfer();
|
||||||
|
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
|
||||||
|
|
||||||
|
await assetTransfer.TransferAsset(transactionContext, asset.ID, 'Me');
|
||||||
|
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
|
||||||
|
expect(ret).to.eql(Object.assign({}, asset, {Owner: 'Me'}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
@ -238,3 +238,18 @@ jobs:
|
||||||
- script: ../ci/scripts/run-test-network-secured.sh
|
- script: ../ci/scripts/run-test-network-secured.sh
|
||||||
workingDirectory: test-network
|
workingDirectory: test-network
|
||||||
displayName: Run Test Network Secured Chaincode
|
displayName: Run Test Network Secured Chaincode
|
||||||
|
|
||||||
|
- job: TestNetworkEvents
|
||||||
|
displayName: Test Network
|
||||||
|
pool:
|
||||||
|
vmImage: ubuntu-18.04
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
Events-Javascript:
|
||||||
|
CHAINCODE_NAME: events
|
||||||
|
CHAINCODE_LANGUAGE: javascript
|
||||||
|
steps:
|
||||||
|
- template: templates/install-deps.yml
|
||||||
|
- script: ../ci/scripts/run-test-network-events.sh
|
||||||
|
workingDirectory: test-network
|
||||||
|
displayName: Run Test Network Events Chaincode
|
||||||
|
|
|
||||||
36
ci/scripts/run-test-network-events.sh
Executable file
36
ci/scripts/run-test-network-events.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
FABRIC_VERSION=${FABRIC_VERSION:-2.2}
|
||||||
|
CHAINCODE_LANGUAGE=${CHAINCODE_LANGUAGE:-javascript}
|
||||||
|
CHAINCODE_NAME=${CHAINCODE_NAME:-events}
|
||||||
|
|
||||||
|
function print() {
|
||||||
|
GREEN='\033[0;32m'
|
||||||
|
NC='\033[0m'
|
||||||
|
echo
|
||||||
|
echo -e "${GREEN}${1}${NC}"
|
||||||
|
}
|
||||||
|
|
||||||
|
function createNetwork() {
|
||||||
|
print "Creating network"
|
||||||
|
./network.sh up createChannel -ca
|
||||||
|
print "Deploying ${CHAINCODE_NAME} chaincode"
|
||||||
|
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccl "${CHAINCODE_LANGUAGE}" -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopNetwork() {
|
||||||
|
print "Stopping network"
|
||||||
|
./network.sh down
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run Javascript application
|
||||||
|
createNetwork
|
||||||
|
print "Initializing Javascript application"
|
||||||
|
pushd ../asset-transfer-events/application-javascript
|
||||||
|
npm install
|
||||||
|
print "Executing app.js"
|
||||||
|
node app.js
|
||||||
|
popd
|
||||||
|
stopNetwork
|
||||||
|
print "Remove wallet storage"
|
||||||
|
rm -R ../asset-transfer-events/application-javascript/wallet
|
||||||
|
|
@ -42,6 +42,9 @@ if [ "$CC_SRC_PATH" = "NA" ]; then
|
||||||
if [ "$CC_NAME" = "basic" ]; then
|
if [ "$CC_NAME" = "basic" ]; then
|
||||||
println $'\e[0;32m'asset-transfer-basic$'\e[0m' chaincode
|
println $'\e[0;32m'asset-transfer-basic$'\e[0m' chaincode
|
||||||
CC_SRC_PATH="../asset-transfer-basic"
|
CC_SRC_PATH="../asset-transfer-basic"
|
||||||
|
elif [ "$CC_NAME" = "events" ]; then
|
||||||
|
println $'\e[0;32m'asset-transfer-events$'\e[0m' chaincode
|
||||||
|
CC_SRC_PATH="../asset-transfer-events"
|
||||||
elif [ "$CC_NAME" = "secured" ]; then
|
elif [ "$CC_NAME" = "secured" ]; then
|
||||||
println $'\e[0;32m'asset-transfer-secured-agreeement$'\e[0m' chaincode
|
println $'\e[0;32m'asset-transfer-secured-agreeement$'\e[0m' chaincode
|
||||||
CC_SRC_PATH="../asset-transfer-secured-agreement"
|
CC_SRC_PATH="../asset-transfer-secured-agreement"
|
||||||
|
|
@ -55,7 +58,7 @@ if [ "$CC_SRC_PATH" = "NA" ]; then
|
||||||
println $'\e[0;32m'asset-transfer-sbe$'\e[0m' chaincode
|
println $'\e[0;32m'asset-transfer-sbe$'\e[0m' chaincode
|
||||||
CC_SRC_PATH="../asset-transfer-sbe"
|
CC_SRC_PATH="../asset-transfer-sbe"
|
||||||
else
|
else
|
||||||
fatalln "The chaincode name ${CC_NAME} is not supported by this script. Supported chaincode names are: basic, ledger, private, sbe, secured"
|
fatalln "The chaincode name ${CC_NAME} is not supported by this script. Supported chaincode names are: basic, events, ledger, private, sbe, secured"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# now see what language it is written in
|
# now see what language it is written in
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue