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