mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
Add the javascript application for ledger queries
Add the asset-transfer-ledger-queries javascript application. Update the CI script to run it against go and javascript chaincode. Signed-off-by: Bret Harrison <beharrison@nc.rr.com>
This commit is contained in:
parent
8e2535ee65
commit
f361386231
12 changed files with 422 additions and 68 deletions
|
|
@ -9,7 +9,7 @@
|
||||||
const {Gateway, Wallets} = require('fabric-network');
|
const {Gateway, Wallets} = require('fabric-network');
|
||||||
const FabricCAServices = require('fabric-ca-client');
|
const FabricCAServices = require('fabric-ca-client');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const {buildCAClient, registerUser, enrollAdmin} = require('../../test-application/javascript/CAUtil.js');
|
const {buildCAClient, registerAndEnrollUser, enrollAdmin} = require('../../test-application/javascript/CAUtil.js');
|
||||||
const {buildCCP, buildWallet} = require('../../test-application/javascript/AppUtil.js');
|
const {buildCCP, buildWallet} = require('../../test-application/javascript/AppUtil.js');
|
||||||
|
|
||||||
const channelName = 'mychannel';
|
const channelName = 'mychannel';
|
||||||
|
|
@ -22,25 +22,46 @@ function prettyJSONString(inputString) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// pre-requisites:
|
// pre-requisites:
|
||||||
// - fabric-sample two organization test-network setup with two peers, ordering service, and 2 certificate authorities
|
// - fabric-sample two organization test-network setup with two peers, ordering service,
|
||||||
|
// and 2 certificate authorities
|
||||||
// ===> from directory /fabric-samples/test-network
|
// ===> from directory /fabric-samples/test-network
|
||||||
// network.sh run createChannel -ca
|
// ./network.sh up createChannel -ca
|
||||||
// - any of the asset-transfer-basic chaincodes deployed on the channel "mychannel" with the chaincodeName of "basic"
|
// - Use any of the asset-transfer-basic chaincodes deployed on the channel "mychannel"
|
||||||
// This deploy command will package, install, approve, and commit the javascript chaincode, all the actions it takes
|
// with the chaincode name of "basic". 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.
|
// to deploy a chaincode to a channel.
|
||||||
// ===> from directory /fabric-samples/test-network
|
// ===> from directory /fabric-samples/test-network
|
||||||
// network.sh deployCC -ccn basic -ccl javascript
|
// ./network.sh deployCC -ccn basic -ccl javascript
|
||||||
// - node install
|
// - Be sure that node.js is installed
|
||||||
|
// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript
|
||||||
|
// node -v
|
||||||
// - npm installed code dependencies
|
// - npm installed code dependencies
|
||||||
// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript
|
// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript
|
||||||
// npm install
|
// npm install
|
||||||
// - to run this test application
|
// - to run this test application
|
||||||
// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript
|
// ===> from directory /fabric-samples/asset-transfer-basic/application-javascript
|
||||||
// node app.js
|
// node app.js
|
||||||
// # this may be run again again
|
|
||||||
|
// NOTE: If you see kind an error like these:
|
||||||
|
/*
|
||||||
|
2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied
|
||||||
|
******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]]
|
||||||
|
******** FAILED to run the application: Error: Identity not found in wallet: appUser
|
||||||
|
*/
|
||||||
|
// Delete the /fabric-samples/asset-transfer-basic/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.
|
||||||
|
//
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A test application to show basic operations with any of the asset-transfer-basic chaincodes
|
* A test application to show basic queries operations with any of the asset-transfer-basic chaincodes
|
||||||
* -- How to submit a transaction
|
* -- How to submit a transaction
|
||||||
* -- How to query and check the results
|
* -- How to query and check the results
|
||||||
*
|
*
|
||||||
|
|
@ -64,7 +85,7 @@ async function main() {
|
||||||
|
|
||||||
// in a real application this would be done only when a new user was required to be added
|
// 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
|
// and would be part of an administrative flow
|
||||||
await registerUser(caClient, wallet, userId, 'org1.department1');
|
await registerAndEnrollUser(caClient, wallet, userId, 'org1.department1');
|
||||||
|
|
||||||
// Create a new gateway instance for interacting with the fabric network.
|
// Create a new gateway instance for interacting with the fabric network.
|
||||||
// In a real application this would be done as the backend server session is setup for
|
// In a real application this would be done as the backend server session is setup for
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
"author": "Hyperledger",
|
"author": "Hyperledger",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fabric-ca-client": "2.2.0",
|
"fabric-ca-client": "^2.2.0",
|
||||||
"fabric-network": "2.2.0"
|
"fabric-network": "^2.2.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
#
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
#
|
||||||
|
|
||||||
|
coverage
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
/*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
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',
|
||||||
|
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']
|
||||||
|
}
|
||||||
|
};
|
||||||
14
asset-transfer-ledger-queries/application-javascript/.gitignore
vendored
Normal file
14
asset-transfer-ledger-queries/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
|
||||||
242
asset-transfer-ledger-queries/application-javascript/app.js
Normal file
242
asset-transfer-ledger-queries/application-javascript/app.js
Normal file
|
|
@ -0,0 +1,242 @@
|
||||||
|
/*
|
||||||
|
* 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 {buildCCP, buildWallet} = require('../../test-application/javascript/AppUtil.js');
|
||||||
|
|
||||||
|
const channelName = 'mychannel';
|
||||||
|
const chaincodeName = 'ledger';
|
||||||
|
const walletPath = path.join(__dirname, 'wallet');
|
||||||
|
const userId = 'appUser';
|
||||||
|
|
||||||
|
function prettyJSONString(inputString) {
|
||||||
|
return JSON.stringify(JSON.parse(inputString), null, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
// pre-requisites:
|
||||||
|
// - fabric-sample two organization test-network setup with two peers, ordering service,
|
||||||
|
// and 2 certificate authorities, with the state database using couchdb
|
||||||
|
// ===> from directory /fabric-samples/test-network
|
||||||
|
// ./network.sh up createChannel -ca -s couchdb
|
||||||
|
// - Use any of the asset-transfer-ledger-queries chaincodes deployed on the channel "mychannel"
|
||||||
|
// with the chaincode name of "ledger". 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 ledger -ccl javascript
|
||||||
|
// - Be sure that node.js is installed
|
||||||
|
// ===> from directory /fabric-samples/asset-transfer-ledger-queries/application-javascript
|
||||||
|
// node -v
|
||||||
|
// - npm installed code dependencies
|
||||||
|
// ===> from directory /fabric-samples/asset-transfer-ledger-queries/application-javascript
|
||||||
|
// npm install
|
||||||
|
// - to run this test application
|
||||||
|
// ===> from directory /fabric-samples/asset-transfer-ledger-queries/application-javascript
|
||||||
|
// node app.js
|
||||||
|
|
||||||
|
// NOTE: If you see kind an error like these:
|
||||||
|
/*
|
||||||
|
2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied
|
||||||
|
******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied
|
||||||
|
|
||||||
|
OR
|
||||||
|
|
||||||
|
Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]]
|
||||||
|
******** FAILED to run the application: Error: Identity not found in wallet: appUser
|
||||||
|
*/
|
||||||
|
// Delete the /fabric-samples/asset-transfer-ledger-queries/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.
|
||||||
|
//
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A test application to show ledger queries operations with any of the asset-transfer-ledger-queries chaincodes
|
||||||
|
* -- How to submit a transaction
|
||||||
|
* -- How to query and check the results
|
||||||
|
*
|
||||||
|
* To see the SDK workings, try setting the logging to show on the console before running
|
||||||
|
* export HFC_LOGGING='{"debug":"console"}'
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
let skipInit = false;
|
||||||
|
if (process.argv.length > 2) {
|
||||||
|
if (process.argv[2] === 'skipInit') {
|
||||||
|
skipInit = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
// build an in memory object with the network configuration (also known as a connection profile)
|
||||||
|
const ccp = buildCCP();
|
||||||
|
|
||||||
|
// build an instance of the fabric ca services client based on
|
||||||
|
// the information in the network configuration
|
||||||
|
const caClient = buildCAClient(FabricCAServices, ccp);
|
||||||
|
|
||||||
|
// setup the wallet to hold the credentials of the application user
|
||||||
|
const wallet = await buildWallet(Wallets, walletPath);
|
||||||
|
|
||||||
|
// in a real application this would be done on an administrative flow, and only once
|
||||||
|
await enrollAdmin(caClient, 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(caClient, wallet, userId, 'org1.department1');
|
||||||
|
|
||||||
|
// Create a new gateway instance for interacting with the fabric network.
|
||||||
|
// In a real application this would be done as the backend server session is setup for
|
||||||
|
// a user that has been verified.
|
||||||
|
const gateway = new Gateway();
|
||||||
|
|
||||||
|
try {
|
||||||
|
// setup the gateway instance
|
||||||
|
// The user will now be able to create connections to the fabric network and be able to
|
||||||
|
// submit transactions and query. All transactions submitted by this gateway will be
|
||||||
|
// signed by this user using the credentials stored in the wallet.
|
||||||
|
await gateway.connect(ccp, {
|
||||||
|
wallet,
|
||||||
|
identity: userId,
|
||||||
|
discovery: {enabled: true, asLocalhost: true} // using asLocalhost as this gateway is using a fabric network deployed locally
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build a network instance based on the channel where the smart contract is deployed
|
||||||
|
const network = await gateway.getNetwork(channelName);
|
||||||
|
|
||||||
|
// Get the contract from the network.
|
||||||
|
const contract = network.getContract(chaincodeName);
|
||||||
|
|
||||||
|
// Initialize a set of asset data on the channel using the chaincode 'InitLedger' function.
|
||||||
|
// This type of transaction would only be run once by an application the first time it was started after it
|
||||||
|
// deployed the first time. Any updates to the chaincode deployed later would likely not need to run
|
||||||
|
// an "init" type function.
|
||||||
|
if (!skipInit) {
|
||||||
|
try {
|
||||||
|
console.log('\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger');
|
||||||
|
await contract.submitTransaction('InitLedger');
|
||||||
|
console.log('*** Result: committed');
|
||||||
|
} catch(initError) {
|
||||||
|
// this is error is OK if we are rerunning this app without restarting
|
||||||
|
console.log(`******** initLedger failed :: ${initError}`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log('*** not executing "InitLedger');
|
||||||
|
}
|
||||||
|
|
||||||
|
let result;
|
||||||
|
|
||||||
|
// Let's try a query operation (function).
|
||||||
|
// This will be sent to just one peer and the results will be shown.
|
||||||
|
console.log('\n--> Evaluate Transaction: GetAssetsByRange, function returns assets in a specific range from asset1 to before asset6');
|
||||||
|
result = await contract.evaluateTransaction('GetAssetsByRange', 'asset1', 'asset6');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
console.log('\n--> Evaluate Transaction: GetAssetsByRange, function use an open start and open end range to return assest1 to asset6');
|
||||||
|
result = await contract.evaluateTransaction('GetAssetsByRange', '', '');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
console.log('\n--> Evaluate Transaction: GetAssetsByRange, function use an fixed start (asset3) and open end range to return assest3 to asset6');
|
||||||
|
result = await contract.evaluateTransaction('GetAssetsByRange', 'asset3', '');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
console.log('\n--> Evaluate Transaction: GetAssetsByRange, function use an open start and fixed end (asset3) range to return assest1 to asset2');
|
||||||
|
result = await contract.evaluateTransaction('GetAssetsByRange', '', 'asset3');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
// Now let's try to submit a transaction.
|
||||||
|
// This will be sent to both peers and if both peers endorse the transaction, the endorsed proposal will be sent
|
||||||
|
// to the orderer to be committed by each of the peer's to the channel ledger.
|
||||||
|
console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID(asset7), color(yellow), size(5), owner(Tom), and appraisedValue(1300) arguments');
|
||||||
|
await contract.submitTransaction('CreateAsset', 'asset7', 'yellow', '5', 'Tom', '1300');
|
||||||
|
console.log('*** Result: committed');
|
||||||
|
|
||||||
|
console.log('\n--> Evaluate Transaction: ReadAsset, function returns information about an asset with ID(asset7)');
|
||||||
|
result = await contract.evaluateTransaction('ReadAsset', 'asset7');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
console.log('\n--> Evaluate Transaction: AssetExists, function returns "true" if an asset with ID(asset7) exist');
|
||||||
|
result = await contract.evaluateTransaction('AssetExists', 'asset7');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
// Now let's try to submit a transaction that deletes an asset
|
||||||
|
// This will be sent to both peers and if both peers endorse the transaction, the endorsed proposal will be sent
|
||||||
|
// to the orderer to be committed by each of the peer's to the channel ledger.
|
||||||
|
console.log('\n--> Submit Transaction: DeleteAsset with ID(asset7)');
|
||||||
|
await contract.submitTransaction('DeleteAsset', 'asset7');
|
||||||
|
console.log('*** Result: committed');
|
||||||
|
|
||||||
|
console.log('\n--> Evaluate Transaction: AssetExists, function returns "false" if an asset with ID(asset7) does not exist');
|
||||||
|
result = await contract.evaluateTransaction('AssetExists', 'asset7');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`)
|
||||||
|
|
||||||
|
console.log('\n--> Submit Transaction: TransferAsset, transfer asset(asset2) to new owner(Tom)');
|
||||||
|
await contract.submitTransaction('TransferAsset', 'asset2', 'Tom');
|
||||||
|
console.log('*** Result: committed');
|
||||||
|
|
||||||
|
console.log('\n--> Evaluate Transaction: ReadAsset, function returns information about an asset with ID(asset2)');
|
||||||
|
result = await contract.evaluateTransaction('ReadAsset', 'asset2');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
// Rich Query with Pagination (Only supported if CouchDB is used as state database)
|
||||||
|
console.log('\n--> Evaluate Transaction: QueryAssetsWithPagination, function returns "Tom" assets');
|
||||||
|
result = await contract.evaluateTransaction('QueryAssetsWithPagination', '{"selector":{"docType":"asset","owner":"Tom"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}','3','');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
console.log('\n--> Submit Transaction: TransferAssetByColor, transfer all yellow assets to new owner(Michel)');
|
||||||
|
await contract.submitTransaction('TransferAssetByColor', 'yellow', 'Michel');
|
||||||
|
console.log('*** Result: committed');
|
||||||
|
|
||||||
|
// Rich Query (Only supported if CouchDB is used as state database):
|
||||||
|
console.log('\n--> Evaluate Transaction: QueryAssetsByOwner, find all assets with owner(Michel)');
|
||||||
|
result = await contract.evaluateTransaction('QueryAssetsByOwner', 'Michel');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
console.log('\n--> Evaluate Transaction: GetAssetHistory, get the history of an asset(asset7)');
|
||||||
|
result = await contract.evaluateTransaction('GetAssetHistory', 'asset7');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
// Rich Query (Only supported if CouchDB is used as state database):
|
||||||
|
console.log('\n--> Evaluate Transaction: QueryAssets, assets of size 15');
|
||||||
|
result = await contract.evaluateTransaction('QueryAssets', '{"selector":{"size":15}}');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
// Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database):
|
||||||
|
console.log('\n--> Evaluate Transaction: QueryAssets, Jin Soo\'s assets');
|
||||||
|
result = await contract.evaluateTransaction('QueryAssets', '{"selector":{"docType":"asset","owner":"Jin Soo"}, "use_index":["_design/indexOwnerDoc", "indexOwner"]}');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
// Rich Query with Pagination (Only supported if CouchDB is used as state database)
|
||||||
|
console.log('\n--> Evaluate Transaction: GetAssetsByRangeWithPagination - get page 1 of assets from asset3 to asset6 (asset3, asset4)');
|
||||||
|
result = await contract.evaluateTransaction('GetAssetsByRangeWithPagination', 'asset3', 'asset6', '2', '');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
// Rich Query with Pagination (Only supported if CouchDB is used as state database)
|
||||||
|
console.log('\n--> Evaluate Transaction: GetAssetsByRangeWithPagination - get page 2 of assets from asset3 to asset6 (asset4, asset5)');
|
||||||
|
result = await contract.evaluateTransaction('GetAssetsByRangeWithPagination', 'asset3', 'asset6', '2', 'asset4');
|
||||||
|
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
|
||||||
|
|
||||||
|
console.log('*** all tests completed');
|
||||||
|
} finally {
|
||||||
|
// Disconnect from the gateway when the application is closing
|
||||||
|
// This will close all connections to the network
|
||||||
|
gateway.disconnect();
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`******** FAILED to run the application: ${error}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('*** application ending');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "asset-transfer-basic",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Asset-transfer-basic application implemented in JavaScript",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=12",
|
||||||
|
"npm": ">=5"
|
||||||
|
},
|
||||||
|
"engineStrict": true,
|
||||||
|
"author": "Hyperledger",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"fabric-ca-client": "^2.2.0",
|
||||||
|
"fabric-network": "^2.2.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -99,7 +99,7 @@ type Asset struct {
|
||||||
// HistoryQueryResult structure used for returning result of history query
|
// HistoryQueryResult structure used for returning result of history query
|
||||||
type HistoryQueryResult struct {
|
type HistoryQueryResult struct {
|
||||||
Record *Asset `json:"record"`
|
Record *Asset `json:"record"`
|
||||||
TxID string `json:"txID"`
|
TxId string `json:"txId"`
|
||||||
Timestamp time.Time `json:"timestamp"`
|
Timestamp time.Time `json:"timestamp"`
|
||||||
IsDelete bool `json:"isDelete"`
|
IsDelete bool `json:"isDelete"`
|
||||||
}
|
}
|
||||||
|
|
@ -379,6 +379,8 @@ func getQueryResultForQueryStringWithPagination(ctx contractapi.TransactionConte
|
||||||
|
|
||||||
// GetAssetHistory returns the chain of custody for an asset since issuance.
|
// GetAssetHistory returns the chain of custody for an asset since issuance.
|
||||||
func (t *SimpleChaincode) GetAssetHistory(ctx contractapi.TransactionContextInterface, assetID string) ([]HistoryQueryResult, error) {
|
func (t *SimpleChaincode) GetAssetHistory(ctx contractapi.TransactionContextInterface, assetID string) ([]HistoryQueryResult, error) {
|
||||||
|
log.Printf("GetAssetHistory: ID %v", assetID)
|
||||||
|
|
||||||
resultsIterator, err := ctx.GetStub().GetHistoryForKey(assetID)
|
resultsIterator, err := ctx.GetStub().GetHistoryForKey(assetID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -393,17 +395,24 @@ func (t *SimpleChaincode) GetAssetHistory(ctx contractapi.TransactionContextInte
|
||||||
}
|
}
|
||||||
|
|
||||||
var asset Asset
|
var asset Asset
|
||||||
err = json.Unmarshal(response.Value, &asset)
|
if len(response.Value) > 0 {
|
||||||
if err != nil {
|
err = json.Unmarshal(response.Value, &asset)
|
||||||
return nil, err
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
asset = Asset{
|
||||||
|
ID: assetID,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
timestamp, err := ptypes.Timestamp(response.Timestamp)
|
timestamp, err := ptypes.Timestamp(response.Timestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
record := HistoryQueryResult{
|
record := HistoryQueryResult{
|
||||||
TxID: response.TxId,
|
TxId: response.TxId,
|
||||||
Timestamp: timestamp,
|
Timestamp: timestamp,
|
||||||
Record: &asset,
|
Record: &asset,
|
||||||
IsDelete: response.IsDelete,
|
IsDelete: response.IsDelete,
|
||||||
|
|
|
||||||
|
|
@ -81,7 +81,7 @@ class Chaincode extends Contract{
|
||||||
// ==== Create asset object and marshal to JSON ====
|
// ==== Create asset object and marshal to JSON ====
|
||||||
let asset = {};
|
let asset = {};
|
||||||
asset.docType = 'asset';
|
asset.docType = 'asset';
|
||||||
asset.assetID = assetID;
|
asset.ID = assetID;
|
||||||
asset.color = color;
|
asset.color = color;
|
||||||
asset.size = size;
|
asset.size = size;
|
||||||
asset.owner = owner;
|
asset.owner = owner;
|
||||||
|
|
@ -90,7 +90,7 @@ class Chaincode extends Contract{
|
||||||
// === Save asset to state ===
|
// === Save asset to state ===
|
||||||
await ctx.stub.putState(assetID, Buffer.from(JSON.stringify(asset)));
|
await ctx.stub.putState(assetID, Buffer.from(JSON.stringify(asset)));
|
||||||
let indexName = 'color~name'
|
let indexName = 'color~name'
|
||||||
let colorNameIndexKey = await ctx.stub.createCompositeKey(indexName, [asset.color, asset.assetID]);
|
let colorNameIndexKey = await ctx.stub.createCompositeKey(indexName, [asset.color, asset.ID]);
|
||||||
|
|
||||||
// Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble.
|
// Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble.
|
||||||
// Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value
|
// Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value
|
||||||
|
|
@ -137,7 +137,7 @@ class Chaincode extends Contract{
|
||||||
|
|
||||||
// delete the index
|
// delete the index
|
||||||
let indexName = 'color~name';
|
let indexName = 'color~name';
|
||||||
let colorNameIndexKey = ctx.stub.createCompositeKey(indexName, [assetJSON.color, assetJSON.assetID]);
|
let colorNameIndexKey = ctx.stub.createCompositeKey(indexName, [assetJSON.color, assetJSON.ID]);
|
||||||
if (!colorNameIndexKey) {
|
if (!colorNameIndexKey) {
|
||||||
throw new Error(' Failed to create the createCompositeKey');
|
throw new Error(' Failed to create the createCompositeKey');
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +145,7 @@ class Chaincode extends Contract{
|
||||||
await ctx.stub.deleteState(colorNameIndexKey);
|
await ctx.stub.deleteState(colorNameIndexKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TransferAsset transfers a asset by setting a new owner name on the asset
|
// TransferAsset transfers an asset by setting a new owner name on the asset
|
||||||
async TransferAsset(ctx, assetName, newOwner) {
|
async TransferAsset(ctx, assetName, newOwner) {
|
||||||
|
|
||||||
let assetAsBytes = await ctx.stub.getState(assetName);
|
let assetAsBytes = await ctx.stub.getState(assetName);
|
||||||
|
|
@ -245,7 +245,7 @@ class Chaincode extends Contract{
|
||||||
// GetQueryResultForQueryString executes the passed in query string.
|
// GetQueryResultForQueryString executes the passed in query string.
|
||||||
// Result set is built and returned as a byte array containing the JSON results.
|
// Result set is built and returned as a byte array containing the JSON results.
|
||||||
async GetQueryResultForQueryString(ctx, queryString) {
|
async GetQueryResultForQueryString(ctx, queryString) {
|
||||||
|
|
||||||
let resultsIterator = await ctx.stub.getQueryResult(queryString);
|
let resultsIterator = await ctx.stub.getQueryResult(queryString);
|
||||||
let results = await this.GetAllResults(resultsIterator, false);
|
let results = await this.GetAllResults(resultsIterator, false);
|
||||||
|
|
||||||
|
|
@ -258,7 +258,7 @@ class Chaincode extends Contract{
|
||||||
// The number of fetched records will be equal to or lesser than the page size.
|
// The number of fetched records will be equal to or lesser than the page size.
|
||||||
// Paginated range queries are only valid for read only transactions.
|
// Paginated range queries are only valid for read only transactions.
|
||||||
async GetAssetsByRangeWithPagination(ctx, startKey, endKey, pageSize, bookmark) {
|
async GetAssetsByRangeWithPagination(ctx, startKey, endKey, pageSize, bookmark) {
|
||||||
|
|
||||||
const { iterator, metadata } = await ctx.stub.getStateByRangeWithPagination(startKey, endKey, pageSize, bookmark);
|
const { iterator, metadata } = await ctx.stub.getStateByRangeWithPagination(startKey, endKey, pageSize, bookmark);
|
||||||
const results = await this.GetAllResults(iterator, false);
|
const results = await this.GetAllResults(iterator, false);
|
||||||
|
|
||||||
|
|
@ -293,8 +293,8 @@ class Chaincode extends Contract{
|
||||||
// GetAssetHistory returns the chain of custody for an asset since issuance.
|
// GetAssetHistory returns the chain of custody for an asset since issuance.
|
||||||
async GetAssetHistory(ctx, assetName) {
|
async GetAssetHistory(ctx, assetName) {
|
||||||
|
|
||||||
let resultsIterator = await ctx.stub.getHistoryForKey(assetName);
|
const resultsIterator = await ctx.stub.getHistoryForKey(assetName);
|
||||||
let results = await this.GetAllResults(resultsIterator, true);
|
const results = await this.GetAllResults(resultsIterator, true);
|
||||||
|
|
||||||
return JSON.stringify(results);
|
return JSON.stringify(results);
|
||||||
}
|
}
|
||||||
|
|
@ -302,7 +302,7 @@ class Chaincode extends Contract{
|
||||||
// AssetExists returns true when asset with given ID exists in world state
|
// AssetExists returns true when asset with given ID exists in world state
|
||||||
async AssetExists(ctx, assetName) {
|
async AssetExists(ctx, assetName) {
|
||||||
// ==== Check if asset already exists ====
|
// ==== Check if asset already exists ====
|
||||||
let assetState = await ctx.stub.getState(assetName);
|
const assetState = await ctx.stub.getState(assetName);
|
||||||
if ( !assetState || assetState.length === 0 ) {
|
if ( !assetState || assetState.length === 0 ) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@ -310,80 +310,74 @@ class Chaincode extends Contract{
|
||||||
}
|
}
|
||||||
|
|
||||||
async GetAllResults(iterator, isHistory) {
|
async GetAllResults(iterator, isHistory) {
|
||||||
let allResults = [];
|
const allResults = [];
|
||||||
while (true) {
|
let res = await iterator.next();
|
||||||
let res = await iterator.next();
|
while (!res.done) {
|
||||||
|
const jsonRes = {};
|
||||||
|
|
||||||
if (res.value && res.value.value.toString()) {
|
if (isHistory) {
|
||||||
let jsonRes = {};
|
jsonRes.TxId = res.value.txId;
|
||||||
console.log(res.value.value.toString('utf8'));
|
jsonRes.Timestamp = res.value.timestamp;
|
||||||
if (isHistory && isHistory === true) {
|
jsonRes.IsDelete = res.value.isDelete;
|
||||||
jsonRes.TxId = res.value.tx_id;
|
|
||||||
jsonRes.Timestamp = res.value.timestamp;
|
|
||||||
try {
|
|
||||||
jsonRes.Value = JSON.parse(res.value.value.toString('utf8'));
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
jsonRes.Value = res.value.value.toString('utf8');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
jsonRes.Key = res.value.key;
|
|
||||||
try {
|
|
||||||
jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err);
|
|
||||||
jsonRes.Record = res.value.value.toString('utf8');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
allResults.push(jsonRes);
|
|
||||||
}
|
}
|
||||||
if (res.done) {
|
|
||||||
await iterator.close();
|
if (res.value.value.length > 0) {
|
||||||
return allResults;
|
jsonRes.Record = JSON.parse(res.value.value.toString('utf8'));
|
||||||
|
} else {
|
||||||
|
jsonRes.Record = {ID: res.value.key};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
jsonRes.Key = res.value.key;
|
||||||
|
|
||||||
|
console.log('Result: ' + JSON.stringify(jsonRes));
|
||||||
|
allResults.push(jsonRes);
|
||||||
|
res = await iterator.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
await iterator.close();
|
||||||
|
return allResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitLedger creates sample assets in the ledger
|
// InitLedger creates sample assets in the ledger
|
||||||
async InitLedger(ctx) {
|
async InitLedger(ctx) {
|
||||||
const assets = [
|
const assets = [
|
||||||
{
|
{
|
||||||
assetID: 'asset1',
|
ID: 'asset1',
|
||||||
color: 'blue',
|
color: 'blue',
|
||||||
size: 5,
|
size: 5,
|
||||||
owner: 'Tom',
|
owner: 'Tom',
|
||||||
appraisedValue: 100
|
appraisedValue: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
assetID: 'asset2',
|
ID: 'asset2',
|
||||||
color: 'red',
|
color: 'red',
|
||||||
size: 5,
|
size: 5,
|
||||||
owner: 'Brad',
|
owner: 'Brad',
|
||||||
appraisedValue: 100
|
appraisedValue: 100
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
assetID: 'asset3',
|
ID: 'asset3',
|
||||||
color: 'green',
|
color: 'green',
|
||||||
size: 10,
|
size: 10,
|
||||||
owner: 'Jin Soo',
|
owner: 'Jin Soo',
|
||||||
appraisedValue: 200
|
appraisedValue: 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
assetID: 'asset4',
|
ID: 'asset4',
|
||||||
color: 'yellow',
|
color: 'yellow',
|
||||||
size: 10,
|
size: 10,
|
||||||
owner: 'Max',
|
owner: 'Max',
|
||||||
appraisedValue: 200
|
appraisedValue: 200
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
assetID: 'asset5',
|
ID: 'asset5',
|
||||||
color: 'black',
|
color: 'black',
|
||||||
size: 15,
|
size: 15,
|
||||||
owner: 'Adriana',
|
owner: 'Adriana',
|
||||||
appraisedValue: 250
|
appraisedValue: 250
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
assetID: 'asset6',
|
ID: 'asset6',
|
||||||
color: 'white',
|
color: 'white',
|
||||||
size: 15,
|
size: 15,
|
||||||
owner: 'Michel',
|
owner: 'Michel',
|
||||||
|
|
@ -394,7 +388,7 @@ class Chaincode extends Contract{
|
||||||
for (let i = 0; i < assets.length; i++) {
|
for (let i = 0; i < assets.length; i++) {
|
||||||
await this.CreateAsset(
|
await this.CreateAsset(
|
||||||
ctx,
|
ctx,
|
||||||
assets[i].assetID,
|
assets[i].ID,
|
||||||
assets[i].color,
|
assets[i].color,
|
||||||
assets[i].size,
|
assets[i].size,
|
||||||
assets[i].owner,
|
assets[i].owner,
|
||||||
|
|
|
||||||
|
|
@ -27,3 +27,4 @@ popd
|
||||||
|
|
||||||
print "Stopping network"
|
print "Stopping network"
|
||||||
./network.sh down
|
./network.sh down
|
||||||
|
rm -R ../asset-transfer-basic/application-javascript/wallet
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
FABRIC_VERSION=${FABRIC_VERSION:-2.2}
|
FABRIC_VERSION=${FABRIC_VERSION:-2.2}
|
||||||
CHAINCODE_LANGUAGE=${CHAINCODE_LANGUAGE:-go}
|
|
||||||
CHAINCODE_NAME=${CHAINCODE_NAME:-ledger}
|
CHAINCODE_NAME=${CHAINCODE_NAME:-ledger}
|
||||||
|
|
||||||
function print() {
|
function print() {
|
||||||
|
|
@ -14,8 +13,28 @@ function print() {
|
||||||
print "Creating network"
|
print "Creating network"
|
||||||
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
|
./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}"
|
||||||
|
|
||||||
print "Deploying ${CHAINCODE_NAME} chaincode"
|
print "Deploying ${CHAINCODE_NAME} go chaincode"
|
||||||
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}"
|
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1.0 -ccs 1 -ccl go
|
||||||
|
|
||||||
|
# Run Javascript application against the go chaincode
|
||||||
|
print "Initializing Javascript application"
|
||||||
|
pushd ../asset-transfer-ledger-queries/application-javascript
|
||||||
|
npm install
|
||||||
|
print "Executing app.js"
|
||||||
|
node app.js
|
||||||
|
popd
|
||||||
|
|
||||||
|
print "Deploying ${CHAINCODE_NAME} javascript chaincode"
|
||||||
|
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 2.0 -ccs 2 -ccl javascript
|
||||||
|
|
||||||
|
# Run Javascript application against the javascript chaincode
|
||||||
|
print "Initializing Javascript application"
|
||||||
|
pushd ../asset-transfer-ledger-queries/application-javascript
|
||||||
|
npm install
|
||||||
|
print "Executing app.js"
|
||||||
|
node app.js skipInit
|
||||||
|
popd
|
||||||
|
|
||||||
print "Stopping network"
|
print "Stopping network"
|
||||||
./network.sh down
|
./network.sh down
|
||||||
|
rm -R ../asset-transfer-ledger-queries/application-javascript/wallet
|
||||||
|
|
@ -6,9 +6,6 @@
|
||||||
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const fs = require('fs');
|
|
||||||
const path = require('path');
|
|
||||||
|
|
||||||
const adminUserId = 'admin';
|
const adminUserId = 'admin';
|
||||||
const adminUserPasswd = 'adminpw';
|
const adminUserPasswd = 'adminpw';
|
||||||
|
|
||||||
|
|
@ -53,7 +50,7 @@ exports.enrollAdmin = async (caClient, wallet) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.registerUser = async (caClient, wallet, userId, affiliation) => {
|
exports.registerAndEnrollUser = async (caClient, wallet, userId, affiliation) => {
|
||||||
try {
|
try {
|
||||||
// Check to see if we've already enrolled the user
|
// Check to see if we've already enrolled the user
|
||||||
const userIdentity = await wallet.get(userId);
|
const userIdentity = await wallet.get(userId);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue