From e90554f4f86b5199683e772197fca8e878ca7f41 Mon Sep 17 00:00:00 2001 From: Sijo Cherian Date: Thu, 6 Aug 2020 14:23:11 -0400 Subject: [PATCH] JS App workflow for private-data asset transfer, Chaincode fixes (#252) * Adding JS App workflow, Bugfixes in Go Chaincode Initial impl JS App workflow, Added 2 asset sample, with transfer & delete Bugfixes: Bug fix in ReadTransferAgreement Improved logging in Go Chaincode, Improved Chaincode error handling, checks for execution on orgs peer, bug fix, lint issues use addDiscoveryInterest to scope policy, instead of setEndorsingOrganizations Signed-off-by: Sijo Cherian * added js app to CI pipeline , and linting matrix Signed-off-by: Sijo Cherian Co-authored-by: Sijo Cherian --- .../application-javascript/.eslintignore | 5 + .../application-javascript/.eslintrc.js | 37 +++ .../application-javascript/.gitignore | 16 + .../application-javascript/app.js | 273 ++++++++++++++++++ .../application-javascript/caUtils.js | 97 +++++++ .../application-javascript/package.json | 45 +++ .../chaincode-go/go.mod | 18 +- .../chaincode-go/go.sum | 96 ++++++ .../chaincode-go/private_asset_queries.go | 80 ++--- .../chaincode-go/private_asset_transfer.go | 171 +++++++---- ci/azure-pipelines.yml | 8 +- ci/scripts/run-test-network-private.sh | 12 +- 12 files changed, 766 insertions(+), 92 deletions(-) create mode 100644 asset-transfer-private-data/application-javascript/.eslintignore create mode 100644 asset-transfer-private-data/application-javascript/.eslintrc.js create mode 100644 asset-transfer-private-data/application-javascript/.gitignore create mode 100644 asset-transfer-private-data/application-javascript/app.js create mode 100644 asset-transfer-private-data/application-javascript/caUtils.js create mode 100644 asset-transfer-private-data/application-javascript/package.json diff --git a/asset-transfer-private-data/application-javascript/.eslintignore b/asset-transfer-private-data/application-javascript/.eslintignore new file mode 100644 index 00000000..15958470 --- /dev/null +++ b/asset-transfer-private-data/application-javascript/.eslintignore @@ -0,0 +1,5 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +coverage diff --git a/asset-transfer-private-data/application-javascript/.eslintrc.js b/asset-transfer-private-data/application-javascript/.eslintrc.js new file mode 100644 index 00000000..8b83df73 --- /dev/null +++ b/asset-transfer-private-data/application-javascript/.eslintrc.js @@ -0,0 +1,37 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +module.exports = { + env: { + node: true, + mocha: true + }, + parserOptions: { + ecmaVersion: 8, + sourceType: 'script' + }, + extends: "eslint:recommended", + rules: { + indent: ['error', 4], + '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-tabs': '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'] + } +}; diff --git a/asset-transfer-private-data/application-javascript/.gitignore b/asset-transfer-private-data/application-javascript/.gitignore new file mode 100644 index 00000000..b7db0913 --- /dev/null +++ b/asset-transfer-private-data/application-javascript/.gitignore @@ -0,0 +1,16 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Coverage directory used by tools like istanbul +coverage + +# Dependency directories +node_modules/ +jspm_packages/ +package-lock.json + +wallet.org1 +wallet.org2 +!wallet.org1/.gitkeep +!wallet.org2/.gitkeep \ No newline at end of file diff --git a/asset-transfer-private-data/application-javascript/app.js b/asset-transfer-private-data/application-javascript/app.js new file mode 100644 index 00000000..93556f96 --- /dev/null +++ b/asset-transfer-private-data/application-javascript/app.js @@ -0,0 +1,273 @@ +/* + * 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 fs = require('fs'); +const caUtils = require('./caUtils'); + +const myChannel = 'mychannel'; +const myChaincodeName = 'private'; + +const memberAssetCollectionName = 'assetCollection'; +const org1PrivateCollectionName = 'Org1MSPPrivateCollection'; +const org2PrivateCollectionName = 'Org2MSPPrivateCollection'; +const mspOrg2 = 'Org2MSP'; +const mspOrg1 = 'Org1MSP'; +function prettyJSONString(inputString) { + if (inputString) { + return JSON.stringify(JSON.parse(inputString), null, 2); + } + else { + return inputString; + } +} + +async function initContractFromOrg1Identity() { + console.log('\nFabric client user & Gateway init: Using Org1 identity to Org1 Peer'); + // load the network configuration + let ccpPath = path.resolve(__dirname, '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org1.example.com', 'connection-org1.json'); + let fileExists = fs.existsSync(ccpPath); + if (!fileExists) { + throw new Error(`no such file or directory: ${ccpPath}`); + } + let ccpOrg1 = JSON.parse(fs.readFileSync(ccpPath, 'utf8')); + // Create a new file system based wallet for managing identities. + const walletPathOrg1 = path.join(__dirname, 'wallet/org1'); + const walletOrg1 = await Wallets.newFileSystemWallet(walletPathOrg1); + console.log(`Org1 wallet path: ${walletPathOrg1}`); + + // Create a new CA client for interacting with this Orgs CA. + const caInfo = ccpOrg1.certificateAuthorities['ca.org1.example.com']; + const caTLSCACerts = caInfo.tlsCACerts.pem; + const caService = new FabricCAServices(caInfo.url, { trustedRoots: caTLSCACerts, verify: false }, caInfo.caName); + + // register & enroll admin user with CA, stores admin identity in local wallet + await caUtils.EnrollOrgAdminUser(mspOrg1, walletOrg1, caService); + + // register & enroll application user with CA, which is used as client identify to make chaincode calls, stores app user identity in local wallet + await caUtils.RegisterOrgUser(caUtils.Org1UserId, mspOrg1, walletOrg1, caService); + + try { + // Create a new gateway for connecting to Org's peer node. + const gatewayOrg1 = new Gateway(); + //connect using Discovery enabled + await gatewayOrg1.connect(ccpOrg1, + { wallet: walletOrg1, identity: caUtils.Org1UserId, discovery: { enabled: true, asLocalhost: true } }); + + return gatewayOrg1; + } catch (error) { + console.error(`Error in connecting to gateway: ${error}`); + process.exit(1); + } +} + +async function initContractFromOrg2Identity() { + console.log('\nFabric client user & Gateway init: Using Org2 identity to Org2 Peer'); + // load the network configuration + let ccpPath = path.resolve(__dirname, '..', '..', 'test-network', 'organizations', 'peerOrganizations', 'org2.example.com', 'connection-org2.json'); + let fileExists = fs.existsSync(ccpPath); + if (!fileExists) { + throw new Error(`no such file or directory: ${ccpPath}`); + } + const ccpOrg2 = JSON.parse(fs.readFileSync(ccpPath, 'utf8')); + + // Create a new file system based wallet for managing identities. + const walletPathOrg2 = path.join(__dirname, 'wallet/org2'); + const walletOrg2 = await Wallets.newFileSystemWallet(walletPathOrg2); + console.log(`Org2 wallet path: ${walletPathOrg2}`); + + // Create a new CA client for interacting with this Orgs CA. + const caInfo = ccpOrg2.certificateAuthorities['ca.org2.example.com']; + const caTLSCACerts = caInfo.tlsCACerts.pem; + const caService = new FabricCAServices(caInfo.url, { trustedRoots: caTLSCACerts, verify: false }, caInfo.caName); + + await caUtils.EnrollOrgAdminUser(mspOrg2, walletOrg2, caService); + + // register & enroll application user with CA, which is used as client identify to make chaincode calls, stores app user identity in local wallet + await caUtils.RegisterOrgUser(caUtils.Org2UserId, mspOrg2, walletOrg2, caService); + + try { + // Create a new gateway for connecting to Org's peer node. + const gatewayOrg2 = new Gateway(); + await gatewayOrg2.connect(ccpOrg2, + { wallet: walletOrg2, identity: caUtils.Org2UserId, discovery: { enabled: true, asLocalhost: true } }); + + return gatewayOrg2; + } catch (error) { + console.error(`Error in connecting to gateway: ${error}`); + process.exit(1); + } +} + +// Main workflow : usecase details at asset-transfer-private-data/chaincode-go/README.md +// This app uses fabric-samples/test-network based setup and the companion chaincode +// For this usecase illustration, we will use both Org1 & Org2 client identity from this same app +// In real world the Org1 & Org2 identity will be used in different apps to achieve asset transfer. +async function main() { + try { + + /** ******* Fabric client init: Using Org1 identity to Org1 Peer ********** */ + const gatewayOrg1 = await initContractFromOrg1Identity(); + const networkOrg1 = await gatewayOrg1.getNetwork(myChannel); + const contractOrg1 = networkOrg1.getContract(myChaincodeName); + // Since this sample chaincode uses, Private Data Collection level endorsement policy, addDiscoveryInterest + // scopes the discovery service further to use the endorsement policies of collections, if any + contractOrg1.addDiscoveryInterest({ name: myChaincodeName, collectionNames: [memberAssetCollectionName, org1PrivateCollectionName] }); + + /** ~~~~~~~ Fabric client init: Using Org2 identity to Org2 Peer ~~~~~~~ */ + const gatewayOrg2 = await initContractFromOrg2Identity(); + const networkOrg2 = await gatewayOrg2.getNetwork(myChannel); + const contractOrg2 = networkOrg2.getContract(myChaincodeName); + contractOrg2.addDiscoveryInterest({ name: myChaincodeName, collectionNames: [memberAssetCollectionName, org2PrivateCollectionName] }); + try { + // Sample transactions are listed below + // Add few sample Assets & transfers one of the asset from Org1 to Org2 as the new owner + let assetID1 = 'asset1'; + let assetID2 = 'asset2'; + const assetType = 'ValuableAsset'; + let result; + let asset1Data = { objectType: assetType, assetID: assetID1, color: 'green', size: 20, appraisedValue: 100 }; + let asset2Data = { objectType: assetType, assetID: assetID2, color: 'blue', size: 35, appraisedValue: 727 }; + + console.log('\n**************** As Org1 Client ****************'); + console.log('Adding Assets to work with: Submit Transaction: CreateAsset ' + assetID1); + let statefulTxn = contractOrg1.createTransaction('CreateAsset'); + //if you need to customize endorsement to specific set of Orgs, use setEndorsingOrganizations + //statefulTxn.setEndorsingOrganizations(mspOrg1); + let tmapData = Buffer.from(JSON.stringify(asset1Data)); + statefulTxn.setTransient({ + asset_properties: tmapData + }); + result = await statefulTxn.submit(); + + //Add asset2 + console.log('Submit Transaction: CreateAsset ' + assetID2); + statefulTxn = contractOrg1.createTransaction('CreateAsset'); + tmapData = Buffer.from(JSON.stringify(asset2Data)); + statefulTxn.setTransient({ + asset_properties: tmapData + }); + result = await statefulTxn.submit(); + + console.log('\n***********************'); + console.log('Evaluate Transaction: GetAssetByRange asset0-asset9'); + // GetAssetByRange returns assets on the ledger with ID in the range of startKey (inclusive) and endKey (exclusive) + result = await contractOrg1.evaluateTransaction('GetAssetByRange', 'asset0', 'asset9'); + console.log(' result: ' + prettyJSONString(result.toString())); + + console.log('\n***********************'); + console.log('Evaluate Transaction: ReadAssetPrivateDetails from ' + org1PrivateCollectionName); + // ReadAssetPrivateDetails reads data from Org's private collection. Args: collectionName, assetID + result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); + console.log(' result: ' + prettyJSONString(result.toString())); + + + console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~'); + console.log('Evaluate Transaction: ReadAsset ' + assetID1); + result = await contractOrg2.evaluateTransaction('ReadAsset', assetID1); + console.log(' result: ' + prettyJSONString(result.toString())); + let assetOwner = JSON.parse(result.toString()).owner; + console.log(' Asset owner: ' + Buffer.from(assetOwner, 'base64').toString()); + + // Org2 cannot ReadAssetPrivateDetails from Org1's private collection due to Collection policy + // Will fail: await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); + + // Buyer from Org2 agrees to buy the asset assetID1 // + // To purchase the asset, the buyer needs to agree to the same value as the asset owner + let dataForAgreement = { assetID: assetID1, appraisedValue: 100 }; + console.log('\nSubmit Transaction: AgreeToTransfer payload ' + JSON.stringify(dataForAgreement)); + statefulTxn = contractOrg2.createTransaction('AgreeToTransfer'); + tmapData = Buffer.from(JSON.stringify(dataForAgreement)); + statefulTxn.setTransient({ + asset_value: tmapData + }); + result = await statefulTxn.submit(); + + //Buyer can withdraw the Agreement, using DeleteTranferAgreement + /*statefulTxn = contractOrg2.createTransaction('DeleteTranferAgreement'); + statefulTxn.setEndorsingOrganizations(mspOrg2); + let dataForDeleteAgreement = { assetID: assetID1 }; + tmapData = Buffer.from(JSON.stringify(dataForDeleteAgreement)); + statefulTxn.setTransient({ + agreement_delete: tmapData + }); + result = await statefulTxn.submit();*/ + + console.log('\n**************** As Org1 Client ****************'); + // All members can send txn ReadTransferAgreement, set by Org2 above + console.log('Evaluate Transaction: ReadTransferAgreement ' + assetID1); + result = await contractOrg1.evaluateTransaction('ReadTransferAgreement', assetID1); + console.log(' result: ' + prettyJSONString(result.toString())); + + // Transfer the asset to Org2 // + // To transfer the asset, the owner needs to pass the MSP ID of new asset owner, and initiate the transfer + console.log('Submit Transaction: TransferAsset ' + assetID1); + let buyerDetails = { assetID: assetID1, buyerMSP: mspOrg2 }; + statefulTxn = contractOrg1.createTransaction('TransferAsset'); + tmapData = Buffer.from(JSON.stringify(buyerDetails)); + statefulTxn.setTransient({ + asset_owner: tmapData + }); + result = await statefulTxn.submit(); + + console.log('\n***********************'); + //Again ReadAsset : results will show that the buyer identity now owns the asset: + console.log('Evaluate Transaction: ReadAsset ' + assetID1); + result = await contractOrg1.evaluateTransaction('ReadAsset', assetID1); + console.log(' result: ' + prettyJSONString(result.toString())); + assetOwner = JSON.parse(result.toString()).owner; + console.log(' Asset owner: ' + Buffer.from(assetOwner, 'base64').toString()); + + //Confirm that transfer removed the private details from the Org1 collection: + console.log('Evaluate Transaction: ReadAssetPrivateDetails'); + // ReadAssetPrivateDetails reads data from Org's private collection: Should return empty + result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); + console.log(' result: ' + prettyJSONString(result.toString())); + + console.log('Evaluate Transaction: ReadAsset ' + assetID2); + result = await contractOrg1.evaluateTransaction('ReadAsset', assetID2); + console.log(' result: ' + prettyJSONString(result.toString())); + + console.log('\n***********************'); + // Delete Asset2 + console.log('Deleting Asset ' + assetID2); + statefulTxn = contractOrg1.createTransaction('DeleteAsset'); + + let dataForDelete = { assetID: assetID2 }; + tmapData = Buffer.from(JSON.stringify(dataForDelete)); + statefulTxn.setTransient({ + asset_delete: tmapData + }); + result = await statefulTxn.submit(); + console.log('Evaluate Transaction: ReadAsset ' + assetID2); + result = await contractOrg1.evaluateTransaction('ReadAsset', assetID2); + console.log(' result: ' + prettyJSONString(result.toString())); + + console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~'); + // Org2 can ReadAssetPrivateDetails: Org2 is owner, and private details exist in new owner's Collection + console.log('Evaluate Transaction as Org2: ReadAssetPrivateDetails ' + assetID1 + ' from ' + org2PrivateCollectionName); + result = await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org2PrivateCollectionName, assetID1); + console.log(' result: ' + prettyJSONString(result.toString())); + } finally { + // Disconnect from the gateway peer when all work for this client identity is complete + gatewayOrg1.disconnect(); + gatewayOrg2.disconnect(); + } + } catch (error) { + console.error(`Error in transaction: ${error}`); + if (error.stack) { + console.error(error.stack); + } + process.exit(1); + } +} + +main(); \ No newline at end of file diff --git a/asset-transfer-private-data/application-javascript/caUtils.js b/asset-transfer-private-data/application-javascript/caUtils.js new file mode 100644 index 00000000..645a8378 --- /dev/null +++ b/asset-transfer-private-data/application-javascript/caUtils.js @@ -0,0 +1,97 @@ +/* + * Copyright IBM Corp. All Rights Reserved. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +'use strict'; + +const adminUserId = 'admin'; +const adminUserPasswd = 'adminpw'; +const org1UserId = 'appUser1'; +const org2UserId = 'appUser2'; +const caChaincodeUserRole = 'client'; + +async function registerOrgUser(appUserId, mspId, wallet, caService) { + try { + // Check to see if we've already enrolled the user. + const userIdentity = await wallet.get(appUserId); + if (userIdentity) { + console.log('An identity for the user ' + appUserId + ' already exists in the wallet'); + return; + } + + // Check to see if we've already enrolled the admin user. + const adminIdentity = await wallet.get(adminUserId); + if (!adminIdentity) { + console.log('An identity for the admin user does not exist in the wallet'); + console.log('Call enrollAdmin for admin user enroll before retrying'); + return; + } + + // build a user object for authenticating with the CA + const provider = wallet.getProviderRegistry().getProvider(adminIdentity.type); + const adminUser = await provider.getUserContext(adminIdentity, adminUserId); + + // Register the user, enroll the user, and import the new identity into the wallet. + // if affiliation is specified by client, the affiliation value must be configured in CA + const secret = await caService.register({ + affiliation: 'org2.department1', + enrollmentID: appUserId, + role: caChaincodeUserRole + }, adminUser); + const enrollment = await caService.enroll({ + enrollmentID: appUserId, + enrollmentSecret: secret + }); + const x509Identity = { + credentials: { + certificate: enrollment.certificate, + privateKey: enrollment.key.toBytes(), + }, + mspId: mspId, + type: 'X.509', + }; + await wallet.put(appUserId, x509Identity); + console.log('Successfully registered and enrolled user ' + appUserId + ' and imported it into the wallet'); + + } catch (error) { + console.error(`Failed to register user : ${error}`); + process.exit(1); + } +} + +async function enrollOrgAdminUser(mspId, wallet, caService) { + try { + + // Check to see if we've already enrolled the admin user. + const identity = await wallet.get(adminUserId); + if (identity) { + console.log('An identity for the admin user already exists in the wallet'); + return; + } + + // Enroll the admin user, and import the new identity into the wallet. + const enrollment = await caService.enroll({ enrollmentID: adminUserId, enrollmentSecret: adminUserPasswd }); + const x509Identity = { + credentials: { + certificate: enrollment.certificate, + privateKey: enrollment.key.toBytes(), + }, + mspId: mspId, + type: 'X.509', + }; + await wallet.put(adminUserId, x509Identity); + console.log('Successfully enrolled admin user and imported it into the wallet'); + + } catch (error) { + console.error(`Failed to enroll admin user : ${error}`); + process.exit(1); + } +} + +exports.AdminUserId = adminUserId; +exports.Org1UserId = org1UserId; +exports.Org2UserId = org2UserId; +exports.RegisterOrgUser = registerOrgUser; +exports.EnrollOrgAdminUser = enrollOrgAdminUser; \ No newline at end of file diff --git a/asset-transfer-private-data/application-javascript/package.json b/asset-transfer-private-data/application-javascript/package.json new file mode 100644 index 00000000..265ceed8 --- /dev/null +++ b/asset-transfer-private-data/application-javascript/package.json @@ -0,0 +1,45 @@ +{ + "name": "asset-transfer-private-data", + "version": "1.0.0", + "description": "asset-transfer-private-data application implemented in JavaScript", + "engines": { + "node": ">=12", + "npm": ">=5" + }, + "scripts": { + "lint": "eslint .", + "pretest": "npm run lint", + "test": "nyc mocha --recursive" + }, + "engineStrict": true, + "author": "Hyperledger", + "license": "Apache-2.0", + "dependencies": { + "fabric-ca-client": "^2.2.0", + "fabric-network": "^2.2.0" + }, + "devDependencies": { + "chai": "^4.2.0", + "eslint": "^5.9.0", + "mocha": "^5.2.0", + "nyc": "^14.1.1", + "sinon": "^7.1.1", + "sinon-chai": "^3.3.0" + }, + "nyc": { + "exclude": [ + "coverage/**", + "test/**" + ], + "reporter": [ + "text-summary", + "html" + ], + "all": true, + "check-coverage": true, + "statements": 100, + "branches": 100, + "functions": 100, + "lines": 100 + } +} diff --git a/asset-transfer-private-data/chaincode-go/go.mod b/asset-transfer-private-data/chaincode-go/go.mod index f895d27d..332f10af 100644 --- a/asset-transfer-private-data/chaincode-go/go.mod +++ b/asset-transfer-private-data/chaincode-go/go.mod @@ -3,6 +3,22 @@ module github.com/hyperledger/fabric-samples/asset-transfer-private-data/chainco go 1.14 require ( - github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212 + github.com/go-openapi/jsonreference v0.19.4 // indirect + github.com/go-openapi/spec v0.19.8 // indirect + github.com/go-openapi/swag v0.19.9 // indirect + github.com/gobuffalo/envy v1.9.0 // indirect + github.com/gobuffalo/packd v1.0.0 // indirect + github.com/golang/protobuf v1.4.2 // indirect + github.com/hyperledger/fabric-chaincode-go v0.0.0-20200511190512-bcfeb58dd83a github.com/hyperledger/fabric-contract-api-go v1.1.0 + github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23 // indirect + github.com/mailru/easyjson v0.7.1 // indirect + github.com/rogpeppe/go-internal v1.6.0 // indirect + github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect + golang.org/x/net v0.0.0-20200707034311-ab3426394381 // indirect + golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 // indirect + google.golang.org/genproto v0.0.0-20200721032028-5044d0edf986 // indirect + google.golang.org/grpc v1.30.0 // indirect + google.golang.org/protobuf v1.25.0 // indirect + gopkg.in/yaml.v2 v2.3.0 // indirect ) diff --git a/asset-transfer-private-data/chaincode-go/go.sum b/asset-transfer-private-data/chaincode-go/go.sum index 94a66455..f22cc6ed 100644 --- a/asset-transfer-private-data/chaincode-go/go.sum +++ b/asset-transfer-private-data/chaincode-go/go.sum @@ -6,48 +6,84 @@ github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbt github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= github.com/cucumber/godog v0.8.0/go.mod h1:Cp3tEV1LRAyH/RuCThcxHS/+9ORZ+FMzPva2AZ5Ki+A= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= github.com/go-openapi/jsonpointer v0.19.3 h1:gihV7YNZK1iK6Tgwwsxo2rJbD1GTbdm72325Bq8FI3w= github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= github.com/go-openapi/jsonreference v0.19.2 h1:o20suLFB4Ri0tuzpWtyHlh7E7HnkqTNLq6aR6WVNS1w= github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/jsonreference v0.19.4 h1:3Vw+rh13uq2JFNxgnMTGE1rnoieU9FmyE1gvnyylsYg= +github.com/go-openapi/jsonreference v0.19.4/go.mod h1:RdybgQwPxbL4UEjuAruzK1x3nE69AqPYEJeo/TWfEeg= github.com/go-openapi/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo= github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo= +github.com/go-openapi/spec v0.19.8 h1:qAdZLh1r6QF/hI/gTq+TJTvsQUodZsM7KLqkAJdiJNg= +github.com/go-openapi/spec v0.19.8/go.mod h1:Hm2Jr4jv8G1ciIAo+frC/Ft+rR2kQDh8JHKHb3gWUSk= github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/go-openapi/swag v0.19.5 h1:lTz6Ys4CmqqCQmZPBlbQENR1/GucA2bzYTE12Pw4tFY= github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.9 h1:1IxuqvBUU3S2Bi4YC7tlP9SJF1gVpCvqN0T2Qof4azE= +github.com/go-openapi/swag v0.19.9/go.mod h1:ao+8BpOPyKdpQz3AOJfbeEVpLmWAvlT1IfTe5McPyhY= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/gobuffalo/envy v1.7.0 h1:GlXgaiBkmrYMHco6t4j7SacKO4XUjvh5pwXh0f4uxXU= github.com/gobuffalo/envy v1.7.0/go.mod h1:n7DRkBerg/aorDM8kbduw5dN3oXGswK5liaSCx4T5NI= +github.com/gobuffalo/envy v1.9.0 h1:eZR0DuEgVLfeIb1zIKt3bT4YovIMf9O9LXQeCZLXpqE= +github.com/gobuffalo/envy v1.9.0/go.mod h1:FurDp9+EDPE4aIUS3ZLyD+7/9fpx7YRt/ukY6jIHf0w= github.com/gobuffalo/logger v1.0.0/go.mod h1:2zbswyIUa45I+c+FLXuWl9zSWEiVuthsk8ze5s8JvPs= github.com/gobuffalo/packd v0.3.0 h1:eMwymTkA1uXsqxS0Tpoop3Lc0u3kTfiMBE6nKtQU4g4= github.com/gobuffalo/packd v0.3.0/go.mod h1:zC7QkmNkYVGKPw4tHpBQ+ml7W/3tIebgeo1b36chA3Q= +github.com/gobuffalo/packd v1.0.0 h1:6ERZvJHfe24rfFmA9OaoKBdC7+c9sydrytMg8SdFGBM= +github.com/gobuffalo/packd v1.0.0/go.mod h1:6VTc4htmJRFB7u1m/4LeMTWjFoYrUiBkU9Fdec9hrhI= github.com/gobuffalo/packr v1.30.1 h1:hu1fuVR3fXEZR7rXNW3h8rqSML8EVAf6KNm0NKO/wKg= github.com/gobuffalo/packr v1.30.1/go.mod h1:ljMyFO2EcrnzsHsN99cvbq055Y9OhRrIaviy289eRuk= github.com/gobuffalo/packr/v2 v2.5.1/go.mod h1:8f9c96ITobJlPzI44jj+4tHnEKNt0xXWSVlXRN9X1Iw= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0 h1:/QaMHBdZ26BB3SSst0Iwl10Epc+xhTquomWX0oZEB6w= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212 h1:1i4lnpV8BDgKOLi1hgElfBqdHXjXieSuj8629mwBZ8o= github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20200511190512-bcfeb58dd83a h1:KoFw2HnRfW+EItMP0zvUUl1FGzDb/7O0ov7uXZffQok= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20200511190512-bcfeb58dd83a/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc= github.com/hyperledger/fabric-contract-api-go v1.1.0 h1:K9uucl/6eX3NF0/b+CGIiO1IPm1VYQxBkpnVGJur2S4= github.com/hyperledger/fabric-contract-api-go v1.1.0/go.mod h1:nHWt0B45fK53owcFpLtAe8DH0Q5P068mnzkNXMPSL7E= github.com/hyperledger/fabric-protos-go v0.0.0-20190919234611-2a87503ac7c9/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e h1:9PS5iezHk/j7XriSlNuSQILyCOfcZ9wZ3/PiucmSE8E= github.com/hyperledger/fabric-protos-go v0.0.0-20200424173316-dd554ba3746e/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23 h1:SEbB3yH4ISTGRifDamYXAst36gO2kM855ndMJlsv+pc= +github.com/hyperledger/fabric-protos-go v0.0.0-20200707132912-fee30f3ccd23/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc= github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg= @@ -55,21 +91,30 @@ github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0L github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.1 h1:mdxE1MF9o53iCb2Ghj1VfWvh7ZOwHpnVG/xwXrV90U8= +github.com/mailru/easyjson v0.7.1/go.mod h1:KAzv3t3aY1NaHWoQz1+4F1ccyAH66Jk7yos7ldAVICs= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.3.0 h1:RR9dF3JtopPvtkroDZuVD7qquD0bnHlKSqaQhgwt8yk= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.3.2/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.6.0 h1:IZRgg4sfrDH7nsAD1Y/Nwj+GzIfEwpJSLjCaNC3SbsI= +github.com/rogpeppe/go-internal v1.6.0/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= @@ -84,10 +129,13 @@ github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoH github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= +github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= @@ -97,15 +145,27 @@ golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnf golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20190621222207-cc06ce4a13d4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297 h1:k7pJ2yAPLPgbskkFdhRCsA77k2fySZ1zf2zCjvQCiIM= golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200707034311-ab3426394381 h1:VXak5I6aEWmAXeQjA+QSZzlgNrpq9mjcfDemuexIKsU= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -114,25 +174,61 @@ golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542 h1:6ZQFf1D2YYDDI7eSwW8adlkkavTB9sw5I24FVtEvNUQ= golang.org/x/sys v0.0.0-20190710143415-6ec70d6a5542/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666 h1:gVCS+QOncANNPlmlO1AhlU3oxs4V9z+gTtPwIk3p2N8= +golang.org/x/sys v0.0.0-20200720211630-cb9d2d5c5666/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3 h1:cokOdA+Jmi5PJGXLlLllQSgYigAEfHXJAERHVMaCc2k= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= golang.org/x/tools v0.0.0-20190624180213-70d37148ca0c/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b h1:lohp5blsw53GBXtLyLNaTXPXS9pJ1tiTw61ZHUoE9Qw= google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200721032028-5044d0edf986 h1:10ohwcLf82I55O/aQxYqmWKoOdNbQTYYComeP1KDOS4= +google.golang.org/genproto v0.0.0-20200721032028-5044d0edf986/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.30.0 h1:M5a8xTlYTxwMn5ZFkwhRabsygDY5G8TYLyQDBxJNAxE= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0 h1:UhZDfRO8JRQru4/+LlLE0BRKGF8L+PICnvYZmx/fEGA= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0 h1:Ejskq+SyPohKW+1uil0JJMtmHCgJPJ/qWTxr8qp+R4c= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= diff --git a/asset-transfer-private-data/chaincode-go/private_asset_queries.go b/asset-transfer-private-data/chaincode-go/private_asset_queries.go index cf87cb25..ea834a82 100644 --- a/asset-transfer-private-data/chaincode-go/private_asset_queries.go +++ b/asset-transfer-private-data/chaincode-go/private_asset_queries.go @@ -9,6 +9,7 @@ package main import ( "encoding/json" "fmt" + "log" "github.com/hyperledger/fabric-contract-api-go/contractapi" ) @@ -16,16 +17,20 @@ import ( // ReadAsset reads the information from collection func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, assetID string) (*Asset, error) { + log.Printf("ReadAsset: collection %v, ID %v", assetCollection, assetID) assetJSON, err := ctx.GetStub().GetPrivateData(assetCollection, assetID) //get the asset from chaincode state if err != nil { return nil, fmt.Errorf("failed to read from asset %v", err) } + + //No Asset found, return empty response if assetJSON == nil { - return nil, fmt.Errorf("%v does not exist", assetID) + log.Printf("%v does not exist in collection %v", assetID, assetCollection) + return nil, nil } - asset := new(Asset) - err = json.Unmarshal(assetJSON, asset) + var asset *Asset + err = json.Unmarshal(assetJSON, &asset) if err != nil { return nil, fmt.Errorf("failed to unmarshal JSON: %v", err) } @@ -36,17 +41,18 @@ func (s *SmartContract) ReadAsset(ctx contractapi.TransactionContextInterface, a // ReadAssetPrivateDetails reads the asset private details in organization specific collection func (s *SmartContract) ReadAssetPrivateDetails(ctx contractapi.TransactionContextInterface, collection string, assetID string) (*AssetPrivateDetails, error) { - + log.Printf("ReadAssetPrivateDetails: collection %v, ID %v", collection, assetID) assetDetailsJSON, err := ctx.GetStub().GetPrivateData(collection, assetID) // Get the asset from chaincode state if err != nil { return nil, fmt.Errorf("failed to read from asset details %v", err) } if assetDetailsJSON == nil { - return nil, fmt.Errorf("appraisal value for %v does not exist in private data collection", assetID) + log.Printf("AssetPrivateDetails for %v does not exist in collection %v", assetID, collection) + return nil, nil } - assetDetails := new(AssetPrivateDetails) - err = json.Unmarshal(assetDetailsJSON, assetDetails) + var assetDetails *AssetPrivateDetails + err = json.Unmarshal(assetDetailsJSON, &assetDetails) if err != nil { return nil, fmt.Errorf("failed to unmarshal JSON: %v", err) } @@ -54,34 +60,34 @@ func (s *SmartContract) ReadAssetPrivateDetails(ctx contractapi.TransactionConte return assetDetails, nil } -// ReadTransferAgreement gets the identity from the transfer agreement from collection -func (s *SmartContract) ReadTransferAgreement(ctx contractapi.TransactionContextInterface, assetID string) (string, error) { - - // create composite key +// ReadTransferAgreement gets the buyer's identity from the transfer agreement from collection +func (s *SmartContract) ReadTransferAgreement(ctx contractapi.TransactionContextInterface, assetID string) (*TransferAgreement, error) { + log.Printf("ReadTransferAgreement: collection %v, ID %v", assetCollection, assetID) + // composite key for TransferAgreement of this asset transferAgreeKey, err := ctx.GetStub().CreateCompositeKey(transferAgreementObjectType, []string{assetID}) if err != nil { - return "", fmt.Errorf("failed to create composite key: %v", err) + return nil, fmt.Errorf("failed to create composite key: %v", err) } buyerIdentity, err := ctx.GetStub().GetPrivateData(assetCollection, transferAgreeKey) // Get the identity from collection if err != nil { - return "", fmt.Errorf("failed to read from asset %v", err) + return nil, fmt.Errorf("failed to read TransferAgreement: %v", err) } if buyerIdentity == nil { - return "", fmt.Errorf("transfer agreement for %v does not exist", assetID) + log.Printf("TransferAgreement for %v does not exist", assetID) + return nil, nil } - - return string(buyerIdentity), nil - + agreement := &TransferAgreement{ + ID: assetID, + BuyerID: string(buyerIdentity), + } + return agreement, nil } -// =========================================================================================== // GetAssetByRange performs a range query based on the start and end keys provided. Range // queries can be used to read data from private data collections, but can not be used in // a transaction that also writes to private data. - -// =========================================================================================== -func (s *SmartContract) GetAssetByRange(ctx contractapi.TransactionContextInterface, startKey string, endKey string) ([]Asset, error) { +func (s *SmartContract) GetAssetByRange(ctx contractapi.TransactionContextInterface, startKey string, endKey string) ([]*Asset, error) { resultsIterator, err := ctx.GetStub().GetPrivateDataByRange(assetCollection, startKey, endKey) if err != nil { @@ -89,7 +95,7 @@ func (s *SmartContract) GetAssetByRange(ctx contractapi.TransactionContextInterf } defer resultsIterator.Close() - results := []Asset{} + results := []*Asset{} for resultsIterator.HasNext() { response, err := resultsIterator.Next() @@ -97,14 +103,13 @@ func (s *SmartContract) GetAssetByRange(ctx contractapi.TransactionContextInterf return nil, err } - asset := new(Asset) - - err = json.Unmarshal(response.Value, asset) + var asset *Asset + err = json.Unmarshal(response.Value, &asset) if err != nil { return nil, fmt.Errorf("failed to unmarshal JSON: %v", err) } - results = append(results, *asset) + results = append(results, asset) } return results, nil @@ -125,14 +130,15 @@ func (s *SmartContract) GetAssetByRange(ctx contractapi.TransactionContextInterf // ============================================================================================ // ===== Example: Parameterized rich query ================================================= -// QueryAssetByOwner queries for assets based on a passed in owner. + +// QueryAssetByOwner queries for assets based on assetType, owner. // This is an example of a parameterized query where the query logic is baked into the chaincode, // and accepting a single query parameter (owner). // Only available on state databases that support rich query (e.g. CouchDB) // ========================================================================================= -func (s *SmartContract) QueryAssetByOwner(ctx contractapi.TransactionContextInterface, owner string) ([]Asset, error) { +func (s *SmartContract) QueryAssetByOwner(ctx contractapi.TransactionContextInterface, assetType string, owner string) ([]*Asset, error) { - queryString := fmt.Sprintf("{\"selector\":{\"type\":\"asset\",\"owner\":\"%s\"}}", owner) + queryString := fmt.Sprintf("{\"selector\":{\"objectType\":\"%v\",\"owner\":\"%v\"}}", assetType, owner) queryResults, err := s.getQueryResultForQueryString(ctx, queryString) if err != nil { @@ -141,14 +147,13 @@ func (s *SmartContract) QueryAssetByOwner(ctx contractapi.TransactionContextInte return queryResults, nil } -// ===== Example: Ad hoc rich query ======================================================== + // QueryAssets uses a query string to perform a query for assets. // Query string matching state database syntax is passed in and executed as is. // Supports ad hoc queries that can be defined at runtime by the client. // If this is not desired, follow the QueryAssetByOwner example for parameterized queries. // Only available on state databases that support rich query (e.g. CouchDB) -// ========================================================================================= -func (s *SmartContract) QueryAssets(ctx contractapi.TransactionContextInterface, queryString string) ([]Asset, error) { +func (s *SmartContract) QueryAssets(ctx contractapi.TransactionContextInterface, queryString string) ([]*Asset, error) { queryResults, err := s.getQueryResultForQueryString(ctx, queryString) if err != nil { @@ -158,7 +163,7 @@ func (s *SmartContract) QueryAssets(ctx contractapi.TransactionContextInterface, } // getQueryResultForQueryString executes the passed in query string. -func (s *SmartContract) getQueryResultForQueryString(ctx contractapi.TransactionContextInterface, queryString string) ([]Asset, error) { +func (s *SmartContract) getQueryResultForQueryString(ctx contractapi.TransactionContextInterface, queryString string) ([]*Asset, error) { resultsIterator, err := ctx.GetStub().GetPrivateDataQueryResult(assetCollection, queryString) if err != nil { @@ -166,22 +171,21 @@ func (s *SmartContract) getQueryResultForQueryString(ctx contractapi.Transaction } defer resultsIterator.Close() - results := []Asset{} + results := []*Asset{} for resultsIterator.HasNext() { response, err := resultsIterator.Next() if err != nil { return nil, err } + var asset *Asset - asset := new(Asset) - - err = json.Unmarshal(response.Value, asset) + err = json.Unmarshal(response.Value, &asset) if err != nil { return nil, fmt.Errorf("failed to unmarshal JSON: %v", err) } - results = append(results, *asset) + results = append(results, asset) } return results, nil } diff --git a/asset-transfer-private-data/chaincode-go/private_asset_transfer.go b/asset-transfer-private-data/chaincode-go/private_asset_transfer.go index 90434cdb..c3e51dd0 100644 --- a/asset-transfer-private-data/chaincode-go/private_asset_transfer.go +++ b/asset-transfer-private-data/chaincode-go/private_asset_transfer.go @@ -16,6 +16,9 @@ import ( "github.com/hyperledger/fabric-contract-api-go/contractapi" ) +const assetCollection = "assetCollection" +const transferAgreementObjectType = "transferAgreement" + // Asset describes main asset details that are visible to all organizations type Asset struct { Type string `json:"objectType"` //Type is used to distinguish the various types of objects in state database @@ -31,10 +34,13 @@ type AssetPrivateDetails struct { AppraisedValue int `json:"appraisedValue"` } -const assetCollection = "assetCollection" - -const transferAgreementObjectType = "transferAgreement" +// TransferAgreement describes the buyer agreement returned by ReadTransferAgreement +type TransferAgreement struct { + ID string `json:"assetID"` + BuyerID string `json:"buyerID"` +} +// SmartContract of this fabric sample type SmartContract struct { contractapi.Contract } @@ -49,10 +55,11 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface) return fmt.Errorf("error getting transient: %v", err) } - // Asset properties are private, therefore they get passed in transient field + // Asset properties are private, therefore they get passed in transient field, instead of func args transientAssetJSON, ok := transientMap["asset_properties"] if !ok { - return fmt.Errorf("asset not found in the transient map") + //log error to stdout + return fmt.Errorf("asset not found in the transient map input") } type assetTransientInput struct { @@ -90,7 +97,7 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface) if err != nil { return fmt.Errorf("failed to get asset: %v", err) } else if assetAsBytes != nil { - fmt.Println("this asset already exists: " + assetInput.ID) + fmt.Println("Asset already exists: " + assetInput.ID) return fmt.Errorf("this asset already exists: " + assetInput.ID) } @@ -100,6 +107,14 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface) return fmt.Errorf("failed to get verified OrgID: %v", err) } + // Verify that the client is submitting request to peer in their organization + // This is to ensure that a client from another org doesn't attempt to read or + // write private data from this peer. + err = verifyClientOrgMatchesPeerOrg(ctx) + if err != nil { + return fmt.Errorf("CreateAsset cannot be performed: Error %v", err) + } + // Make submitting client the owner asset := &Asset{ Type: assetInput.Type, @@ -110,13 +125,16 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface) } assetJSONasBytes, err := json.Marshal(asset) if err != nil { - return fmt.Errorf("failed to marshal into JSON: %v", err) + return fmt.Errorf("failed to marshal asset into JSON: %v", err) } // Save asset to private data collection + // Typical logger, logs to stdout/file in the fabric managed docker container, running this chaincode + // Look for container name like dev-peer0.org1.example.com-{chaincodename_version}-xyz + log.Printf("CreateAsset Put: collection %v, ID %v", assetCollection, assetInput.ID) err = ctx.GetStub().PutPrivateData(assetCollection, assetInput.ID, assetJSONasBytes) if err != nil { - return fmt.Errorf("failed to put asset into private data collection: %v", err) + return fmt.Errorf("failed to put asset into private data collecton: %v", err) } // Save asset details to collection visible to owning organization @@ -130,10 +148,14 @@ func (s *SmartContract) CreateAsset(ctx contractapi.TransactionContextInterface) return fmt.Errorf("failed to marshal into JSON: %v", err) } - // Get collection name for this organization. Needs to be read by a member of the organization. - orgCollection, err := getCollectionName(ctx, true) + // Get collection name for this organization. + orgCollection, err := getCollectionName(ctx) + if err != nil { + return fmt.Errorf("failed to infer private collection name for the org: %v", err) + } // Put asset appraised value into owners org specific private data collection + log.Printf("Put: collection %v, ID %v", orgCollection, assetInput.ID) err = ctx.GetStub().PutPrivateData(orgCollection, assetInput.ID, assetPrivateDetailsAsBytes) if err != nil { return fmt.Errorf("failed to put asset private details: %v", err) @@ -180,9 +202,19 @@ func (s *SmartContract) AgreeToTransfer(ctx contractapi.TransactionContextInterf return fmt.Errorf("appraisedValue field must be a positive integer") } - // Get collection name for this organization. Needs to be read by a member of the organization. - orgCollection, err := getCollectionName(ctx, true) + // Verify that the client is submitting request to peer in their organization + err = verifyClientOrgMatchesPeerOrg(ctx) + if err != nil { + return fmt.Errorf("AgreeToTransfer cannot be performed: Error %v", err) + } + // Get collection name for this organization. Needs to be read by a member of the organization. + orgCollection, err := getCollectionName(ctx) + if err != nil { + return fmt.Errorf("failed to infer private collection name for the org: %v", err) + } + + log.Printf("AgreeToTransfer Put: collection %v, ID %v", orgCollection, valueJSON.ID) // Put agreed value in the org specifc private data collection err = ctx.GetStub().PutPrivateData(orgCollection, valueJSON.ID, valueJSONasBytes) if err != nil { @@ -197,6 +229,7 @@ func (s *SmartContract) AgreeToTransfer(ctx contractapi.TransactionContextInterf return fmt.Errorf("failed to create composite key: %v", err) } + log.Printf("AgreeToTransfer Put: collection %v, ID %v, Key %v", assetCollection, valueJSON.ID, transferAgreeKey) err = ctx.GetStub().PutPrivateData(assetCollection, transferAgreeKey, []byte(clientID)) if err != nil { return fmt.Errorf("failed to put asset bid: %v", err) @@ -236,34 +269,54 @@ func (s *SmartContract) TransferAsset(ctx contractapi.TransactionContextInterfac if len(assetTransferInput.BuyerMSP) == 0 { return fmt.Errorf("buyerMSP field must be a non-empty string") } - + log.Printf("TransferAsset: verify asset exists ID %v", assetTransferInput.ID) // Read asset from the private data collection asset, err := s.ReadAsset(ctx, assetTransferInput.ID) if err != nil { return fmt.Errorf("failed to get asset: %v", err) } + // Verify that the client is submitting request to peer in their organization + err = verifyClientOrgMatchesPeerOrg(ctx) + if err != nil { + return fmt.Errorf("TransferAsset cannot be performed: Error %v", err) + } + // Verify transfer details and transfer owner err = s.verifyAgreement(ctx, assetTransferInput.ID, asset.Owner, assetTransferInput.BuyerMSP) if err != nil { return fmt.Errorf("failed transfer verification: %v", err) } - buyerID, err := s.ReadTransferAgreement(ctx, assetTransferInput.ID) + transferAgreement, err := s.ReadTransferAgreement(ctx, assetTransferInput.ID) + if err != nil { + return fmt.Errorf("failed ReadTransferAgreement to find buyerID: %v", err) + } + if transferAgreement.BuyerID == "" { + return fmt.Errorf("BuyerID not found in TransferAgreement for %v", assetTransferInput.ID) + } // Transfer asset in private data collection to new owner - asset.Owner = buyerID + asset.Owner = transferAgreement.BuyerID - assetJSONasBytes, _ := json.Marshal(asset) + assetJSONasBytes, err := json.Marshal(asset) + if err != nil { + return fmt.Errorf("failed marshalling asset %v: %v", assetTransferInput.ID, err) + } + + log.Printf("TransferAsset Put: collection %v, ID %v", assetCollection, assetTransferInput.ID) err = ctx.GetStub().PutPrivateData(assetCollection, assetTransferInput.ID, assetJSONasBytes) //rewrite the asset if err != nil { return err } // Get collection name for this organization - ownersCollection, err := getCollectionName(ctx, false) + ownersCollection, err := getCollectionName(ctx) + if err != nil { + return fmt.Errorf("failed to infer private collection name for the org: %v", err) + } - // Delete the marble appraised value from this organiztion's private data collection + // Delete the asset appraised value from this organization's private data collection err = ctx.GetStub().DelPrivateData(ownersCollection, assetTransferInput.ID) if err != nil { return err @@ -304,8 +357,10 @@ func (s *SmartContract) verifyAgreement(ctx contractapi.TransactionContextInterf // Check 2: verify that the buyer has agreed to the appraised value // Get collection names - - collectionOwner, err := getCollectionName(ctx, false) // get buyers collection + collectionOwner, err := getCollectionName(ctx) // get owner collection from caller identity + if err != nil { + return fmt.Errorf("failed to infer private collection name for the org: %v", err) + } collectionBuyer := buyerMSP + "PrivateCollection" // get buyers collection @@ -324,7 +379,7 @@ func (s *SmartContract) verifyAgreement(ctx contractapi.TransactionContextInterf return fmt.Errorf("failed to get hash of appraised value from buyer collection %v: %v", collectionBuyer, err) } if buyerAppraisedValueHash == nil { - return fmt.Errorf("hash of appraised value for %v does not exist in collection %v", assetID, collectionBuyer) + return fmt.Errorf("hash of appraised value for %v does not exist in collection %v. AgreeToTransfer must be called by the buyer first", assetID, collectionBuyer) } // Verify that the two hashes match @@ -363,12 +418,19 @@ func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface) return fmt.Errorf("assetID field must be a non-empty string") } + // Verify that the client is submitting request to peer in their organization + err = verifyClientOrgMatchesPeerOrg(ctx) + if err != nil { + return fmt.Errorf("DeleteAsset cannot be performed: Error %v", err) + } + + log.Printf("Deleting Asset: %v", assetDeleteInput.ID) valAsbytes, err := ctx.GetStub().GetPrivateData(assetCollection, assetDeleteInput.ID) //get the asset from chaincode state if err != nil { return fmt.Errorf("failed to read asset: %v", err) } if valAsbytes == nil { - return fmt.Errorf("asset private details does not exist: %v", assetDeleteInput.ID) + return fmt.Errorf("asset not found: %v", assetDeleteInput.ID) } var assetToDelete Asset @@ -384,8 +446,10 @@ func (s *SmartContract) DeleteAsset(ctx contractapi.TransactionContextInterface) } // Finally, delete private details of asset - - ownerCollection, err := getCollectionName(ctx, true) // Get owners collection. Needs to be read by a member of the organization. + ownerCollection, err := getCollectionName(ctx) // Get owners collection + if err != nil { + return fmt.Errorf("failed to infer private collection name for the org: %v", err) + } err = ctx.GetStub().DelPrivateData(ownerCollection, assetDeleteInput.ID) // Delete the asset if err != nil { @@ -406,7 +470,7 @@ func (s *SmartContract) DeleteTranferAgreement(ctx contractapi.TransactionContex } // Asset properties are private, therefore they get passed in transient field - transientDeleteJSON, ok := transientMap["agree_delete"] + transientDeleteJSON, ok := transientMap["agreement_delete"] if !ok { return fmt.Errorf("asset to delete not found in the transient map") } @@ -425,23 +489,38 @@ func (s *SmartContract) DeleteTranferAgreement(ctx contractapi.TransactionContex return fmt.Errorf("ID field must be a non-empty string") } + // Verify that the client is submitting request to peer in their organization + err = verifyClientOrgMatchesPeerOrg(ctx) + if err != nil { + return fmt.Errorf("DeleteTranferAgreement cannot be performed: Error %v", err) + } // Delete private details of agreement + orgCollection, err := getCollectionName(ctx) // Get proposers collection. + if err != nil { + return fmt.Errorf("failed to infer private collection name for the org: %v", err) + } + tranferAgreeKey, err := ctx.GetStub().CreateCompositeKey(transferAgreementObjectType, []string{assetDeleteInput. + ID}) // Create composite key + if err != nil { + return fmt.Errorf("failed to create composite key: %v", err) + } - orgCollection, err := getCollectionName(ctx, true) // Get proposers collection. Needs to be read by a member of the organization. + valAsbytes, err := ctx.GetStub().GetPrivateData(assetCollection, tranferAgreeKey) //get the transfer_agreement + if err != nil { + return fmt.Errorf("failed to read transfer_agreement: %v", err) + } + if valAsbytes == nil { + return fmt.Errorf("asset's transfer_agreement does not exist: %v", assetDeleteInput.ID) + } + log.Printf("Deleting TranferAgreement: %v", assetDeleteInput.ID) err = ctx.GetStub().DelPrivateData(orgCollection, assetDeleteInput.ID) // Delete the asset if err != nil { return err } // Delete transfer agreement record - - tranferAgreeKey, err := ctx.GetStub().CreateCompositeKey(transferAgreementObjectType, []string{assetDeleteInput.ID}) // Create composite key - if err != nil { - return fmt.Errorf("failed to create composite key: %v", err) - } - - err = ctx.GetStub().DelState(tranferAgreeKey) // remove agreement from state + err = ctx.GetStub().DelPrivateData(assetCollection, tranferAgreeKey) // remove agreement from state if err != nil { return err } @@ -451,22 +530,12 @@ func (s *SmartContract) DeleteTranferAgreement(ctx contractapi.TransactionContex } // getCollectionName is an internal helper function to get collection of submitting client identity. -// The collection name can optionally be verified against the peer org ID, to ensure that a -// client from another org doesn't attempt to read or write private data from this peer. -func getCollectionName(ctx contractapi.TransactionContextInterface, verifyOrg bool) (string, error) { +func getCollectionName(ctx contractapi.TransactionContextInterface) (string, error) { // Get the MSP ID of submitting client identity clientMSPID, err := ctx.GetClientIdentity().GetMSPID() if err != nil { - return "", fmt.Errorf("failed to get verified OrgID: %v", err) - } - - // Verify that the client is submitting request to peer in their organization - if verifyOrg { - err = verifyClientOrgMatchesPeerOrg(clientMSPID) - if err != nil { - return "", err - } + return "", fmt.Errorf("failed to get verified MSPID: %v", err) } // Create the collection name @@ -476,10 +545,14 @@ func getCollectionName(ctx contractapi.TransactionContextInterface, verifyOrg bo } // verifyClientOrgMatchesPeerOrg is an internal function used verify client org id and matches peer org id. -func verifyClientOrgMatchesPeerOrg(clientMSPID string) error { +func verifyClientOrgMatchesPeerOrg(ctx contractapi.TransactionContextInterface) error { + clientMSPID, err := ctx.GetClientIdentity().GetMSPID() + if err != nil { + return fmt.Errorf("failed getting the client's MSPID: %v", err) + } peerMSPID, err := shim.GetMSPID() if err != nil { - return fmt.Errorf("failed getting peer's orgID: %v", err) + return fmt.Errorf("failed getting the peer's MSPID: %v", err) } if clientMSPID != peerMSPID { @@ -494,11 +567,11 @@ func main() { chaincode, err := contractapi.NewChaincode(new(SmartContract)) if err != nil { - log.Panicf("error creating private mables chaincode: %v", err) + log.Panicf("error creating the chaincode: %v", err) return } if err := chaincode.Start(); err != nil { - log.Panicf("error starting private mables chaincode: %v", err) + log.Panicf("error starting the chaincode: %v", err) } } diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index 759d36a3..bd9c3d9c 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -99,11 +99,15 @@ jobs: Ledger-Chaincode-Go: DIRECTORY: asset-transfer-ledger-queries LANGUAGE: go - TYPE: chaincode - Private-Chaincode-Go: + TYPE: chaincode + PrivateData-Chaincode-Go: DIRECTORY: asset-transfer-private-data LANGUAGE: go TYPE: chaincode + PrivateData-Application-Javascript: + DIRECTORY: asset-transfer-private-data + LANGUAGE: javascript + TYPE: application Secured-Chaincode-Go: DIRECTORY: asset-transfer-secured-agreement LANGUAGE: go diff --git a/ci/scripts/run-test-network-private.sh b/ci/scripts/run-test-network-private.sh index c2578c40..c38cf978 100755 --- a/ci/scripts/run-test-network-private.sh +++ b/ci/scripts/run-test-network-private.sh @@ -14,8 +14,16 @@ function print() { print "Creating network" ./network.sh up createChannel -ca -s couchdb -i "${FABRIC_VERSION}" -print "Deploying ${CHAINCODE_NAME} chaincode" -./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}" +print "Deploying private-data ${CHAINCODE_NAME} chaincode" +./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccv 1 -ccs 1 -ccl "${CHAINCODE_LANGUAGE}" -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-go/collections_config.json + +# Run Javascript application +print "Initializing Javascript application" +pushd ../asset-transfer-private-data/application-javascript +npm install +print "Executing app.js" +node app.js +popd print "Stopping network" ./network.sh down