This commit is contained in:
mahoney@uk.ibm.com 2020-11-22 13:08:24 +00:00
commit 3ad52d5ec3
120 changed files with 10365 additions and 1034 deletions

View file

@ -25,9 +25,9 @@ transfer an asset in a more realistic transfer scenario.
| **Smart Contract** | **Description** | **Tutorial** | **Smart contract languages** | **Application languages** | | **Smart Contract** | **Description** | **Tutorial** | **Smart contract languages** | **Application languages** |
| -----------|------------------------------|----------|---------|---------| | -----------|------------------------------|----------|---------|---------|
| [Basic](asset-transfer-basic) | The Basic sample smart contract that allows you to create and transfer an asset by putting data on the ledger and retrieving it. This sample is recommended for new Fabric users. | [Writing your first application](https://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html) | Go, JavaScript, TypeScript, Java | Go, JavaScript, Java | | [Basic](asset-transfer-basic) | The Basic sample smart contract that allows you to create and transfer an asset by putting data on the ledger and retrieving it. This sample is recommended for new Fabric users. | [Writing your first application](https://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html) | Go, JavaScript, TypeScript, Java | Go, JavaScript, TypeScript, Java |
| [Ledger queries](asset-transfer-ledger-queries) | The ledger queries sample demonstrates range queries and transaction updates using range queries (applicable for both LevelDB and CouchDB state databases), and how to deploy an index with your chaincode to support JSON queries (applicable for CouchDB state database only). | [Using CouchDB](https://hyperledger-fabric.readthedocs.io/en/latest/couchdb_tutorial.html) | Go, JavaScript | Java, JavaScript | | [Ledger queries](asset-transfer-ledger-queries) | The ledger queries sample demonstrates range queries and transaction updates using range queries (applicable for both LevelDB and CouchDB state databases), and how to deploy an index with your chaincode to support JSON queries (applicable for CouchDB state database only). | [Using CouchDB](https://hyperledger-fabric.readthedocs.io/en/latest/couchdb_tutorial.html) | Go, JavaScript | Java, JavaScript |
| [Private data](asset-transfer-private-data) | This sample demonstrates the use of private data collections, how to manage private data collections with the chaincode lifecycle, and how the private data hash can be used to verify private data on the ledger. It also demonstrates how to control asset updates and transfers using client-based ownership and access control. | [Using Private Data](https://github.com/hyperledger/fabric-samples/tree/master/asset-transfer-private-data/chaincode-go) | Go | JavaScript | | [Private data](asset-transfer-private-data) | This sample demonstrates the use of private data collections, how to manage private data collections with the chaincode lifecycle, and how the private data hash can be used to verify private data on the ledger. It also demonstrates how to control asset updates and transfers using client-based ownership and access control. | [Using Private Data](https://hyperledger-fabric.readthedocs.io/en/latest/private_data_tutorial.html) | Go | JavaScript |
| [State-Based Endorsement](asset-transfer-sbe) | This sample demonstrates how to override the chaincode-level endorsement policy to set endorsement policies at the key-level (data/asset level). | [Using State-based endorsement](https://github.com/hyperledger/fabric-samples/tree/master/asset-transfer-sbe) | Java, TypeScript | JavaScript | | [State-Based Endorsement](asset-transfer-sbe) | This sample demonstrates how to override the chaincode-level endorsement policy to set endorsement policies at the key-level (data/asset level). | [Using State-based endorsement](https://github.com/hyperledger/fabric-samples/tree/master/asset-transfer-sbe) | Java, TypeScript | JavaScript |
| [Secured agreement](asset-transfer-secured-agreement) | Smart contract that uses implicit private data collections, state-based endorsement, and organization-based ownership and access control to keep data private and securely transfer an asset with the consent of both the current owner and buyer. | [Secured asset transfer](https://hyperledger-fabric.readthedocs.io/en/latest/secured_asset_transfer/secured_private_asset_transfer_tutorial.html) | Go | JavaScript | | [Secured agreement](asset-transfer-secured-agreement) | Smart contract that uses implicit private data collections, state-based endorsement, and organization-based ownership and access control to keep data private and securely transfer an asset with the consent of both the current owner and buyer. | [Secured asset transfer](https://hyperledger-fabric.readthedocs.io/en/latest/secured_asset_transfer/secured_private_asset_transfer_tutorial.html) | Go | JavaScript |
@ -41,7 +41,9 @@ Additional samples demonstrate various Fabric use cases and application patterns
| -------------|------------------------------|------------------| | -------------|------------------------------|------------------|
| [Commercial paper](commercial-paper) | Explore a use case and detailed application development tutorial in which two organizations use a blockchain network to trade commercial paper. | [Commercial paper tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/tutorial/commercial_paper.html) | | [Commercial paper](commercial-paper) | Explore a use case and detailed application development tutorial in which two organizations use a blockchain network to trade commercial paper. | [Commercial paper tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/tutorial/commercial_paper.html) |
| [Off chain data](off_chain_data) | Learn how to use the Peer channel-based event services to build an off-chain database for reporting and analytics. | [Peer channel-based event services](https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html) | | [Off chain data](off_chain_data) | Learn how to use the Peer channel-based event services to build an off-chain database for reporting and analytics. | [Peer channel-based event services](https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html) |
| [High throughput](high-throughput) | Learn how you can design your smart contract to avoid transaction collisions in high volume environments. | | | [Token ERC-20](token-erc-20) | Smart contract demonstrating how to create and transfer fungible tokens using an account-based model. | [README](token-erc-20/README.md) |
| [Token UTXO](token-utxo) | Smart contract demonstrating how to create and transfer fungible tokens using a UTXO (unspent transaction output) model. | [README](token-utxo/README.md) |
| [High throughput](high-throughput) | Learn how you can design your smart contract to avoid transaction collisions in high volume environments. | [README](high-throughput/README.md) |
| [Chaincode](chaincode) | A set of other sample smart contracts, many of which were used in tutorials prior to the asset transfer sample series. | | | [Chaincode](chaincode) | A set of other sample smart contracts, many of which were used in tutorials prior to the asset transfer sample series. | |
| [Interest rate swaps](interest_rate_swaps) | **Deprecated in favor of state based endorsement asset transfer sample** | | | [Interest rate swaps](interest_rate_swaps) | **Deprecated in favor of state based endorsement asset transfer sample** | |
| [Fabcar](fabcar) | **Deprecated in favor of basic asset transfer sample** | | | [Fabcar](fabcar) | **Deprecated in favor of basic asset transfer sample** | |

View file

@ -63,12 +63,14 @@ func main() {
contract := network.GetContract("basic") contract := network.GetContract("basic")
log.Println("--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger")
result, err := contract.SubmitTransaction("InitLedger") result, err := contract.SubmitTransaction("InitLedger")
if err != nil { if err != nil {
log.Fatalf("failed to evaluate transaction: %v", err) log.Fatalf("Failed to Submit transaction: %v", err)
} }
log.Println(string(result)) log.Println(string(result))
log.Println("--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger")
result, err = contract.EvaluateTransaction("GetAllAssets") result, err = contract.EvaluateTransaction("GetAllAssets")
if err != nil { if err != nil {
log.Fatalf("Failed to evaluate transaction: %v", err) log.Fatalf("Failed to evaluate transaction: %v", err)
@ -78,28 +80,28 @@ func main() {
log.Println("--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments") log.Println("--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments")
result, err = contract.SubmitTransaction("CreateAsset", "asset13", "yellow", "5", "Tom", "1300") result, err = contract.SubmitTransaction("CreateAsset", "asset13", "yellow", "5", "Tom", "1300")
if err != nil { if err != nil {
log.Fatalf("Failed to evaluate transaction: %v", err) log.Fatalf("Failed to Submit transaction: %v", err)
} }
log.Println(string(result)) log.Println(string(result))
log.Println("--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID") log.Println("--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID")
result, err = contract.EvaluateTransaction("ReadAsset", "asset13") result, err = contract.EvaluateTransaction("ReadAsset", "asset13")
if err != nil { if err != nil {
log.Fatalf("failed to evaluate transaction: %v\n", err) log.Fatalf("Failed to evaluate transaction: %v\n", err)
} }
log.Println(string(result)) log.Println(string(result))
log.Println("--> Evaluate Transaction: AssetExists, function returns 'true' if an asset with given assetID exist") log.Println("--> Evaluate Transaction: AssetExists, function returns 'true' if an asset with given assetID exist")
result, err = contract.EvaluateTransaction("AssetExists", "asset1") result, err = contract.EvaluateTransaction("AssetExists", "asset1")
if err != nil { if err != nil {
log.Fatalf("failed to evaluate transaction: %v\n", err) log.Fatalf("Failed to evaluate transaction: %v\n", err)
} }
log.Println(string(result)) log.Println(string(result))
log.Println("--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom") log.Println("--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom")
_, err = contract.SubmitTransaction("TransferAsset", "asset1", "Tom") _, err = contract.SubmitTransaction("TransferAsset", "asset1", "Tom")
if err != nil { if err != nil {
log.Fatalf("Failed to submit transaction: %v", err) log.Fatalf("Failed to Submit transaction: %v", err)
} }
log.Println("--> Evaluate Transaction: ReadAsset, function returns 'asset1' attributes") log.Println("--> Evaluate Transaction: ReadAsset, function returns 'asset1' attributes")

View file

@ -2,4 +2,4 @@ module asset-transfer-basic
go 1.14 go 1.14
require github.com/hyperledger/fabric-sdk-go v1.0.0-beta2 require github.com/hyperledger/fabric-sdk-go v1.0.0-beta3.0.20201006151309-9c426dcc5096

View file

@ -1,22 +1,46 @@
bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg= github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004 h1:lkAMpLVBDaj17e85keuznYcH5rqI438v41pKcBl4ZxQ=
github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA= github.com/cloudflare/cfssl v0.0.0-20180223231731-4e2dcbde5004/go.mod h1:yMWuSON2oQp+43nFtAV/uvKQIFpSPerB57DCt9t8sSA=
github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiKw=
github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo=
github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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.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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0= github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA= github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= github.com/gogo/protobuf v1.1.1 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
@ -24,104 +48,230 @@ github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfU
github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= github.com/golang/mock v1.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk=
github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
github.com/golang/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= 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.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/certificate-transparency-go v0.0.0-20180222191210-5ab67e519c93 h1:qdfmdGwtm13OVx+AxguOWUTbgmXGn2TbdUHipo3chMg= github.com/google/certificate-transparency-go v0.0.0-20180222191210-5ab67e519c93 h1:qdfmdGwtm13OVx+AxguOWUTbgmXGn2TbdUHipo3chMg=
github.com/google/certificate-transparency-go v0.0.0-20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg= github.com/google/certificate-transparency-go v0.0.0-20180222191210-5ab67e519c93/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= 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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce h1:xdsDDbiBDQTKASoGEZ+pEmF1OnWuu8AQ9I8iNbHNeno=
github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w= github.com/hashicorp/hcl v0.0.0-20180404174102-ef8a98b0bbce/go.mod h1:oZtUIOe8dh44I2q6ScRibXws4Ajl+d+nod3AaR9vL5w=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hyperledger/fabric-config v0.0.5 h1:khRkm8U9Ghdg8VmZfptgzCFlCzrka8bPfUkM+/j6Zlg=
github.com/hyperledger/fabric-config v0.0.5/go.mod h1:YpITBI/+ZayA3XWY5lF302K7PAsFYjEEPM/zr3hegA8=
github.com/hyperledger/fabric-lib-go v1.0.0 h1:UL1w7c9LvHZUSkIvHTDGklxFv2kTeva1QI2emOVc324= github.com/hyperledger/fabric-lib-go v1.0.0 h1:UL1w7c9LvHZUSkIvHTDGklxFv2kTeva1QI2emOVc324=
github.com/hyperledger/fabric-lib-go v1.0.0/go.mod h1:H362nMlunurmHwkYqR5uHL2UDWbQdbfz74n8kbCFsqc= github.com/hyperledger/fabric-lib-go v1.0.0/go.mod h1:H362nMlunurmHwkYqR5uHL2UDWbQdbfz74n8kbCFsqc=
github.com/hyperledger/fabric-protos-go v0.0.0-20191121202242-f5500d5e3e85 h1:bNgEcCg5NVRWs/T+VUEfhgh5Olx/N4VB+0+ybW+oSuA= github.com/hyperledger/fabric-protos-go v0.0.0-20191121202242-f5500d5e3e85 h1:bNgEcCg5NVRWs/T+VUEfhgh5Olx/N4VB+0+ybW+oSuA=
github.com/hyperledger/fabric-protos-go v0.0.0-20191121202242-f5500d5e3e85/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= github.com/hyperledger/fabric-protos-go v0.0.0-20191121202242-f5500d5e3e85/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0=
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/hyperledger/fabric-sdk-go v1.0.0-beta1.0.20200526155846-219a09aadc0f h1:eAkJx0+8PBbfP6xZxVRD2agk9W7oDbqllxO+ERgnKJk= github.com/hyperledger/fabric-sdk-go v1.0.0-beta1.0.20200526155846-219a09aadc0f h1:eAkJx0+8PBbfP6xZxVRD2agk9W7oDbqllxO+ERgnKJk=
github.com/hyperledger/fabric-sdk-go v1.0.0-beta1.0.20200526155846-219a09aadc0f/go.mod h1:/s224b8NLvOJOCIqBvWd9O6u7GE33iuIOT6OfcTE1OE= github.com/hyperledger/fabric-sdk-go v1.0.0-beta1.0.20200526155846-219a09aadc0f/go.mod h1:/s224b8NLvOJOCIqBvWd9O6u7GE33iuIOT6OfcTE1OE=
github.com/hyperledger/fabric-sdk-go v1.0.0-beta2 h1:FBYygns0Qga+mQ4PXycyTU5m4N9KAZM+Ttf7agiV7M8= github.com/hyperledger/fabric-sdk-go v1.0.0-beta2 h1:FBYygns0Qga+mQ4PXycyTU5m4N9KAZM+Ttf7agiV7M8=
github.com/hyperledger/fabric-sdk-go v1.0.0-beta2/go.mod h1:/s224b8NLvOJOCIqBvWd9O6u7GE33iuIOT6OfcTE1OE= github.com/hyperledger/fabric-sdk-go v1.0.0-beta2/go.mod h1:/s224b8NLvOJOCIqBvWd9O6u7GE33iuIOT6OfcTE1OE=
github.com/hyperledger/fabric-sdk-go v1.0.0-beta3.0.20201006151309-9c426dcc5096 h1:veml7LmfavSHqF8w8z/PGGlfdXvmx5SstQIH6Nyy87c=
github.com/hyperledger/fabric-sdk-go v1.0.0-beta3.0.20201006151309-9c426dcc5096/go.mod h1:qWE9Syfg1KbwNjtILk70bJLilnmCvllIYFCSY/pa1RU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515 h1:T+h1c/A9Gawja4Y9mFVWj2vyii2bbUNDw3kt9VxK2EY=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c=
github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.7.6 h1:U+1DqNen04MdEPgFiIwdOUiqZ8qPa37xgogX/sd3+54= github.com/magiconair/properties v1.7.6 h1:U+1DqNen04MdEPgFiIwdOUiqZ8qPa37xgogX/sd3+54=
github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.7.6/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU= github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/pkcs11 v0.0.0-20190329070431-55f3fac3af27/go.mod h1:WCBAbTOdfhHhz7YXujeZMF7owC4tPb1naKFsgfUISjo= github.com/miekg/pkcs11 v0.0.0-20190329070431-55f3fac3af27/go.mod h1:WCBAbTOdfhHhz7YXujeZMF7owC4tPb1naKFsgfUISjo=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238 h1:+MZW2uvHgN8kYvksEN3f7eFL2wpzk0GxmlFsMybWc7E= github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238 h1:+MZW2uvHgN8kYvksEN3f7eFL2wpzk0GxmlFsMybWc7E=
github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/mitchellh/mapstructure v0.0.0-20180220230111-00c29f56e238/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y=
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.4.2/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.1.0 h1:cmiOvKzEunMsAxyhXSzpL5Q1CRKpVv0KQsnAIcSEVYM= github.com/pelletier/go-toml v1.1.0 h1:cmiOvKzEunMsAxyhXSzpL5Q1CRKpVv0KQsnAIcSEVYM=
github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= github.com/pelletier/go-toml v1.1.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8= github.com/prometheus/client_golang v0.8.0 h1:1921Yw9Gc3iSc4VQh3PIoOqgPCZS7G/4xQNVUp8Mda8=
github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v0.8.0/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910 h1:idejC8f05m9MGOsuEi1ATq9shN03HrxNkD/luQvxCv8=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 h1:osmNoEW2SCW3L7EX0km2LYM8HKpNWRiouxjE3XHkyGc= github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1 h1:osmNoEW2SCW3L7EX0km2LYM8HKpNWRiouxjE3XHkyGc=
github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= github.com/prometheus/common v0.0.0-20180518154759-7600349dcfe1/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/procfs v0.0.0-20180705121852-ae68e2d4c00f h1:c9M4CCa6g8WURSsbrl3lb/w/G1Z5xZpYvhhjdcVDOkE= github.com/prometheus/procfs v0.0.0-20180705121852-ae68e2d4c00f h1:c9M4CCa6g8WURSsbrl3lb/w/G1Z5xZpYvhhjdcVDOkE=
github.com/prometheus/procfs v0.0.0-20180705121852-ae68e2d4c00f/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20180705121852-ae68e2d4c00f/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/spf13/afero v1.1.0 h1:bopulORc2JeYaxfHLvJa5NzxviA9PoWhpiiJkru7Ji4= github.com/spf13/afero v1.1.0 h1:bopulORc2JeYaxfHLvJa5NzxviA9PoWhpiiJkru7Ji4=
github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= github.com/spf13/afero v1.1.0/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
github.com/spf13/afero v1.3.1 h1:GPTpEAuNr98px18yNQ66JllNil98wfRZ/5Ukny8FeQA=
github.com/spf13/afero v1.3.1/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg= github.com/spf13/cast v1.2.0 h1:HHl1DSRbEQN2i8tJmtS6ViPyHx35+p51amrdsiTCrkg=
github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg= github.com/spf13/cast v1.2.0/go.mod h1:r2rcYCSwa1IExKTDiTfzaxqT2FNHs8hODu4LnUfgKEg=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec h1:2ZXvIUGghLpdTVHR1UfvfrzoVlZaE/yOWC5LueIHZig= github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec h1:2ZXvIUGghLpdTVHR1UfvfrzoVlZaE/yOWC5LueIHZig=
github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= github.com/spf13/jwalterweatherman v0.0.0-20180109140146-7c0cea34c8ec/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4= github.com/spf13/pflag v1.0.1 h1:aCvUg6QPl3ibpQUxyLkrEkCHtPqYJL4x9AuhqVqFis4=
github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.0.2 h1:Ncr3ZIuJn322w2k1qmzXDnkLAdQMlJqBa9kfAH+irso= github.com/spf13/viper v1.0.2 h1:Ncr3ZIuJn322w2k1qmzXDnkLAdQMlJqBa9kfAH+irso=
github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM= github.com/spf13/viper v1.0.2/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/spf13/viper v1.1.1 h1:/8JBRFO4eoHu1TmpsLgNBq1CQgRUg4GolYlEFieqJgo=
github.com/spf13/viper v1.1.1/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 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/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU=
github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e h1:mvOa4+/DXStR4ZXOks/UsjeFdn5O5JpLUtzqk9U8xXw=
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8=
github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb h1:vxqkjztXSaPVDc8FQCdHTaejm2x747f6yPbnu1h2xkg=
github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= 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-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-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
golang.org/x/lint v0.0.0-20190930215403-16217165b5de/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-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-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= golang.org/x/net v0.0.0-20190311183353-d8887717615a h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= 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-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= 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-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-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/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-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/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=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 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/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-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-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-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= 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/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-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d h1:XB2jc5XQ9uhizGTS2vWcN01bc4dI6z3C4KY5MQm8SS8= google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d h1:XB2jc5XQ9uhizGTS2vWcN01bc4dI6z3C4KY5MQm8SS8=
google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= google.golang.org/genproto v0.0.0-20190327125643-d831d65fe17d/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= 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 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= 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.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 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-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= gopkg.in/yaml.v2 v2.2.1 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.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-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

@ -128,8 +128,16 @@ async function main() {
// This will be sent to both peers and if both peers endorse the transaction, the endorsed proposal will be sent // This will be sent to both peers and if both peers endorse the transaction, the endorsed proposal will be sent
// to the orderer to be committed by each of the peer's to the channel ledger. // to the orderer to be committed by each of the peer's to the channel ledger.
console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments'); console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments');
await contract.submitTransaction('CreateAsset', 'asset13', 'yellow', '5', 'Tom', '1300'); result = await contract.submitTransaction('CreateAsset', 'asset13', 'yellow', '5', 'Tom', '1300');
console.log('*** Result: committed'); // The "submitTransaction" returns the value generated by the chaincode. Notice how we normally do not
// look at this value as the chaincodes are not returning a value. So for demonstration purposes we
// have the javascript version of the chaincode return a value on the function 'CreateAsset'.
// This value will be the same as the 'ReadAsset' results for the newly created asset.
// The other chaincode versions could be updated to also return a value.
// Having the chaincode return a value after after doing a create or update could avoid the application
// from making an "evaluateTransaction" call to get information on the asset added by the chaincode
// during the create or update.
console.log(`*** Result committed: ${prettyJSONString(result.toString())}`);
console.log('\n--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID'); console.log('\n--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID');
result = await contract.evaluateTransaction('ReadAsset', 'asset13'); result = await contract.evaluateTransaction('ReadAsset', 'asset13');

View file

@ -0,0 +1,15 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Dependency directories
node_modules/
jspm_packages/
# Compiled TypeScript files
dist

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,50 @@
{
"name": "asset-transfer-basic",
"version": "1.0.0",
"description": "Asset Transfer Basic contract implemented in TypeScript",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"engines": {
"node": ">=12",
"npm": ">=5"
},
"scripts": {
"lint": "tslint -c tslint.json 'src/**/*.ts'",
"pretest": "npm run lint",
"start": "npm run build && node dist/app.js",
"build": "tsc",
"build:watch": "tsc -w",
"prepublishOnly": "npm run build"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-ca-client": "^2.2.0",
"fabric-network": "^2.2.0"
},
"devDependencies": {
"tslint": "^5.11.0",
"typescript": "^3.1.6"
},
"nyc": {
"extension": [
".ts",
".tsx"
],
"exclude": [
"coverage/**",
"dist/**"
],
"reporter": [
"text-summary",
"html"
],
"all": true,
"check-coverage": true,
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100
}
}

View file

@ -0,0 +1,171 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { Gateway, GatewayOptions } from 'fabric-network';
import * as path from 'path';
import { buildCCPOrg1, buildWallet, prettyJSONString } from './utils//AppUtil';
import { buildCAClient, enrollAdmin, registerAndEnrollUser } from './utils/CAUtil';
const channelName = 'mychannel';
const chaincodeName = 'basic';
const mspOrg1 = 'Org1MSP';
const walletPath = path.join(__dirname, 'wallet');
const org1UserId = 'appUser';
// pre-requisites:
// - fabric-sample two organization test-network setup with two peers, ordering service,
// and 2 certificate authorities
// ===> from directory /fabric-samples/test-network
// ./network.sh up createChannel -ca
// - Use any of the asset-transfer-basic chaincodes deployed on the channel "mychannel"
// with the chaincode name of "basic". The following deploy command will package,
// install, approve, and commit the javascript chaincode, all the actions it takes
// to deploy a chaincode to a channel.
// ===> from directory /fabric-samples/test-network
// ./network.sh deployCC -ccn basic -ccl javascript
// - Be sure that node.js is installed
// ===> from directory /fabric-samples/asset-transfer-basic/application-typescript
// node -v
// - npm installed code dependencies
// ===> from directory /fabric-samples/asset-transfer-basic/application-typescript
// npm install
// - to run this test application
// ===> from directory /fabric-samples/asset-transfer-basic/application-typescript
// npm start
// NOTE: If you see kind an error like these:
/*
2020-08-07T20:23:17.590Z - error: [DiscoveryService]: send[mychannel] - Channel:mychannel received discovery error:access denied
******** FAILED to run the application: Error: DiscoveryService: mychannel error: access denied
OR
Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]]
******** FAILED to run the application: Error: Identity not found in wallet: appUser
*/
// Delete the /fabric-samples/asset-transfer-basic/application-typescript/wallet directory
// and retry this application.
//
// The certificate authority must have been restarted and the saved certificates for the
// admin and application user are not valid. Deleting the wallet store will force these to be reset
// with the new certificate authority.
//
/**
* A test application to show basic queries operations with any of the asset-transfer-basic chaincodes
* -- How to submit a transaction
* -- How to query and check the results
*
* To see the SDK workings, try setting the logging to show on the console before running
* export HFC_LOGGING='{"debug":"console"}'
*/
async function main() {
try {
// build an in memory object with the network configuration (also known as a connection profile)
const ccp = buildCCPOrg1();
// build an instance of the fabric ca services client based on
// the information in the network configuration
const caClient = buildCAClient(ccp, 'ca.org1.example.com');
// setup the wallet to hold the credentials of the application user
const wallet = await buildWallet(walletPath);
// in a real application this would be done on an administrative flow, and only once
await enrollAdmin(caClient, wallet, mspOrg1);
// in a real application this would be done only when a new user was required to be added
// and would be part of an administrative flow
await registerAndEnrollUser(caClient, wallet, mspOrg1, org1UserId, 'org1.department1');
// Create a new gateway instance for interacting with the fabric network.
// In a real application this would be done as the backend server session is setup for
// a user that has been verified.
const gateway = new Gateway();
const gatewayOpts: GatewayOptions = {
wallet,
identity: org1UserId,
discovery: { enabled: true, asLocalhost: true }, // using asLocalhost as this gateway is using a fabric network deployed locally
};
try {
// setup the gateway instance
// The user will now be able to create connections to the fabric network and be able to
// submit transactions and query. All transactions submitted by this gateway will be
// signed by this user using the credentials stored in the wallet.
await gateway.connect(ccp, gatewayOpts);
// Build a network instance based on the channel where the smart contract is deployed
const network = await gateway.getNetwork(channelName);
// Get the contract from the network.
const contract = network.getContract(chaincodeName);
// Initialize a set of asset data on the channel using the chaincode 'InitLedger' function.
// This type of transaction would only be run once by an application the first time it was started after it
// deployed the first time. Any updates to the chaincode deployed later would likely not need to run
// an "init" type function.
console.log('\n--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger');
await contract.submitTransaction('InitLedger');
console.log('*** Result: committed');
// Let's try a query type operation (function).
// This will be sent to just one peer and the results will be shown.
console.log('\n--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger');
let result = await contract.evaluateTransaction('GetAllAssets');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
// Now let's try to submit a transaction.
// This will be sent to both peers and if both peers endorse the transaction, the endorsed proposal will be sent
// to the orderer to be committed by each of the peer's to the channel ledger.
console.log('\n--> Submit Transaction: CreateAsset, creates new asset with ID, color, owner, size, and appraisedValue arguments');
await contract.submitTransaction('CreateAsset', 'asset13', 'yellow', '5', 'Tom', '1300');
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID');
result = await contract.evaluateTransaction('ReadAsset', 'asset13');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Evaluate Transaction: AssetExists, function returns "true" if an asset with given assetID exist');
result = await contract.evaluateTransaction('AssetExists', 'asset1');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
console.log('\n--> Submit Transaction: UpdateAsset asset1, change the appraisedValue to 350');
await contract.submitTransaction('UpdateAsset', 'asset1', 'blue', '5', 'Tomoko', '350');
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes');
result = await contract.evaluateTransaction('ReadAsset', 'asset1');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
try {
// How about we try a transactions where the executing chaincode throws an error
// Notice how the submitTransaction will throw an error containing the error thrown by the chaincode
console.log('\n--> Submit Transaction: UpdateAsset asset70, asset70 does not exist and should return an error');
await contract.submitTransaction('UpdateAsset', 'asset70', 'blue', '5', 'Tomoko', '300');
console.log('******** FAILED to return an error');
} catch (error) {
console.log(`*** Successfully caught the error: \n ${error}`);
}
console.log('\n--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom');
await contract.submitTransaction('TransferAsset', 'asset1', 'Tom');
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: ReadAsset, function returns "asset1" attributes');
result = await contract.evaluateTransaction('ReadAsset', 'asset1');
console.log(`*** Result: ${prettyJSONString(result.toString())}`);
} finally {
// Disconnect from the gateway when the application is closing
// This will close all connections to the network
gateway.disconnect();
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();

View file

@ -0,0 +1,72 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import { Wallet, Wallets } from 'fabric-network';
import * as fs from 'fs';
import * as path from 'path';
const buildCCPOrg1 = (): Record<string, any> => {
// load the common connection configuration file
const ccpPath = path.resolve(__dirname, '..', '..', '..', '..', 'test-network',
'organizations', 'peerOrganizations', 'org1.example.com', 'connection-org1.json');
const fileExists = fs.existsSync(ccpPath);
if (!fileExists) {
throw new Error(`no such file or directory: ${ccpPath}`);
}
const contents = fs.readFileSync(ccpPath, 'utf8');
// build a JSON object from the file contents
const ccp = JSON.parse(contents);
console.log(`Loaded the network configuration located at ${ccpPath}`);
return ccp;
};
const buildCCPOrg2 = (): Record<string, any> => {
// load the common connection configuration file
const ccpPath = path.resolve(__dirname, '..', '..', '..', '..', 'test-network',
'organizations', 'peerOrganizations', 'org2.example.com', 'connection-org2.json');
const fileExists = fs.existsSync(ccpPath);
if (!fileExists) {
throw new Error(`no such file or directory: ${ccpPath}`);
}
const contents = fs.readFileSync(ccpPath, 'utf8');
// build a JSON object from the file contents
const ccp = JSON.parse(contents);
console.log(`Loaded the network configuration located at ${ccpPath}`);
return ccp;
};
const buildWallet = async (walletPath: string): Promise<Wallet> => {
// Create a new wallet : Note that wallet is for managing identities.
let wallet: Wallet;
if (walletPath) {
wallet = await Wallets.newFileSystemWallet(walletPath);
console.log(`Built a file system wallet at ${walletPath}`);
} else {
wallet = await Wallets.newInMemoryWallet();
console.log('Built an in memory wallet');
}
return wallet;
};
const prettyJSONString = (inputString: string): string => {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
} else {
return inputString;
}
};
export {
buildCCPOrg1,
buildCCPOrg2,
buildWallet,
prettyJSONString,
};

View file

@ -0,0 +1,104 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
import * as FabricCAServices from 'fabric-ca-client';
import { Wallet } from 'fabric-network';
const adminUserId = 'admin';
const adminUserPasswd = 'adminpw';
/**
*
* @param {*} ccp
*/
const buildCAClient = (ccp: Record<string, any>, caHostName: string): FabricCAServices => {
// Create a new CA client for interacting with the CA.
const caInfo = ccp.certificateAuthorities[caHostName]; // lookup CA details from config
const caTLSCACerts = caInfo.tlsCACerts.pem;
const caClient = new FabricCAServices(caInfo.url, { trustedRoots: caTLSCACerts, verify: false }, caInfo.caName);
console.log(`Built a CA Client named ${caInfo.caName}`);
return caClient;
};
const enrollAdmin = async (caClient: FabricCAServices, wallet: Wallet, orgMspId: string): Promise<void> => {
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 caClient.enroll({ enrollmentID: adminUserId, enrollmentSecret: adminUserPasswd });
const x509Identity = {
credentials: {
certificate: enrollment.certificate,
privateKey: enrollment.key.toBytes(),
},
mspId: orgMspId,
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}`);
}
};
const registerAndEnrollUser = async (caClient: FabricCAServices, wallet: Wallet, orgMspId: string, userId: string, affiliation: string): Promise<void> => {
try {
// Check to see if we've already enrolled the user
const userIdentity = await wallet.get(userId);
if (userIdentity) {
console.log(`An identity for the user ${userId} already exists in the wallet`);
return;
}
// Must use an admin to register a new 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('Enroll the admin user 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 caClient.register({
affiliation,
enrollmentID: userId,
role: 'client',
}, adminUser);
const enrollment = await caClient.enroll({
enrollmentID: userId,
enrollmentSecret: secret,
});
const x509Identity = {
credentials: {
certificate: enrollment.certificate,
privateKey: enrollment.key.toBytes(),
},
mspId: orgMspId,
type: 'X.509',
};
await wallet.put(userId, x509Identity);
console.log(`Successfully registered and enrolled user ${userId} and imported it into the wallet`);
} catch (error) {
console.error(`Failed to register user : ${error}`);
}
};
export {
buildCAClient,
enrollAdmin,
registerAndEnrollUser,
};

View file

@ -0,0 +1,19 @@
{
"compilerOptions": {
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"outDir": "dist",
"target": "es2017",
"moduleResolution": "node",
"module": "commonjs",
"declaration": true,
"sourceMap": true,
"noImplicitAny": true
},
"include": [
"./src/**/*"
],
"exclude": [
"./src/**/*.spec.ts"
]
}

View file

@ -0,0 +1,23 @@
{
"defaultSeverity": "error",
"extends": [
"tslint:recommended"
],
"jsRules": {},
"rules": {
"indent": [true, "spaces", 4],
"linebreak-style": [true, "LF"],
"quotemark": [true, "single"],
"semicolon": [true, "always"],
"no-console": false,
"curly": true,
"triple-equals": true,
"no-string-throw": true,
"no-var-keyword": true,
"no-trailing-whitespace": true,
"object-literal-key-quotes": [true, "as-needed"],
"object-literal-sort-keys": false,
"max-line-length": false
},
"rulesDirectory": []
}

View file

@ -1,8 +1,6 @@
# Asset-Transfer-Basic as an external service # Asset-Transfer-Basic as an external service
See the "Chaincode as an external service" documentation for an introduction on how to run chaincode as an external service. This sample provides an introduction to how to use external builder and launcher scripts to run chaincode as an external service to your peer. For more information, see the [Chaincode as an external service](https://hyperledger-fabric.readthedocs.io/en/latest/cc_launcher.html) topic in the Fabric documentation.
This sample includes the external builder and launcher scripts which the peers in your Fabric network will require
in order to run an asset transfer chaincode as an external service.
**Note:** each organization in a real network would need to setup and host their own instance of the external service. For simplification purpose, in this sample we use the same instance for both organizations. **Note:** each organization in a real network would need to setup and host their own instance of the external service. For simplification purpose, in this sample we use the same instance for both organizations.

View file

@ -72,7 +72,8 @@ class AssetTransfer extends Contract {
Owner: owner, Owner: owner,
AppraisedValue: appraisedValue, AppraisedValue: appraisedValue,
}; };
return ctx.stub.putState(id, Buffer.from(JSON.stringify(asset))); ctx.stub.putState(id, Buffer.from(JSON.stringify(asset)));
return JSON.stringify(asset);
} }
// ReadAsset returns the asset stored in the world state with given id. // ReadAsset returns the asset stored in the world state with given id.

View file

@ -0,0 +1,5 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -0,0 +1,36 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
module.exports = {
env: {
node: true,
mocha: true
},
parserOptions: {
ecmaVersion: 8,
sourceType: 'script'
},
extends: 'eslint:recommended',
rules: {
indent: ['error', 'tab'],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
curly: 'error',
eqeqeq: 'error',
'no-throw-literal': 'error',
'no-var': 'error',
'dot-notation': 'error',
'no-trailing-spaces': 'error',
'no-use-before-define': 'error',
'no-useless-call': 'error',
'no-with': 'error',
'operator-linebreak': 'error',
yoda: 'error',
'quote-props': ['error', 'as-needed']
}
};

View file

@ -0,0 +1,14 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Dependency directories
node_modules/
jspm_packages/
package-lock.json
wallet
!wallet/.gitkeep

View file

@ -0,0 +1,545 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
/**
* Application that shows events when creating and updating an asset
* -- How to register a contract listener for chaincode events
* -- How to get the chaincode event name and value from the chaincode event
* -- How to retrieve the transaction and block information from the chaincode event
* -- How to register a block listener for full block events
* -- How to retrieve the transaction and block information from the full block event
* -- How to register to recieve private data associated with transactions when
* registering a block listener
* -- How to retreive the private data from the full block event
* -- The listener will be notified of an event at anytime. Notice that events will
* be posted by the listener after the application activity causing the ledger change
* and during other application activity unrelated to the event
* -- How to connect to a Gateway that will not use events when submitting transactions.
* This may be useful when the application does not want to wait for the peer to commit
* blocks and notify the application.
*
* To see the SDK workings, try setting the logging to be displayed on the console
* before executing this application.
* export HFC_LOGGING='{"debug":"console"}'
* See the following on how the SDK is working with the Peer's Event Services
* https://hyperledger-fabric.readthedocs.io/en/latest/peer_event_services.html
*
* See the following for more details on using the Node SDK
* https://hyperledger.github.io/fabric-sdk-node/release-2.2/module-fabric-network.html
*/
// pre-requisites:
// - fabric-sample two organization test-network setup with two peers, ordering service,
// and 2 certificate authorities
// ===> from directory test-network
// ./network.sh up createChannel -ca
//
// - Use the asset-transfer-events/chaincode-javascript chaincode deployed on
// the channel "mychannel". The following deploy command will package, install,
// approve, and commit the javascript chaincode, all the actions it takes
// to deploy a chaincode to a channel.
// ===> from directory test-network
// ./network.sh deployCC -ccn events -ccl javascript -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
//
// - Be sure that node.js is installed
// ===> from directory asset-transfer-sbe/application-javascript
// node -v
// - npm installed code dependencies
// ===> from directory asset-transfer-sbe/application-javascript
// npm install
// - to run this test application
// ===> from directory asset-transfer-sbe/application-javascript
// node app.js
// NOTE: If you see an error like these:
/*
Error in setup: Error: DiscoveryService: mychannel error: access denied
OR
Failed to register user : Error: fabric-ca request register failed with errors [[ { code: 20, message: 'Authentication failure' } ]]
*/
// Delete the /fabric-samples/asset-transfer-sbe/application-javascript/wallet directory
// and retry this application.
//
// The certificate authority must have been restarted and the saved certificates for the
// admin and application user are not valid. Deleting the wallet store will force these to be reset
// with the new certificate authority.
//
// use this to set logging, must be set before the require('fabric-network');
process.env.HFC_LOGGING = '{"debug": "./debug.log"}';
const { Gateway, Wallets } = require('fabric-network');
const EventStrategies = require('fabric-network/lib/impl/event/defaulteventhandlerstrategies');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, registerAndEnrollUser, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const channelName = 'mychannel';
const chaincodeName = 'events';
const org1 = 'Org1MSP';
const Org1UserId = 'appUser1';
const RED = '\x1b[31m\n';
const GREEN = '\x1b[32m\n';
const BLUE = '\x1b[34m';
const RESET = '\x1b[0m';
/**
* Perform a sleep -- asynchronous wait
* @param ms the time in milliseconds to sleep for
*/
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function initGatewayForOrg1(useCommitEvents) {
console.log(`${GREEN}--> Fabric client user & Gateway init: Using Org1 identity to Org1 Peer${RESET}`);
// build an in memory object with the network configuration (also known as a connection profile)
const ccpOrg1 = buildCCPOrg1();
// build an instance of the fabric ca services client based on
// the information in the network configuration
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
// setup the wallet to cache the credentials of the application user, on the app server locally
const walletPathOrg1 = path.join(__dirname, 'wallet', 'org1');
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
// in a real application this would be done on an administrative flow, and only once
// stores admin identity in local wallet, if needed
await enrollAdmin(caOrg1Client, walletOrg1, org1);
// register & enroll application user with CA, which is used as client identify to make chaincode calls
// and stores app user identity in local wallet
// In a real application this would be done only when a new user was required to be added
// and would be part of an administrative flow
await registerAndEnrollUser(caOrg1Client, walletOrg1, org1, Org1UserId, 'org1.department1');
try {
// Create a new gateway for connecting to Org's peer node.
const gatewayOrg1 = new Gateway();
if (useCommitEvents) {
await gatewayOrg1.connect(ccpOrg1, {
wallet: walletOrg1,
identity: Org1UserId,
discovery: { enabled: true, asLocalhost: true }
});
} else {
await gatewayOrg1.connect(ccpOrg1, {
wallet: walletOrg1,
identity: Org1UserId,
discovery: { enabled: true, asLocalhost: true },
eventHandlerOptions: EventStrategies.NONE
});
}
return gatewayOrg1;
} catch (error) {
console.error(`Error in connecting to gateway for Org1: ${error}`);
process.exit(1);
}
}
function checkAsset(org, resultBuffer, color, size, owner, appraisedValue, price) {
console.log(`${GREEN}<-- Query results from ${org}${RESET}`);
let asset;
if (resultBuffer) {
asset = JSON.parse(resultBuffer.toString('utf8'));
} else {
console.log(`${RED}*** Failed to read asset${RESET}`);
}
console.log(`*** verify asset ${asset.ID}`);
if (asset) {
if (asset.Color === color) {
console.log(`*** asset ${asset.ID} has color ${asset.Color}`);
} else {
console.log(`${RED}*** asset ${asset.ID} has color of ${asset.Color}${RESET}`);
}
if (asset.Size === size) {
console.log(`*** asset ${asset.ID} has size ${asset.Size}`);
} else {
console.log(`${RED}*** Failed size check from ${org} - asset ${asset.ID} has size of ${asset.Size}${RESET}`);
}
if (asset.Owner === owner) {
console.log(`*** asset ${asset.ID} owned by ${asset.Owner}`);
} else {
console.log(`${RED}*** Failed owner check from ${org} - asset ${asset.ID} owned by ${asset.Owner}${RESET}`);
}
if (asset.AppraisedValue === appraisedValue) {
console.log(`*** asset ${asset.ID} has appraised value ${asset.AppraisedValue}`);
} else {
console.log(`${RED}*** Failed appraised value check from ${org} - asset ${asset.ID} has appraised value of ${asset.AppraisedValue}${RESET}`);
}
if (price) {
if (asset.asset_properties && asset.asset_properties.Price === price) {
console.log(`*** asset ${asset.ID} has price ${asset.asset_properties.Price}`);
} else {
console.log(`${RED}*** Failed price check from ${org} - asset ${asset.ID} has price of ${asset.asset_properties.Price}${RESET}`);
}
}
}
}
function showTransactionData(transactionData) {
const creator = transactionData.actions[0].header.creator;
console.log(` - submitted by: ${creator.mspid}-${creator.id_bytes.toString('hex')}`);
for (const endorsement of transactionData.actions[0].payload.action.endorsements) {
console.log(` - endorsed by: ${endorsement.endorser.mspid}-${endorsement.endorser.id_bytes.toString('hex')}`);
}
const chaincode = transactionData.actions[0].payload.chaincode_proposal_payload.input.chaincode_spec;
console.log(` - chaincode:${chaincode.chaincode_id.name}`);
console.log(` - function:${chaincode.input.args[0].toString()}`);
for (let x = 1; x < chaincode.input.args.length; x++) {
console.log(` - arg:${chaincode.input.args[x].toString()}`);
}
}
async function main() {
console.log(`${BLUE} **** START ****${RESET}`);
try {
let randomNumber = Math.floor(Math.random() * 1000) + 1;
// use a random key so that we can run multiple times
let assetKey = `item-${randomNumber}`;
/** ******* Fabric client init: Using Org1 identity to Org1 Peer ******* */
const gateway1Org1 = await initGatewayForOrg1(true); // transaction handling uses commit events
const gateway2Org1 = await initGatewayForOrg1();
try {
//
// - - - - - - C H A I N C O D E E V E N T S
//
console.log(`${BLUE} **** CHAINCODE EVENTS ****${RESET}`);
let transaction;
let listener;
const network1Org1 = await gateway1Org1.getNetwork(channelName);
const contract1Org1 = network1Org1.getContract(chaincodeName);
try {
// first create a listener to be notified of chaincode code events
// coming from the chaincode ID "events"
listener = async (event) => {
// The payload of the chaincode event is the value place there by the
// chaincode. Notice it is a byte data and the application will have
// to know how to deserialize.
// In this case we know that the chaincode will always place the asset
// being worked with as the payload for all events produced.
const asset = JSON.parse(event.payload.toString());
console.log(`${GREEN}<-- Contract Event Received: ${event.eventName} - ${JSON.stringify(asset)}${RESET}`);
// show the information available with the event
console.log(`*** Event: ${event.eventName}:${asset.ID}`);
// notice how we have access to the transaction information that produced this chaincode event
const eventTransaction = event.getTransactionEvent();
console.log(`*** transaction: ${eventTransaction.transactionId} status:${eventTransaction.status}`);
showTransactionData(eventTransaction.transactionData);
// notice how we have access to the full block that contains this transaction
const eventBlock = eventTransaction.getBlockEvent();
console.log(`*** block: ${eventBlock.blockNumber.toString()}`);
};
// now start the client side event service and register the listener
console.log(`${GREEN}--> Start contract event stream to peer in Org1${RESET}`);
await contract1Org1.addContractListener(listener);
} catch (eventError) {
console.log(`${RED}<-- Failed: Setup contract events - ${eventError}${RESET}`);
}
try {
// C R E A T E
console.log(`${GREEN}--> Submit Transaction: CreateAsset, ${assetKey} owned by Sam${RESET}`);
transaction = contract1Org1.createTransaction('CreateAsset');
await transaction.submit(assetKey, 'blue', '10', 'Sam', '100');
console.log(`${GREEN}<-- Submit CreateAsset Result: committed, asset ${assetKey}${RESET}`);
} catch (createError) {
console.log(`${RED}<-- Submit Failed: CreateAsset - ${createError}${RESET}`);
}
try {
// R E A D
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should be owned by Sam${RESET}`);
const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey);
checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '100');
} catch (readError) {
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
}
try {
// U P D A T E
console.log(`${GREEN}--> Submit Transaction: UpdateAsset ${assetKey} update appraised value to 200`);
transaction = contract1Org1.createTransaction('UpdateAsset');
await transaction.submit(assetKey, 'blue', '10', 'Sam', '200');
console.log(`${GREEN}<-- Submit UpdateAsset Result: committed, asset ${assetKey}${RESET}`);
} catch (updateError) {
console.log(`${RED}<-- Failed: UpdateAsset - ${updateError}${RESET}`);
}
try {
// R E A D
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now have appraised value of 200${RESET}`);
const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey);
checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '200');
} catch (readError) {
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
}
try {
// T R A N S F E R
console.log(`${GREEN}--> Submit Transaction: TransferAsset ${assetKey} to Mary`);
transaction = contract1Org1.createTransaction('TransferAsset');
await transaction.submit(assetKey, 'Mary');
console.log(`${GREEN}<-- Submit TransferAsset Result: committed, asset ${assetKey}${RESET}`);
} catch (transferError) {
console.log(`${RED}<-- Failed: TransferAsset - ${transferError}${RESET}`);
}
try {
// R E A D
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be owned by Mary${RESET}`);
const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey);
checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200');
} catch (readError) {
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
}
try {
// D E L E T E
console.log(`${GREEN}--> Submit Transaction: DeleteAsset ${assetKey}`);
transaction = contract1Org1.createTransaction('DeleteAsset');
await transaction.submit(assetKey);
console.log(`${GREEN}<-- Submit DeleteAsset Result: committed, asset ${assetKey}${RESET}`);
} catch (deleteError) {
console.log(`${RED}<-- Failed: DeleteAsset - ${deleteError}${RESET}`);
if (deleteError.toString().includes('ENDORSEMENT_POLICY_FAILURE')) {
console.log(`${RED}Be sure that chaincode was deployed with the endorsement policy "OR('Org1MSP.peer','Org2MSP.peer')"${RESET}`)
}
}
try {
// R E A D
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be deleted${RESET}`);
const resultBuffer = await contract1Org1.evaluateTransaction('ReadAsset', assetKey);
checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200');
console.log(`${RED}<-- Failed: ReadAsset - should not have read this asset${RESET}`);
} catch (readError) {
console.log(`${GREEN}<-- Success: ReadAsset - ${readError}${RESET}`);
}
// all done with this listener
contract1Org1.removeContractListener(listener);
//
// - - - - - - B L O C K E V E N T S with P R I V A T E D A T A
//
console.log(`${BLUE} **** BLOCK EVENTS with PRIVATE DATA ****${RESET}`);
const network2Org1 = await gateway2Org1.getNetwork(channelName);
const contract2Org1 = network2Org1.getContract(chaincodeName);
randomNumber = Math.floor(Math.random() * 1000) + 1;
assetKey = `item-${randomNumber}`;
let firstBlock = true; // simple indicator to track blocks
try {
let listener;
// create a block listener
listener = async (event) => {
if (firstBlock) {
console.log(`${GREEN}<-- Block Event Received - block number: ${event.blockNumber.toString()}` +
'\n### Note:' +
'\n This block event represents the current top block of the ledger.' +
`\n All block events after this one are events that represent new blocks added to the ledger${RESET}`);
firstBlock = false;
} else {
console.log(`${GREEN}<-- Block Event Received - block number: ${event.blockNumber.toString()}${RESET}`);
}
const transEvents = event.getTransactionEvents();
for (const transEvent of transEvents) {
console.log(`*** transaction event: ${transEvent.transactionId}`);
if (transEvent.privateData) {
for (const namespace of transEvent.privateData.ns_pvt_rwset) {
console.log(` - private data: ${namespace.namespace}`);
for (const collection of namespace.collection_pvt_rwset) {
console.log(` - collection: ${collection.collection_name}`);
if (collection.rwset.reads) {
for (const read of collection.rwset.reads) {
console.log(` - read set - ${BLUE}key:${RESET} ${read.key} ${BLUE}value:${read.value.toString()}`);
}
}
if (collection.rwset.writes) {
for (const write of collection.rwset.writes) {
console.log(` - write set - ${BLUE}key:${RESET}${write.key} ${BLUE}is_delete:${RESET}${write.is_delete} ${BLUE}value:${RESET}${write.value.toString()}`);
}
}
}
}
}
if (transEvent.transactionData) {
showTransactionData(transEvent.transactionData);
}
}
};
// now start the client side event service and register the listener
console.log(`${GREEN}--> Start private data block event stream to peer in Org1${RESET}`);
await network2Org1.addBlockListener(listener, {type: 'private'});
} catch (eventError) {
console.log(`${RED}<-- Failed: Setup block events - ${eventError}${RESET}`);
}
try {
// C R E A T E
console.log(`${GREEN}--> Submit Transaction: CreateAsset, ${assetKey} owned by Sam${RESET}`);
transaction = contract2Org1.createTransaction('CreateAsset');
// create the private data with salt and assign to the transaction
const randomNumber = Math.floor(Math.random() * 100) + 1;
const asset_properties = {
object_type: 'asset_properties',
asset_id: assetKey,
Price: '90',
salt: Buffer.from(randomNumber.toString()).toString('hex')
};
const asset_properties_string = JSON.stringify(asset_properties);
transaction.setTransient({
asset_properties: Buffer.from(asset_properties_string)
});
// With the addition of private data to the transaction
// We must only send this to the organization that will be
// saving the private data or we will get an endorsement policy failure
transaction.setEndorsingOrganizations(org1);
// endorse and commit - private data (transient data) will be
// saved to the implicit collection on the peer
await transaction.submit(assetKey, 'blue', '10', 'Sam', '100');
console.log(`${GREEN}<-- Submit CreateAsset Result: committed, asset ${assetKey}${RESET}`);
} catch (createError) {
console.log(`${RED}<-- Failed: CreateAsset - ${createError}${RESET}`);
}
await sleep(5000); // need to wait for event to be committed
try {
// R E A D
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should be owned by Sam${RESET}`);
const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey);
checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '100', '90');
} catch (readError) {
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
}
try {
// U P D A T E
console.log(`${GREEN}--> Submit Transaction: UpdateAsset ${assetKey} update appraised value to 200`);
transaction = contract2Org1.createTransaction('UpdateAsset');
// update the private data with new salt and assign to the transaction
const randomNumber = Math.floor(Math.random() * 100) + 1;
const asset_properties = {
object_type: 'asset_properties',
asset_id: assetKey,
Price: '90',
salt: Buffer.from(randomNumber.toString()).toString('hex')
};
const asset_properties_string = JSON.stringify(asset_properties);
transaction.setTransient({
asset_properties: Buffer.from(asset_properties_string)
});
transaction.setEndorsingOrganizations(org1);
await transaction.submit(assetKey, 'blue', '10', 'Sam', '200');
console.log(`${GREEN}<-- Submit UpdateAsset Result: committed, asset ${assetKey}${RESET}`);
} catch (updateError) {
console.log(`${RED}<-- Failed: UpdateAsset - ${updateError}${RESET}`);
}
await sleep(5000); // need to wait for event to be committed
try {
// R E A D
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now have appraised value of 200${RESET}`);
const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey);
checkAsset(org1, resultBuffer, 'blue', '10', 'Sam', '200', '90');
} catch (readError) {
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
}
try {
// T R A N S F E R
console.log(`${GREEN}--> Submit Transaction: TransferAsset ${assetKey} to Mary`);
transaction = contract2Org1.createTransaction('TransferAsset');
// update the private data with new salt and assign to the transaction
const randomNumber = Math.floor(Math.random() * 100) + 1;
const asset_properties = {
object_type: 'asset_properties',
asset_id: assetKey,
Price: '180',
salt: Buffer.from(randomNumber.toString()).toString('hex')
};
const asset_properties_string = JSON.stringify(asset_properties);
transaction.setTransient({
asset_properties: Buffer.from(asset_properties_string)
});
transaction.setEndorsingOrganizations(org1);
await transaction.submit(assetKey, 'Mary');
console.log(`${GREEN}<-- Submit TransferAsset Result: committed, asset ${assetKey}${RESET}`);
} catch (transferError) {
console.log(`${RED}<-- Failed: TransferAsset - ${transferError}${RESET}`);
}
await sleep(5000); // need to wait for event to be committed
try {
// R E A D
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be owned by Mary${RESET}`);
const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey);
checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200', '180');
} catch (readError) {
console.log(`${RED}<-- Failed: ReadAsset - ${readError}${RESET}`);
}
try {
// D E L E T E
console.log(`${GREEN}--> Submit Transaction: DeleteAsset ${assetKey}`);
transaction = contract2Org1.createTransaction('DeleteAsset');
await transaction.submit(assetKey);
console.log(`${GREEN}<-- Submit DeleteAsset Result: committed, asset ${assetKey}${RESET}`);
} catch (deleteError) {
console.log(`${RED}<-- Failed: DeleteAsset - ${deleteError}${RESET}`);
}
await sleep(5000); // need to wait for event to be committed
try {
// R E A D
console.log(`${GREEN}--> Evaluate: ReadAsset, - ${assetKey} should now be deleted${RESET}`);
const resultBuffer = await contract2Org1.evaluateTransaction('ReadAsset', assetKey);
checkAsset(org1, resultBuffer, 'blue', '10', 'Mary', '200');
console.log(`${RED}<-- Failed: ReadAsset - should not have read this asset${RESET}`);
} catch (readError) {
console.log(`${GREEN}<-- Success: ReadAsset - ${readError}${RESET}`);
}
// all done with this listener
network2Org1.removeBlockListener(listener);
} catch (runError) {
console.error(`Error in transaction: ${runError}`);
if (runError.stack) {
console.error(runError.stack);
}
}
} catch (error) {
console.error(`Error in setup: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
await sleep(5000);
console.log(`${BLUE} **** END ****${RESET}`);
process.exit(0);
}
main();

View file

@ -0,0 +1,16 @@
{
"name": "asset-transfer-events",
"version": "1.0.0",
"description": "Javascript application that uses chaincode events and block events with private data",
"engines": {
"node": ">=12",
"npm": ">=5"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-ca-client": "^2.2.2",
"fabric-network": "^2.2.2"
}
}

View file

@ -0,0 +1,5 @@
#
# SPDX-License-Identifier: Apache-2.0
#
coverage

View file

@ -0,0 +1,39 @@
/*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
module.exports = {
env: {
node: true,
mocha: true,
es6: true
},
parserOptions: {
ecmaVersion: 8,
sourceType: 'script'
},
extends: 'eslint:recommended',
rules: {
indent: ['error', 'tab'],
'linebreak-style': ['error', 'unix'],
quotes: ['error', 'single'],
semi: ['error', 'always'],
'no-unused-vars': ['error', { args: 'none' }],
'no-console': 'off',
curly: 'error',
eqeqeq: 'error',
'no-throw-literal': 'error',
strict: 'error',
'no-var': 'error',
'dot-notation': 'error',
'no-trailing-spaces': 'error',
'no-use-before-define': 'error',
'no-useless-call': 'error',
'no-with': 'error',
'operator-linebreak': 'error',
yoda: 'error',
'quote-props': ['error', 'as-needed'],
'no-constant-condition': ['error', { checkLoops: false }]
}
};

View file

@ -0,0 +1,15 @@
#
# SPDX-License-Identifier: Apache-2.0
#
# Coverage directory used by tools like istanbul
coverage
# Report cache used by istanbul
.nyc_output
# Dependency directories
node_modules/
jspm_packages/
package-lock.json

View file

@ -0,0 +1,12 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const assetTransferEvents = require('./lib/assetTransferEvents');
module.exports.AssetTransferEvents = assetTransferEvents;
module.exports.contracts = [assetTransferEvents];

View file

@ -0,0 +1,128 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Contract } = require('fabric-contract-api');
async function savePrivateData(ctx, assetKey) {
const clientOrg = ctx.clientIdentity.getMSPID();
const peerOrg = ctx.stub.getMspID();
const collection = '_implicit_org_' + peerOrg;
if (clientOrg === peerOrg) {
const transientMap = ctx.stub.getTransient();
if (transientMap) {
const properties = transientMap.get('asset_properties');
if (properties) {
await ctx.stub.putPrivateData(collection, assetKey, properties);
}
}
}
}
async function removePrivateData(ctx, assetKey) {
const clientOrg = ctx.clientIdentity.getMSPID();
const peerOrg = ctx.stub.getMspID();
const collection = '_implicit_org_' + peerOrg;
if (clientOrg === peerOrg) {
const propertiesBuffer = await ctx.stub.getPrivateData(collection, assetKey);
if (propertiesBuffer && propertiesBuffer.length > 0) {
await ctx.stub.deletePrivateData(collection, assetKey);
}
}
}
async function addPrivateData(ctx, assetKey, asset) {
const clientOrg = ctx.clientIdentity.getMSPID();
const peerOrg = ctx.stub.getMspID();
const collection = '_implicit_org_' + peerOrg;
if (clientOrg === peerOrg) {
const propertiesBuffer = await ctx.stub.getPrivateData(collection, assetKey);
if (propertiesBuffer && propertiesBuffer.length > 0) {
const properties = JSON.parse(propertiesBuffer.toString());
asset.asset_properties = properties;
}
}
}
async function readState(ctx, id) {
const assetBuffer = await ctx.stub.getState(id); // get the asset from chaincode state
if (!assetBuffer || assetBuffer.length === 0) {
throw new Error(`The asset ${id} does not exist`);
}
const assetString = assetBuffer.toString();
const asset = JSON.parse(assetString);
return asset;
}
class AssetTransferEvents extends Contract {
// CreateAsset issues a new asset to the world state with given details.
async CreateAsset(ctx, id, color, size, owner, appraisedValue) {
const asset = {
ID: id,
Color: color,
Size: size,
Owner: owner,
AppraisedValue: appraisedValue,
};
await savePrivateData(ctx, id);
const assetBuffer = Buffer.from(JSON.stringify(asset));
ctx.stub.setEvent('CreateAsset', assetBuffer);
return ctx.stub.putState(id, assetBuffer);
}
// TransferAsset updates the owner field of an asset with the given id in
// the world state.
async TransferAsset(ctx, id, newOwner) {
const asset = await readState(ctx, id);
asset.Owner = newOwner;
const assetBuffer = Buffer.from(JSON.stringify(asset));
await savePrivateData(ctx, id);
ctx.stub.setEvent('TransferAsset', assetBuffer);
return ctx.stub.putState(id, assetBuffer);
}
// ReadAsset returns the asset stored in the world state with given id.
async ReadAsset(ctx, id) {
const asset = await readState(ctx, id);
await addPrivateData(ctx, asset.ID, asset);
return JSON.stringify(asset);
}
// UpdateAsset updates an existing asset in the world state with provided parameters.
async UpdateAsset(ctx, id, color, size, owner, appraisedValue) {
const asset = await readState(ctx, id);
asset.Color = color;
asset.Size = size;
asset.Owner = owner;
asset.AppraisedValue = appraisedValue;
const assetBuffer = Buffer.from(JSON.stringify(asset));
await savePrivateData(ctx, id);
ctx.stub.setEvent('UpdateAsset', assetBuffer);
return ctx.stub.putState(id, assetBuffer);
}
// DeleteAsset deletes an given asset from the world state.
async DeleteAsset(ctx, id) {
const asset = await readState(ctx, id);
const assetBuffer = Buffer.from(JSON.stringify(asset));
await removePrivateData(ctx, id);
ctx.stub.setEvent('DeleteAsset', assetBuffer);
return ctx.stub.deleteState(id);
}
}
module.exports = AssetTransferEvents;

View file

@ -0,0 +1,49 @@
{
"name": "asset-transfer-events",
"version": "1.0.0",
"description": "Asset-Transfer-Events contract implemented in JavaScript",
"main": "index.js",
"engines": {
"node": ">=12",
"npm": ">=5"
},
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint",
"test": "nyc mocha --recursive",
"start": "fabric-chaincode-node start"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "^2.0.0",
"fabric-shim": "^2.0.0"
},
"devDependencies": {
"chai": "^4.1.2",
"eslint": "^4.19.1",
"mocha": "^8.0.1",
"nyc": "^14.1.1",
"sinon": "^6.0.0",
"sinon-chai": "^3.2.0"
},
"nyc": {
"exclude": [
"coverage/**",
"test/**",
"index.js",
".eslintrc.js"
],
"reporter": [
"text-summary",
"html"
],
"all": true,
"check-coverage": true,
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100
}
}

View file

@ -0,0 +1,224 @@
'use strict';
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const expect = chai.expect;
const { Context } = require('fabric-contract-api');
const { ChaincodeStub, ClientIdentity } = require('fabric-shim');
const AssetTransfer = require('../lib/assetTransferEvents.js');
let assert = sinon.assert;
chai.use(sinonChai);
describe('Asset Transfer Events Tests', () => {
let transactionContext, chaincodeStub, clientIdentity, asset;
let transientMap, asset_properties;
beforeEach(() => {
transactionContext = new Context();
chaincodeStub = sinon.createStubInstance(ChaincodeStub);
chaincodeStub.getMspID.returns('org1');
transactionContext.setChaincodeStub(chaincodeStub);
clientIdentity = sinon.createStubInstance(ClientIdentity);
clientIdentity.getMSPID.returns('org1');
transactionContext.clientIdentity = clientIdentity;
chaincodeStub.putState.callsFake((key, value) => {
if (!chaincodeStub.states) {
chaincodeStub.states = {};
}
chaincodeStub.states[key] = value;
});
chaincodeStub.getState.callsFake(async (key) => {
let ret;
if (chaincodeStub.states) {
ret = chaincodeStub.states[key];
}
return Promise.resolve(ret);
});
chaincodeStub.deleteState.callsFake(async (key) => {
if (chaincodeStub.states) {
delete chaincodeStub.states[key];
}
return Promise.resolve(key);
});
chaincodeStub.getStateByRange.callsFake(async () => {
function* internalGetStateByRange() {
if (chaincodeStub.states) {
// Shallow copy
const copied = Object.assign({}, chaincodeStub.states);
for (let key in copied) {
yield {value: copied[key]};
}
}
}
return Promise.resolve(internalGetStateByRange());
});
asset = {
ID: 'asset1',
Color: 'blue',
Size: 5,
Owner: 'Tomoko',
AppraisedValue: 300,
};
const randomNumber = Math.floor(Math.random() * 100) + 1;
asset_properties = {
object_type: 'asset_properties',
asset_id: 'asset1',
Price: '90',
salt: Buffer.from(randomNumber.toString()).toString('hex')
};
transientMap = {
asset_properties: Buffer.from(JSON.stringify(asset_properties))
};
});
describe('Test CreateAsset', () => {
it('should return error on CreateAsset', async () => {
chaincodeStub.putState.rejects('failed inserting key');
let assetTransfer = new AssetTransfer();
try {
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
assert.fail('CreateAsset should have failed');
} catch(err) {
expect(err.name).to.equal('failed inserting key');
}
});
it('should return success on CreateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
expect(ret).to.eql(asset);
});
it('should return success on CreateAsset with transient data', async () => {
let assetTransfer = new AssetTransfer();
chaincodeStub.getTransient.returns(transientMap);
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
expect(ret).to.eql(asset);
});
});
describe('Test ReadAsset', () => {
it('should return error on ReadAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.ReadAsset(transactionContext, 'asset2');
assert.fail('ReadAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on ReadAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
const assetString = await assetTransfer.ReadAsset(transactionContext, 'asset1');
const readAsset = JSON.parse(assetString);
expect(readAsset).to.eql(asset);
});
it('should return success on ReadAsset with private data', async () => {
asset.asset_properties = asset_properties;
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
chaincodeStub.getPrivateData.returns(Buffer.from(JSON.stringify(asset_properties)));
const assetString = await assetTransfer.ReadAsset(transactionContext, 'asset1');
const readAsset = JSON.parse(assetString);
expect(readAsset).to.eql(asset);
});
});
describe('Test UpdateAsset', () => {
it('should return error on UpdateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.UpdateAsset(transactionContext, 'asset2', 'orange', 10, 'Me', 500);
assert.fail('UpdateAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on UpdateAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.UpdateAsset(transactionContext, 'asset1', 'orange', 10, 'Me', 500);
let ret = JSON.parse(await chaincodeStub.getState(asset.ID));
let expected = {
ID: 'asset1',
Color: 'orange',
Size: 10,
Owner: 'Me',
AppraisedValue: 500
};
expect(ret).to.eql(expected);
});
});
describe('Test DeleteAsset', () => {
it('should return error on DeleteAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.DeleteAsset(transactionContext, 'asset2');
assert.fail('DeleteAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on DeleteAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.DeleteAsset(transactionContext, asset.ID);
let ret = await chaincodeStub.getState(asset.ID);
expect(ret).to.equal(undefined);
});
});
describe('Test TransferAsset', () => {
it('should return error on TransferAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
try {
await assetTransfer.TransferAsset(transactionContext, 'asset2', 'Me');
assert.fail('DeleteAsset should have failed');
} catch (err) {
expect(err.message).to.equal('The asset asset2 does not exist');
}
});
it('should return success on TransferAsset', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.CreateAsset(transactionContext, asset.ID, asset.Color, asset.Size, asset.Owner, asset.AppraisedValue);
await assetTransfer.TransferAsset(transactionContext, asset.ID, 'Me');
let ret = JSON.parse((await chaincodeStub.getState(asset.ID)).toString());
expect(ret).to.eql(Object.assign({}, asset, {Owner: 'Me'}));
});
});
});

View file

@ -386,7 +386,7 @@ class Chaincode extends Contract {
}, },
]; ];
for (let asset in assets) { for (const asset of assets) {
await this.CreateAsset( await this.CreateAsset(
ctx, ctx,
asset.assetID, asset.assetID,

View file

@ -22,6 +22,11 @@ const mspOrg1 = 'Org1MSP';
const mspOrg2 = 'Org2MSP'; const mspOrg2 = 'Org2MSP';
const Org1UserId = 'appUser1'; const Org1UserId = 'appUser1';
const Org2UserId = 'appUser2'; const Org2UserId = 'appUser2';
const userOrg1IdentityString = `x509::CN=${Org1UserId},OU=client+OU=org1+OU=department1::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=US`;
const userOrg2IdentityString = `x509::CN=${Org2UserId},OU=client+OU=org2+OU=department1::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK`;
const RED = '\x1b[31m\n';
const RESET = '\x1b[0m';
function prettyJSONString(inputString) { function prettyJSONString(inputString) {
if (inputString) { if (inputString) {
@ -32,6 +37,67 @@ function prettyJSONString(inputString) {
} }
} }
function doFail(msgString) {
console.error(`${RED}\t${msgString}${RESET}`);
process.exit(1);
}
function verifyAssetData(org, resultBuffer, expectedId, color, size, owner, appraisedValue) {
let asset;
if (resultBuffer) {
asset = JSON.parse(resultBuffer.toString('utf8'));
} else {
doFail('Failed to read asset');
}
console.log(`*** verify asset data for: ${expectedId}`);
if (!asset) {
doFail('Received empty asset');
}
if (expectedId !== asset.assetID) {
doFail(`recieved asset ${asset.assetID} , but expected ${expectedId}`);
}
if (asset.color !== color) {
doFail(`asset ${asset.assetID} has color of ${asset.color}, expected value ${color}`);
}
if (asset.size !== size) {
doFail(`Failed size check - asset ${asset.assetID} has size of ${asset.size}, expected value ${size}`);
}
let assetsOwner = Buffer.from(asset.owner, 'base64').toString();
if (assetsOwner === owner) {
console.log(`\tasset ${asset.assetID} owner: ${assetsOwner}`);
} else {
doFail(`Failed owner check from ${org} - asset ${asset.assetID} owned by ${assetsOwner}, expected value ${owner}`);
}
if (appraisedValue) {
if (asset.appraisedValue !== appraisedValue) {
doFail(`Failed appraised value check from ${org} - asset ${asset.assetID} has appraised value of ${asset.appraisedValue}, expected value ${appraisedValue}`);
}
}
}
function verifyAssetPrivateDetails(resultBuffer, expectedId, appraisedValue) {
let assetPD;
if (resultBuffer) {
assetPD = JSON.parse(resultBuffer.toString('utf8'));
} else {
doFail('Failed to read asset private details');
}
console.log(`*** verify private details: ${expectedId}`);
if (!assetPD) {
doFail('Received empty data');
}
if (expectedId !== assetPD.assetID) {
doFail(`recieved ${assetPD.assetID} , but expected ${expectedId}`);
}
if (appraisedValue) {
if (assetPD.appraisedValue !== appraisedValue) {
doFail(`Failed appraised value check - asset ${assetPD.assetID} has appraised value of ${assetPD.appraisedValue}, expected value ${appraisedValue}`);
}
}
}
async function initContractFromOrg1Identity() { async function initContractFromOrg1Identity() {
console.log('\n--> Fabric client user & Gateway init: Using Org1 identity to Org1 Peer'); console.log('\n--> Fabric client user & Gateway init: Using Org1 identity to Org1 Peer');
// build an in memory object with the network configuration (also known as a connection profile) // build an in memory object with the network configuration (also known as a connection profile)
@ -115,8 +181,10 @@ async function main() {
try { try {
// Sample transactions are listed below // Sample transactions are listed below
// Add few sample Assets & transfers one of the asset from Org1 to Org2 as the new owner // Add few sample Assets & transfers one of the asset from Org1 to Org2 as the new owner
let assetID1 = 'asset1'; let randomNumber = Math.floor(Math.random() * 1000) + 1;
let assetID2 = 'asset2'; // use a random key so that we can run multiple times
let assetID1 = `asset${randomNumber}`;
let assetID2 = `asset${randomNumber + 1}`;
const assetType = 'ValuableAsset'; const assetType = 'ValuableAsset';
let result; let result;
let asset1Data = { objectType: assetType, assetID: assetID1, color: 'green', size: 20, appraisedValue: 100 }; let asset1Data = { objectType: assetType, assetID: assetID1, color: 'green', size: 20, appraisedValue: 100 };
@ -146,12 +214,15 @@ async function main() {
console.log('\n--> Evaluate Transaction: GetAssetByRange asset0-asset9'); console.log('\n--> Evaluate Transaction: GetAssetByRange asset0-asset9');
// GetAssetByRange returns assets on the ledger with ID in the range of startKey (inclusive) and endKey (exclusive) // GetAssetByRange returns assets on the ledger with ID in the range of startKey (inclusive) and endKey (exclusive)
result = await contractOrg1.evaluateTransaction('GetAssetByRange', 'asset0', 'asset9'); result = await contractOrg1.evaluateTransaction('GetAssetByRange', 'asset0', 'asset9');
console.log(' result: ' + prettyJSONString(result.toString())); console.log(`<-- result: ${prettyJSONString(result.toString())}`);
if (!result || result.length === 0) {
doFail('recieved empty query list for GetAssetByRange');
}
console.log('\n--> Evaluate Transaction: ReadAssetPrivateDetails from ' + org1PrivateCollectionName); console.log('\n--> Evaluate Transaction: ReadAssetPrivateDetails from ' + org1PrivateCollectionName);
// ReadAssetPrivateDetails reads data from Org's private collection. Args: collectionName, assetID // ReadAssetPrivateDetails reads data from Org's private collection. Args: collectionName, assetID
result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1);
console.log(' result: ' + prettyJSONString(result.toString())); console.log(`<-- result: ${prettyJSONString(result.toString())}`);
verifyAssetPrivateDetails(result, assetID1, 100);
// Attempt Transfer the asset to Org2 , without Org2 adding AgreeToTransfer // // Attempt Transfer the asset to Org2 , without Org2 adding AgreeToTransfer //
// Transaction should return an error: "failed transfer verification ..." // Transaction should return an error: "failed transfer verification ..."
@ -171,9 +242,9 @@ async function main() {
console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~'); console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~');
console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID1); console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID1);
result = await contractOrg2.evaluateTransaction('ReadAsset', assetID1); result = await contractOrg2.evaluateTransaction('ReadAsset', assetID1);
console.log(' result: ' + prettyJSONString(result.toString())); console.log(`<-- result: ${prettyJSONString(result.toString())}`);
let assetOwner = JSON.parse(result.toString()).owner; verifyAssetData(mspOrg2, result, assetID1, 'green', 20, userOrg1IdentityString);
console.log(' Asset owner: ' + Buffer.from(assetOwner, 'base64').toString());
// Org2 cannot ReadAssetPrivateDetails from Org1's private collection due to Collection policy // Org2 cannot ReadAssetPrivateDetails from Org1's private collection due to Collection policy
// Will fail: await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); // Will fail: await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1);
@ -203,7 +274,7 @@ async function main() {
// All members can send txn ReadTransferAgreement, set by Org2 above // All members can send txn ReadTransferAgreement, set by Org2 above
console.log('\n--> Evaluate Transaction: ReadTransferAgreement ' + assetID1); console.log('\n--> Evaluate Transaction: ReadTransferAgreement ' + assetID1);
result = await contractOrg1.evaluateTransaction('ReadTransferAgreement', assetID1); result = await contractOrg1.evaluateTransaction('ReadTransferAgreement', assetID1);
console.log(' result: ' + prettyJSONString(result.toString())); console.log(`<-- result: ${prettyJSONString(result.toString())}`);
// Transfer the asset to Org2 // // 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 // To transfer the asset, the owner needs to pass the MSP ID of new asset owner, and initiate the transfer
@ -219,19 +290,21 @@ async function main() {
//Again ReadAsset : results will show that the buyer identity now owns the asset: //Again ReadAsset : results will show that the buyer identity now owns the asset:
console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID1); console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID1);
result = await contractOrg1.evaluateTransaction('ReadAsset', assetID1); result = await contractOrg1.evaluateTransaction('ReadAsset', assetID1);
console.log(' result: ' + prettyJSONString(result.toString())); console.log(`<-- result: ${prettyJSONString(result.toString())}`);
assetOwner = JSON.parse(result.toString()).owner; verifyAssetData(mspOrg1, result, assetID1, 'green', 20, userOrg2IdentityString);
console.log(' Asset owner: ' + Buffer.from(assetOwner, 'base64').toString());
//Confirm that transfer removed the private details from the Org1 collection: //Confirm that transfer removed the private details from the Org1 collection:
console.log('\n--> Evaluate Transaction: ReadAssetPrivateDetails'); console.log('\n--> Evaluate Transaction: ReadAssetPrivateDetails');
// ReadAssetPrivateDetails reads data from Org's private collection: Should return empty // ReadAssetPrivateDetails reads data from Org's private collection: Should return empty
result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1);
console.log(' result: ' + prettyJSONString(result.toString())); console.log(`<-- result: ${prettyJSONString(result.toString())}`);
if (result && result.length > 0) {
doFail('Expected empty data from ReadAssetPrivateDetails');
}
console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID2); console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID2);
result = await contractOrg1.evaluateTransaction('ReadAsset', assetID2); result = await contractOrg1.evaluateTransaction('ReadAsset', assetID2);
console.log(' result: ' + prettyJSONString(result.toString())); console.log(`<-- result: ${prettyJSONString(result.toString())}`);
verifyAssetData(mspOrg1, result, assetID2, 'blue', 35, userOrg1IdentityString);
console.log('\n********* Demo deleting asset **************'); console.log('\n********* Demo deleting asset **************');
let dataForDelete = { assetID: assetID2 }; let dataForDelete = { assetID: assetID2 };
@ -259,13 +332,17 @@ async function main() {
console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID2); console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID2);
result = await contractOrg1.evaluateTransaction('ReadAsset', assetID2); result = await contractOrg1.evaluateTransaction('ReadAsset', assetID2);
console.log(' result: ' + prettyJSONString(result.toString())); console.log(`<-- result: ${prettyJSONString(result.toString())}`);
if (result && result.length > 0) {
doFail('Expected empty read, after asset is deleted');
}
console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~'); console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~');
// Org2 can ReadAssetPrivateDetails: Org2 is owner, and private details exist in new owner's Collection // Org2 can ReadAssetPrivateDetails: Org2 is owner, and private details exist in new owner's Collection
console.log('\n--> Evaluate Transaction as Org2: ReadAssetPrivateDetails ' + assetID1 + ' from ' + org2PrivateCollectionName); console.log('\n--> Evaluate Transaction as Org2: ReadAssetPrivateDetails ' + assetID1 + ' from ' + org2PrivateCollectionName);
result = await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org2PrivateCollectionName, assetID1); result = await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org2PrivateCollectionName, assetID1);
console.log(' result: ' + prettyJSONString(result.toString())); console.log(`<-- result: ${prettyJSONString(result.toString())}`);
verifyAssetPrivateDetails(result, assetID1, 100);
} finally { } finally {
// Disconnect from the gateway peer when all work for this client identity is complete // Disconnect from the gateway peer when all work for this client identity is complete
gatewayOrg1.disconnect(); gatewayOrg1.disconnect();

View file

@ -1 +1,12 @@
{"index":{"fields":["type","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} {
"index": {
"fields": [
"objectType",
"owner"
]
},
"ddoc": "indexOwnerDoc",
"name": "indexOwner",
"type": "json"
}

View file

@ -1,246 +1 @@
# Private data asset transfer scenario [Using Private Data tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/private_data_tutorial.html)
The private data asset transfer smart contract uses a simple asset transfer to demonstrate the use of private data collections. All the data that is created by the smart contract is stored in the private data that are collections specified in the `collections_config.json` file:
- The `assetCollection` is deployed on the peers of Org1 and Org2. This collection is used to store the main asset details, such as the size, color, and owner. The `"memberOnlyRead"` and `"memberOnlyWrite"` parameters are used to specify that only Org1 and Org2 can read and write to this collection.
- The `Org1MSPPrivateCollection` is deployed only on the Org1 peer. Similarly, the `Org2MSPPrivateCollection` is only deployed on the Org2 peer. These organization specific collections are used to store the appraisal value of the asset. This allows the owner of the asset to keep the value of the asset private from other organizations on the channel. The `"endorsementPolicy"` parameter is used to create a collection specific endorsement policy. Each update to `Org1MSPPrivateCollection` or `Org2MSPPrivateCollection` needs to be endorsed by the organization that stores the collection on their peers.
These three collections are used to transfer the asset between Org1 and Org2. In the tutorial, you will use the private data smart contract to complete the following transfer scenario:
- A member of Org1 uses the `CreateAsset` function to create a new asset. The `CreateAsset` function reads the certificate information of the client identity that submitted the transaction using the `GetClientIdentity.GetID()` API and creates a new asset with the client identity as the asset owner. The main details of the asset, including the owner, are stored in the `assetCollection` collection. The asset is also created with an appraised value. The appraised value is used by each participant to agree to the transfer of the asset, and is only stored in each organization specific collection. Because the asset is created by a member of Org1, the initial appraisal value agreed by the asset owner is stored in the `Org1MSPPrivateCollection`.
- A member of Org2 creates an agreement to trade using the `AgreeToTransfer` function. The potential buyer uses this function to agree to an appraisal value. The value is stored in `Org2MSPPrivateCollection`, and can only read by a member of Org2. The `AgreeToTransfer` function also uses the `GetClientIdentity().GetID()` API to read the client identity that is agreeing to the Transfer. The **TransferAgreement** is stored in the `assetCollection` as a key with the name "transferAgreement{assetID}"
- After the member of Org2 has agreed to the transfer, the asset owner can transfer the asset to the buyer using the `TransferAsset` function. The smart contract completes a couple of checks before the asset is transferred:
- The transfer request is submitted by the owner of the asset.
- The smart contract uses the `GetPrivateDataHash()` function to check that the hash of the asset appraisal value in `Org1MSPPrivateCollection` matches the hash of the appraisal value in the `Org2MSPPrivateCollection`. If the hashes are the same, it confirms that the owner and the interested buyer have agreed to the same asset value.
If both conditions are met, the transfer function will get the client ID of the buyer from the transfer agreement and make the buyer the new owner of the asset. The transfer function will also delete the asset appraisal value from the collection of the former owner, as well as remove the transfer agreement from the `assetCollection`.
The private data asset transfer enabled by this smart contract is meant to demonstrate the use private data collections. For an example of a more realistic transfer scenario, see the [secure asset transfer smart contract](../../asset-transfer-secured-agreement/chaincode-go).
## Deploy the smart contract to the test network
You can run the private data transfer scenario using the Fabric test network. Open a command terminal and navigate to test network directory in your local clone of the `fabric-samples`. We will operate from the `test-network` directory for the remainder of the tutorial.
```
cd fabric-samples/test-network
```
Run the following command to deploy the test network:
```
./network.sh up createChannel -ca -s couchdb
```
The test network is deployed with two peer organizations. The `createChannel` flag deploys the network with a single channel named `mychannel` with Org1 and Org2 as channel members. The `-ca` flag is used to deploy the network using certificate authorities. This allows you to use each organization's CA to register and enroll new users for this tutorial.
## Deploy the smart contract to the channel
You can use the test network script to deploy the private data smart contract to the channel that was just created. Deploy the smart contract to `mychannel` using the following command:
```
./network.sh deployCC -ccn private -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cccg ../asset-transfer-private-data/chaincode-go/collections_config.json
```
The above command deploys the go chaincode with short name `private`, and specifies the private data collection configuration from file `collections_config.json` using `-cccg` flag.
Note that we are using the `-ccep` flag to deploy the private data smart contract with a chaincode endorsement policy of `"OR('Org1MSP.peer','Org2MSP.peer')"`. This allows Org1 and Org2 to create an asset without receiving an endorsement from the other organization.
Now you are ready to call the deployed smart contract.
Note that this sample workflow steps below, can also be executed via the application at `asset-transfer-private-data/application-javascript` folder, in fewer steps. To execute the workflow via CLI, read on.
## Register identities
The private data transfer smart contract supports ownership by individual identities that belong to the network. In our scenario, the owner of the asset will be a member of Org1, while the buyer will belong to Org2. To highlight the connection between the `GetClientIdentity().GetID()` API and the information within a user's certificate, we will register two new identities using the Org1 and Org2 Certificate Authorities (CA's), and then use the CA's to generate each identity's certificate and private key.
First, we need to set the following environment variables to use the the Fabric CA client:
```
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
```
We will use the Org1 CA to create the identity asset owner. Set the Fabric CA client home to the MSP of the Org1 CA admin (this identity was generated by the test network script):
```
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org1.example.com/
```
You can register a new owner client identity using the `fabric-ca-client` tool:
```
fabric-ca-client register --caname ca-org1 --id.name owner --id.secret ownerpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
```
You can now generate the identity certificates and MSP folder by providing the enroll name and secret to the enroll command:
```
fabric-ca-client enroll -u https://owner:ownerpw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org1/tls-cert.pem
```
Run the command below to copy the Node OU configuration file into the owner identity MSP folder.
```
cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp/config.yaml
```
We can now use the Org2 CA to create the buyer identity. Set the Fabric CA client home the Org2 CA admin:
```
export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/
```
You can register a new owner client identity using the `fabric-ca-client` tool:
```
fabric-ca-client register --caname ca-org2 --id.name buyer --id.secret buyerpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
```
We can now enroll to generate the identity MSP folder:
```
fabric-ca-client enroll -u https://buyer:buyerpw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem
```
Run the command below to copy the Node OU configuration file into the buyer identity MSP folder.
```
cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp/config.yaml
```
## Create an asset
Now that we have created the identity of the asset owner, we can invoke the private data smart contract to create a new asset. Use the following environment variables to operate the `peer` CLI as the owner identity from Org1.
```
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
```
Run the following command to define the asset properties:
```
export ASSET_PROPERTIES=$(echo -n "{\"objectType\":\"asset\",\"assetID\":\"asset1\",\"color\":\"green\",\"size\":20,\"appraisedValue\":100}" | base64 | tr -d \\n)
```
We can then invoke the smart contract to create the new asset:
```
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"CreateAsset","Args":[]}' --transient "{\"asset_properties\":\"$ASSET_PROPERTIES\"}"
```
The command above uses the transient data flag, `--transient`, to provide the asset details to the smart contract. Transient data is not part of the transaction read/write set, and as result is not stored on the channel ledger.
Note that command above only targets the Org1 peer. The `CreateAsset` transactions writes to two collections, `assetCollection` and `Org1MSPPrivateCollection`. The `Org1MSPPrivateCollection` requires and endorsement from the Org1 peer in order to write to the collection, while the `assetCollection` inherits the endorsement policy of the chaincode, `"OR('Org1MSP.peer','Org2MSP.peer')"`. An endorsement from the Org1 peer can meet both endorsement policies and is able to create an asset without an endorsement from Org2.
We can read the main details of the asset that was created by using the `ReadAsset` function to query the `assetCollection` collection:
```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'
```
When successful, the command will return the following result:
```
{"objectType":"asset","assetID":"asset1","color":"green","size":20,"owner":"eDUwOTo6Q049b3duZXIsT1U9Y2xpZW50LE89SHlwZXJsZWRnZXIsU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUzo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="}
```
The `"owner"` of the asset is the identity that created the asset by invoking the smart contract. The `GetClientIdentity().GetID()` API reads the common name and issuer of the identity certificate. You can see that information by decoding the owner string out of base64 format:
```
echo eDUwOTo6Q049b3duZXIsT1U9Y2xpZW50LE89SHlwZXJsZWRnZXIsU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUzo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw | base64 --decode
```
The result will show the common name and issuer of the owner certificate:
```
x509::CN=owner,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=Umacbook-air:test-network
```
A member of Org1 can also read the private asset appraisal value that is stored in the `Org1MSPPrivateCollection` on the Org1 peer:
```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
```
The query will return the value of the asset:
```
{"assetID":"asset1","appraisedValue":100}
```
### Buyer from Org2 agrees to buy the asset
The buyer identity from Org2 is interested in buying the asset. In a new terminal, set the following environment variables to operate as the buyer:
```
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/buyer@org2.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:9051
```
Now that we are operating as a member of Org2, we can demonstrate that the asset appraisal is not stored in Org2MSPPrivateCollection, on the Org2 peer:
```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'
```
The empty response shows that, the asset1 private details, does not exist in buyer private collection.
Nor can a member of Org2, able to read the Org1 private data collection:
```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
```
By setting `"memberOnlyRead": true` in the collection configuration file, we specify that only members of Org1 can read data from the collection. A Org2 member who tries to read the collection would only get the following response.
```
Error: endorsement failure during query. response: status:500 message:"failed to read from asset details GET_STATE failed: transaction ID: 10d39a7d0b340455a19ca4198146702d68d884d41a0e60936f1599c1ddb9c99d: tx creator does not have read access permission on privatedata in chaincodeName:private collectionName: Org1MSPPrivateCollection"
```
To purchase the asset, the buyer needs to agree to the same value as the asset owner. The agreed value will be stored in the `Org2MSPDetailsCollection` collection on the Org2 peer. Run the following command to agree to the appraised value of 100:
```
export ASSET_VALUE=$(echo -n "{\"assetID\":\"asset1\",\"appraisedValue\":100}" | base64 | tr -d \\n)
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"AgreeToTransfer","Args":[]}' --transient "{\"asset_value\":\"$ASSET_VALUE\"}"
```
The buyer can now query the value they agreed to in the Org2 private data collection:
```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org2MSPPrivateCollection","asset1"]}'
```
The invoke will return the following value:
```
{"assetID":"asset1","appraisedValue":100}
```
## Org1 member transfers the asset to Org2
Now that buyer has agreed to buy the asset for appraised value, the owner from Org1 can transfer the asset to Org2. In the first terminal (with the following environment variables to operate as Org1):
```
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/owner@org1.example.com/msp
export CORE_PEER_TLS_ROOTCERT_FILE=${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_ADDRESS=localhost:7051
```
Now that buyer has agreed to buy the asset for appraised value, the owner from Org1 can read the data added by `AgreeToTransfer` to see buyer identity.
```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadTransferAgreement","Args":["asset1"]}'
```
The owner from Org1 can now transfer the asset to Org2. To transfer the asset, the owner needs to pass the MSP ID of new asset owner Org. The transfer function will read the client ID of the interested buyer user from the transfer agreement.
```
export ASSET_OWNER=$(echo -n "{\"assetID\":\"asset1\",\"buyerMSP\":\"Org2MSP\"}" | base64 | tr -d \\n)
```
The owner of the asset needs to initiate the transfer.
```
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"TransferAsset","Args":[]}' --transient "{\"asset_owner\":\"$ASSET_OWNER\"}" --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
```
You can ReadAsset `asset1` to see the results of the transfer.
```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAsset","Args":["asset1"]}'
```
The results will show that the buyer identity now owns the asset:
```
{"objectType":"asset","assetID":"asset1","color":"green","size":20,"owner":"eDUwOTo6Q049YnV5ZXIsT1U9Y2xpZW50LE89SHlwZXJsZWRnZXIsU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUzo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"}
```
You can base64 decode the `"owner"` to see that it is the buyer identity:
```
x509::CN=buyer,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UKmacbook-air
```
You can also confirm that transfer removed the private details from the Org1 collection:
```
peer chaincode query -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ${PWD}/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n private -c '{"function":"ReadAssetPrivateDetails","Args":["Org1MSPPrivateCollection","asset1"]}'
```
Your query will return empty result, since the asset private data is removed from the Org1 private data collection.
## Clean up
When you are finished, you can bring down the test network. The command will remove all the nodes of the test network, and delete any ledger data that you created:
```
./network.sh down
```

View file

@ -19,7 +19,7 @@ state-based endorsement, visit the
[Endorsement Policies](https://hyperledger-fabric.readthedocs.io/en/release-2.2/endorsement-policies.html) [Endorsement Policies](https://hyperledger-fabric.readthedocs.io/en/release-2.2/endorsement-policies.html)
topic in the Fabric documentation. topic in the Fabric documentation.
The implementation provided by State Based interface creates a policy which requires signatures from all the Org principals added, and hence is equivalent to an AND policy. For other advanced State Based policy implementations which are not supported by State Based interface directly like OR or NOutOf policies, please refer to method implementations setAssetStateBasedEndorsementWithNOutOfPolicy(), which can be used as an alternative for setAssetStateBasedEndorsement() inside asset-transfer-sbe smart contracts. The implementation provided by State Based interface creates a policy which requires signatures from all the Org principals added, and hence is equivalent to an AND policy. For other advanced State Based policy implementations which are not supported by State Based interface directly like OR or NOutOf policies, please refer to method implementations setStateBasedEndorsementNOutOf(), which can be used as an alternative for setStateBasedEndorsement() inside asset-transfer-sbe smart contracts.
## About the Sample ## About the Sample

View file

@ -70,11 +70,11 @@ public final class AssetContract implements ContractInterface {
String assetJSON = genson.serialize(asset); String assetJSON = genson.serialize(asset);
stub.putStringState(assetId, assetJSON); stub.putStringState(assetId, assetJSON);
// Set the endorsement policy of the assetId Key, such that current owner Org Peer is required to endorse future updates // Set the endorsement policy of the assetId Key, such that current owner Org is required to endorse future updates
setAssetStateBasedEndorsement(ctx, assetId, new String[]{ownerOrg}); setStateBasedEndorsement(ctx, assetId, new String[]{ownerOrg});
// Optionally, set the endorsement policy of the assetId Key, such that any 1(N) out of the Org's specified can endorse future updates // Optionally, set the endorsement policy of the assetId Key, such that any 1 Org (N) out of the specified Orgs can endorse future updates
// setAssetStateBasedEndorsementWithNOutOfPolicy(ctx, assetId, 1, new String[]{"Org1MSP", "Org2MSP"}); // setStateBasedEndorsementNOutOf(ctx, assetId, 1, new String[]{"Org1MSP", "Org2MSP"});
return asset; return asset;
} }
@ -164,7 +164,11 @@ public final class AssetContract implements ContractInterface {
String updatedAssetJSON = genson.serialize(asset); String updatedAssetJSON = genson.serialize(asset);
stub.putStringState(assetId, updatedAssetJSON); stub.putStringState(assetId, updatedAssetJSON);
setAssetStateBasedEndorsement(ctx, assetId, new String[]{newOwnerOrg}); // Re-Set the endorsement policy of the assetId Key, such that a new owner Org Peer is required to endorse future updates
setStateBasedEndorsement(ctx, assetId, new String[]{newOwnerOrg});
// Optionally, set the endorsement policy of the assetId Key, such that any 1 Org (N) out of the specified Orgs can endorse future updates
// setStateBasedEndorsementNOutOf(ctx, assetId, 1, new String[]{"Org1MSP", "Org2MSP"});
return asset; return asset;
} }
@ -195,13 +199,13 @@ public final class AssetContract implements ContractInterface {
/** /**
* Sets an endorsement policy to the assetId Key. * Sets an endorsement policy to the assetId Key.
* Enforces that the owner Org Peers must endorse future update transactions for the specified assetId Key. * Enforces that the owner Org must endorse future update transactions for the specified assetId Key.
* *
* @param ctx the transaction context * @param ctx the transaction context
* @param assetId the id of the asset * @param assetId the id of the asset
* @param ownerOrgs the list of Owner Org MSPID's * @param ownerOrgs the list of Owner Org MSPID's
*/ */
private static void setAssetStateBasedEndorsement(final Context ctx, final String assetId, final String[] ownerOrgs) { private static void setStateBasedEndorsement(final Context ctx, final String assetId, final String[] ownerOrgs) {
StateBasedEndorsement stateBasedEndorsement = StateBasedEndorsementFactory.getInstance().newStateBasedEndorsement(null); StateBasedEndorsement stateBasedEndorsement = StateBasedEndorsementFactory.getInstance().newStateBasedEndorsement(null);
stateBasedEndorsement.addOrgs(StateBasedEndorsement.RoleType.RoleTypeMember, ownerOrgs); stateBasedEndorsement.addOrgs(StateBasedEndorsement.RoleType.RoleTypeMember, ownerOrgs);
ctx.getStub().setStateValidationParameter(assetId, stateBasedEndorsement.policy()); ctx.getStub().setStateValidationParameter(assetId, stateBasedEndorsement.policy());
@ -209,21 +213,21 @@ public final class AssetContract implements ContractInterface {
/** /**
* Sets an endorsement policy to the assetId Key. * Sets an endorsement policy to the assetId Key.
* Enforces that any N out of the Org's Peers specified must endorse future update transactions for the specified assetId Key. * Enforces that a given number of Orgs (N) out of the specified Orgs must endorse future update transactions for the specified assetId Key.
* *
* @param ctx the transaction context * @param ctx the transaction context
* @param assetId the id of the asset * @param assetId the id of the asset
* @param nOrgs the number of N Orgs to endorse out of the list of Orgs provided * @param nOrgs the number of N Orgs to endorse out of the list of Orgs provided
* @param ownerOrgs the list of Owner Org MSPID's * @param ownerOrgs the list of Owner Org MSPID's
*/ */
private static void setAssetStateBasedEndorsementWithNOutOfPolicy(final Context ctx, final String assetId, final int nOrgs, final String[] ownerOrgs) { private static void setStateBasedEndorsementNOutOf(final Context ctx, final String assetId, final int nOrgs, final String[] ownerOrgs) {
ctx.getStub().setStateValidationParameter(assetId, policy(nOrgs, Arrays.asList(ownerOrgs))); ctx.getStub().setStateValidationParameter(assetId, policy(nOrgs, Arrays.asList(ownerOrgs)));
} }
/** /**
* Create the policy such that it requires any N signature's from all of the principals provided * Create a policy that requires a given number (N) of Org principals signatures out of the provided list of Orgs
* *
* @param nOrgs the number of N Org signature's to endorse out of the list of Orgs provided * @param nOrgs the number of Org principals signatures required to endorse (out of the provided list of Orgs)
* @param mspids the list of Owner Org MSPID's * @param mspids the list of Owner Org MSPID's
*/ */
private static byte[] policy(final int nOrgs, final List<String> mspids) { private static byte[] policy(final int nOrgs, final List<String> mspids) {

View file

@ -5,6 +5,7 @@
import { Context, Contract, Info, Transaction } from 'fabric-contract-api'; import { Context, Contract, Info, Transaction } from 'fabric-contract-api';
import { Asset } from './asset'; import { Asset } from './asset';
import { KeyEndorsementPolicy } from 'fabric-shim'; import { KeyEndorsementPolicy } from 'fabric-shim';
import * as fabprotos from 'fabric-shim/bundle';
@Info({title: 'AssetContract', description: 'Asset Transfer Smart Contract, using State Based Endorsement(SBE), implemented in TypeScript' }) @Info({title: 'AssetContract', description: 'Asset Transfer Smart Contract, using State Based Endorsement(SBE), implemented in TypeScript' })
export class AssetContract extends Contract { export class AssetContract extends Contract {
@ -26,8 +27,12 @@ export class AssetContract extends Contract {
const buffer = Buffer.from(JSON.stringify(asset)); const buffer = Buffer.from(JSON.stringify(asset));
// Create the asset // Create the asset
await ctx.stub.putState(assetId, buffer); await ctx.stub.putState(assetId, buffer);
// Set the endorsement policy of the assetId Key, such that current owner Org Peer is required to endorse future updates
await AssetContract.setAssetStateBasedEndorsement(ctx, asset.ID, [ownerOrg]); // Set the endorsement policy of the assetId Key, such that current owner Org is required to endorse future updates
await AssetContract.setStateBasedEndorsement(ctx, assetId, [ownerOrg]);
// Optionally, set the endorsement policy of the assetId Key, such that any 1 Org (N) out of the specified Orgs can endorse future updates
// await AssetContract.setStateBasedEndorsementNOutOf(ctx, assetId, 1, ["Org1MSP", "Org2MSP"]);
} }
// ReadAsset returns asset with given assetId // ReadAsset returns asset with given assetId
@ -78,7 +83,10 @@ export class AssetContract extends Contract {
// Update the asset // Update the asset
await ctx.stub.putState(assetId, Buffer.from(JSON.stringify(asset))); await ctx.stub.putState(assetId, Buffer.from(JSON.stringify(asset)));
// Re-Set the endorsement policy of the assetId Key, such that a new owner Org Peer is required to endorse future updates // Re-Set the endorsement policy of the assetId Key, such that a new owner Org Peer is required to endorse future updates
await AssetContract.setAssetStateBasedEndorsement(ctx, asset.ID, [newOwnerOrg]); await AssetContract.setStateBasedEndorsement(ctx, asset.ID, [newOwnerOrg]);
// Optionally, set the endorsement policy of the assetId Key, such that any 1 Org (N) out of the specified Orgs can endorse future updates
// await AssetContract.setStateBasedEndorsementNOutOf(ctx, assetId, 1, ["Org1MSP", "Org2MSP"]);
} }
// AssetExists returns true when asset with given ID exists // AssetExists returns true when asset with given ID exists
@ -87,16 +95,58 @@ export class AssetContract extends Contract {
return (!!buffer && buffer.length > 0); return (!!buffer && buffer.length > 0);
} }
// setAssetStateBasedEndorsement sets an endorsement policy to the assetId Key // getClientOrgId gets the client's OrgId (MSPID)
// setAssetStateBasedEndorsement enforces that the owner Org Peers must endorse future update transactions for the specified assetId Key private static getClientOrgId(ctx: Context): string {
private static async setAssetStateBasedEndorsement(ctx: Context, assetId: string, ownerOrgs: string[]): Promise<void> { return ctx.clientIdentity.getMSPID();
}
// setStateBasedEndorsement sets an endorsement policy to the assetId Key
// setStateBasedEndorsement enforces that the owner Org must endorse future update transactions for the specified assetId Key
private static async setStateBasedEndorsement(ctx: Context, assetId: string, ownerOrgs: string[]): Promise<void> {
const ep = new KeyEndorsementPolicy(); const ep = new KeyEndorsementPolicy();
ep.addOrgs('MEMBER', ...ownerOrgs); ep.addOrgs('MEMBER', ...ownerOrgs);
await ctx.stub.setStateValidationParameter(assetId, ep.getPolicy()); await ctx.stub.setStateValidationParameter(assetId, ep.getPolicy());
} }
// getClientOrgId gets the client's OrgId (MSPID) // setStateBasedEndorsementNOutOf sets an endorsement policy to the assetId Key
private static getClientOrgId(ctx: Context): string { // setStateBasedEndorsementNOutOf enforces that a given number of Orgs (N) out of the specified Orgs must endorse future update transactions for the specified assetId Key.
return ctx.clientIdentity.getMSPID(); private static async setStateBasedEndorsementNOutOf(ctx: Context, assetId: string, nOrgs:number, ownerOrgs: string[]): Promise<void> {
await ctx.stub.setStateValidationParameter(assetId, AssetContract.policy(nOrgs, ownerOrgs));
}
// Create a policy that requires a given number (N) of Org principals signatures out of the provided list of Orgs
private static policy(nOrgs: number, mspIds: string[]): Uint8Array {
const principals = [];
const sigsPolicies = [];
mspIds.forEach((mspId, i) => {
const mspRole = {
role: fabprotos.common.MSPRole.MSPRoleType.MEMBER,
mspIdentifier: mspId
};
const principal = {
principalClassification: fabprotos.common.MSPPrincipal.Classification.ROLE,
principal: fabprotos.common.MSPRole.encode(mspRole).finish()
};
principals.push(principal);
const signedBy = {
signedBy: i,
};
sigsPolicies.push(signedBy);
});
// create the policy such that it requires any N signature's from all of the principals provided
const allOf = {
n: nOrgs,
rules: sigsPolicies
};
const noutof = {
nOutOf: allOf
};
const spe = {
version: 0,
rule: noutof,
identities: principals
};
return fabprotos.common.SignaturePolicyEnvelope.encode(spe).finish();
} }
} }

416
auction/README.md Normal file
View file

@ -0,0 +1,416 @@
## Simple blind auction sample
The simple blind auction sample uses Hyperledger Fabric to run an auction where bids are kept private until the auction period is over. Instead of displaying the full bid on the public ledger, buyers can only see hashes of other bids while bidding is underway. This prevents buyers from changing their bids in response to bids submitted by others. After the bidding period ends, participants reveal their bid to try to win the auction. The organizations participating in the auction verify that a revealed bid matches the hash on the public ledger. Whichever has the highest bid wins.
A user that wants to sell one item can use the smart contract to create an auction. The auction is stored on the channel ledger and can be read by all channel members. The auctions created by the smart contract are run in three steps:
1. Each auction is created with the status **open**. While the auction is open, buyers can add new bids to the auction. The full bids of each buyer are stored in the implicit private data collections of their organization. After the bid is created, the bidder can submit the hash of the bid to the auction. A bid is added to the auction in two steps because the transaction that creates the bid only needs to be endorsed by a peer of the bidders organization, while a transaction that updates the auction may need to be endorsed by multiple organizations. When the bid is added to the auction, the bidder's organization is added to the list of organizations that need to endorse any updates to the auction.
2. The auction is **closed** to prevent additional bids from being added to the auction. After the auction is closed, bidders that submitted bids to the auction can reveal their full bid. Only revealed bids can win the auction.
3. The auction is **ended** to calculate the winner from the set of revealed bids. All organizations participating in the auction calculate the price that clears the auction and the winning bid. The seller can end the auction only if all bidding organizations endorse the same winner and price.
Before endorsing the transaction that ends the auction, each organization queries the implicit private data collection on their peers to check if any organization member has a winning bid that has not yet been revealed. If a winning bid is found, the organization will withhold their endorsement and prevent the auction from being closed. This prevents the seller from ending the auction prematurely, or colluding with buyers to end the auction at an artificially low price.
The sample uses several Fabric features to make the auction private and secure. Bids are stored in private data collections to prevent bids from being distributed to other peers in the channel. When bidding is closed, the auction smart contract uses the `GetPrivateDataHash()` API to verify that the bid stored in private data is the same bid that is being revealed. State based endorsement is used to add the organization of each bidder to the auction endorsement policy. The smart contract uses the `GetClientIdentity.GetID()` API to ensure that only the potential buyer can read their bid from private state and only the seller can close or end the auction.
This tutorial uses the auction smart contract in a scenario where one seller wants to auction a painting. Four potential buyers from two different organizations will submit bids to the auction and try to win the auction.
## Deploy the chaincode
We will run the auction smart contract using the Fabric test network. Open a command terminal and navigate to the test network directory:
```
cd fabric-samples/test-network
```
You can then run the following command to deploy the test network.
```
./network.sh up createChannel -ca
```
Note that we use the `-ca` flag to deploy the network using certificate authorities. We will use the CA to register and enroll our sellers and buyers.
Run the following command to deploy the auction smart contract. We will override the default endorsement policy to allow any channel member to create an auction without requiring an endorsement from another organization.
```
./network.sh deployCC -ccn auction -ccp ../auction/chaincode-go/ -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
```
## Install the application dependencies
We will interact with the auction smart contract through a set of Node.js applications. Change into the `application-javascript` directory:
```
cd fabric-samples/auction/application-javascript
```
From this directory, run the following command to download the application dependencies:
```
npm install
```
## Register and enroll the application identities
To interact with the network, you will need to enroll the Certificate Authority administrators of Org1 and Org2. You can use the `enrollAdmin.js` program for this task. Run the following command to enroll the Org1 admin:
```
node enrollAdmin.js org1
```
You should see the logs of the admin wallet being created on your local file system. Now run the command to enroll the CA admin of Org2:
```
node enrollAdmin.js org2
```
We can use the CA admins of both organizations to register and enroll the identities of the seller that will create the auction and the bidders who will try to purchase the painting.
Run the following command to register and enroll the seller identity that will create the auction. The seller will belong to Org1.
```
node registerEnrollUser.js org1 seller
```
You should see the logs of the seller wallet being created as well. Run the following commands to register and enroll 2 bidders from Org1 and another 2 bidders from Org2:
```
node registerEnrollUser.js org1 bidder1
node registerEnrollUser.js org1 bidder2
node registerEnrollUser.js org2 bidder3
node registerEnrollUser.js org2 bidder4
```
## Create the auction
The seller from Org1 would like to create an auction to sell a vintage Matchbox painting. Run the following command to use the seller wallet to run the `createAuction.js` application. The program will submit a transaction to the network that creates the auction on the channel ledger. The organization and identity name are passed to the application to use the wallet that was created by the `registerEnrollUser.js` application. The seller needs to provide an ID for the auction and the item to be sold to create the auction:
```
node createAuction.js org1 seller PaintingAuction painting
```
After the transaction is complete, the `createAuction.js` application will query the auction stored in the public channel ledger:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
"organizations": [
"Org1MSP"
],
"privateBids": {},
"revealedBids": {},
"winner": "",
"price": 0,
"status": "open"
}
```
The smart contract uses the `GetClientIdentity().GetID()` API to read identity that creates the auction and defines that identity as the auction `"seller"`. You can see the seller information by decoding the `"seller"` string out of base64 format:
```
echo eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT | base64 --decode
```
The result is the name and issuer of the seller's certificate:
```
x509::CN=org1admin,OU=admin,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org1.example.com,O=org1.example.com,L=Durham,ST=North Carolina,C=USn
```
## Bid on the auction
We can now use the bidder wallets to submit bids to the auction:
### Bid as bidder1
Bidder1 will create a bid to purchase the painting for 800 dollars.
```
node bid.js org1 bidder1 PaintingAuction 800
```
The application will query the bid after it is created:
```
*** Result: Bid: {
"objectType": "bid",
"price": 800,
"org": "Org1MSP",
"bidder": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
}
```
The bid is stored in the Org1 implicit data collection. The `"bidder"` parameter is the information from the certificate of the user that created the bid. Only this identity will be able can query the bid from private state or reveal the bid during the auction.
The `bid.js` application also prints the bidID:
```
*** Result ***SAVE THIS VALUE*** BidID: 8ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd
```
The BidID acts as the unique identifier for the bid. This ID allows you to query the bid using the `queryBid.js` program and add the bid to the auction. Save the bidID returned by the application as an environment variable in your terminal:
```
export BIDDER1_BID_ID=8ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd
```
This value will be different for each transaction, so you will need to use the value returned in your terminal.
Now that the bid has been created, you can submit the bid to the auction. Run the following command to submit the bid that was just created:
```
node submitBid.js org1 bidder1 PaintingAuction $BIDDER1_BID_ID
```
The hash of bid will be added to the list private bids in that have been submitted to `PaintingAuction`. Storing the hash in the public auction allows users to accurately reveal the bid after bidding is closed. The application will query the auction to verify that the bid was added:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
"organizations": [
"Org1MSP"
],
"privateBids": {
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"org": "Org1MSP",
"hash": "5cb50a17b5a21c02fc01306e3e9b54f4db67e9a440552ce898bbd7daa62dce0f"
}
},
"revealedBids": {},
"winner": "",
"price": 0,
"status": "open"
}
```
### Bid as bidder2
Let's submit another bid. Bidder2 would like to purchase the painting for 500 dollars.
```
node bid.js org1 bidder2 PaintingAuction 500
```
Save the Bid ID returned by the application:
```
export BIDDER2_BID_ID=915a908c8f2c368f4a3aedd73176656af81ddfab000b11629503403f3d97b185
```
Submit bidder2's bid to the auction:
```
node submitBid.js org1 bidder2 PaintingAuction $BIDDER2_BID_ID
```
### Bid as bidder3 from Org2
Bidder3 will bid 700 dollars for the painting:
```
node bid.js org2 bidder3 PaintingAuction 700
```
Save the Bid ID returned by the application:
```
export BIDDER3_BID_ID=5e4e637c68833b178739575f6fe09820b019551a8cfbb43a4d172e0aa864dfad
```
Add bidder3's bid to the auction:
```
node submitBid.js org2 bidder3 PaintingAuction $BIDDER3_BID_ID
```
Because bidder3 belongs to Org2, submitting the bid will add Org2 to the list of participating organizations. You can see the Org2 MSP ID has been added to the list of `"organizations"` in the updated auction returned by the application:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000PaintingAuction\u00005e4e637c68833b178739575f6fe09820b019551a8cfbb43a4d172e0aa864dfad\u0000": {
"org": "Org2MSP",
"hash": "40107eab7a99dfc2f25d02b8ab840f12fd802a9f86d8d42b78d7b4409b2c15bd"
},
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"org": "Org1MSP",
"hash": "5cb50a17b5a21c02fc01306e3e9b54f4db67e9a440552ce898bbd7daa62dce0f"
},
"\u0000bid\u0000PaintingAuction\u0000915a908c8f2c368f4a3aedd73176656af81ddfab000b11629503403f3d97b185\u0000": {
"org": "Org1MSP",
"hash": "a458df18b12dffe4ae6d56a270134c2d55bd53fface034bd24381d0073d46a45"
}
},
"revealedBids": {},
"winner": "",
"price": 0,
"status": "open"
}
```
Now that a bid from Org2 has been added to the auction, any updates to the auction need to be endorsed by the Org2 peer. The applications will use `"organizations"` field to specify which organizations need to endorse submitting a new bid, revealing a bid, or updating the auction status.
### Bid as bidder4
Bidder4 from Org2 would like to purchase the painting for 900 dollars:
```
node bid.js org2 bidder4 PaintingAuction 900
```
Save the Bid ID returned by the application:
```
export BIDDER4_BID_ID=49466271ae879bd009e75a60730a12bfa986e75f263202ab81ccd3deec544a35
```
Add bidder4's bid to the auction:
```
node submitBid.js org2 bidder4 PaintingAuction $BIDDER4_BID_ID
```
## Close the auction
Now that all four bidders have joined the auction, the seller would like to close the auction and allow buyers to reveal their bids. The seller identity that created the auction needs to submit the transaction:
```
node closeAuction.js org1 seller PaintingAuction
```
The application will query the auction to allow you to verify that the auction status has changed to closed. As a test, you can try to create and submit a new bid to verify that no new bids can be added to the auction.
## Reveal bids
After the auction is closed, bidders can try to win the auction by revealing their bids. The transaction to reveal a bid needs to pass four checks:
1. The auction is closed.
2. The transaction was submitted by the identity that created the bid.
3. The hash of the revealed bid matches the hash of the bid on the channel ledger. This confirms that the bid is the same as the bid that is stored in the private data collection.
4. The hash of the revealed bid matches the hash that was submitted to the auction. This confirms that the bid was not altered after the auction was closed.
Use the `revealBid.js` application to reveal the bid of Bidder1:
```
node revealBid.js org1 bidder1 PaintingAuction $BIDDER1_BID_ID
```
The full bid details, including the price, are now visible:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000PaintingAuction\u000049466271ae879bd009e75a60730a12bfa986e75f263202ab81ccd3deec544a35\u0000": {
"org": "Org2MSP",
"hash": "b8eaeb4422b93abdfe4ccb6aa11b745b3d1cb072a99bd3eb3618f081fb1b1f89"
},
"\u0000bid\u0000PaintingAuction\u00005e4e637c68833b178739575f6fe09820b019551a8cfbb43a4d172e0aa864dfad\u0000": {
"org": "Org2MSP",
"hash": "40107eab7a99dfc2f25d02b8ab840f12fd802a9f86d8d42b78d7b4409b2c15bd"
},
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"org": "Org1MSP",
"hash": "5cb50a17b5a21c02fc01306e3e9b54f4db67e9a440552ce898bbd7daa62dce0f"
},
"\u0000bid\u0000PaintingAuction\u0000915a908c8f2c368f4a3aedd73176656af81ddfab000b11629503403f3d97b185\u0000": {
"org": "Org1MSP",
"hash": "a458df18b12dffe4ae6d56a270134c2d55bd53fface034bd24381d0073d46a45"
}
},
"revealedBids": {
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"objectType": "bid",
"price": 800,
"org": "Org1MSP",
"bidder": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
}
},
"winner": "",
"price": 0,
"status": "closed"
}
```
Bidder3 from Org2 will also reveal their bid:
```
node revealBid.js org2 bidder3 PaintingAuction $BIDDER3_BID_ID
```
If the auction ended now, Bidder1 would win. Let's try to end the auction using the seller identity and see what happens.
```
node endAuction.js org1 seller PaintingAuction
```
The output should look something like the following:
```
--> Submit the transaction to end the auction
2020-11-06T13:16:11.591Z - warn: [TransactionEventHandler]: strategyFail: commit failure for transaction "99feade5b7ec223839200867b57d18971c3e9f923efc95aaeec720727f927366": TransactionError: Commit of transaction 99feade5b7ec223839200867b57d18971c3e9f923efc95aaeec720727f927366 failed on peer peer0.org1.example.com:7051 with status ENDORSEMENT_POLICY_FAILURE
******** FAILED to submit bid: TransactionError: Commit of transaction 99feade5b7ec223839200867b57d18971c3e9f923efc95aaeec720727f927366 failed on peer peer0.org1.example.com:7051 with status ENDORSEMENT_POLICY_FAILURE
```
Instead of ending the auction, the transaction results in an endorsement policy failure. The end of the auction needs to be endorsed by Org2. Before endorsing the transaction, the Org2 peer queries its private data collection for any winning bids that have not yet been revealed. Because Bidder4 created a bid that is above the winning price, the Org2 peer refuses to endorse the transaction that would end the auction.
Before we can end the auction, we need to reveal the bid from bidder4.
```
node revealBid.js org2 bidder4 PaintingAuction $BIDDER4_BID_ID
```
Bidder2 from Org1 would not win the auction in either case. As a result, Bidder2 decides not to reveal their bid.
## End the auction
Now that the winning bids have been revealed, we can end the auction:
```
node endAuction org1 seller PaintingAuction
```
The transaction was successfully endorsed by both Org1 and Org2, who both calculated the same price and winner. The winning bidder is listed along with the price:
```
*** Result: Auction: {
"objectType": "auction",
"item": "painting",
"seller": "eDUwOTo6Q049c2VsbGVyLE9VPWNsaWVudCtPVT1vcmcxK09VPWRlcGFydG1lbnQxOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT",
"organizations": [
"Org1MSP",
"Org2MSP"
],
"privateBids": {
"\u0000bid\u0000PaintingAuction\u000049466271ae879bd009e75a60730a12bfa986e75f263202ab81ccd3deec544a35\u0000": {
"org": "Org2MSP",
"hash": "b8eaeb4422b93abdfe4ccb6aa11b745b3d1cb072a99bd3eb3618f081fb1b1f89"
},
"\u0000bid\u0000PaintingAuction\u00005e4e637c68833b178739575f6fe09820b019551a8cfbb43a4d172e0aa864dfad\u0000": {
"org": "Org2MSP",
"hash": "40107eab7a99dfc2f25d02b8ab840f12fd802a9f86d8d42b78d7b4409b2c15bd"
},
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"org": "Org1MSP",
"hash": "5cb50a17b5a21c02fc01306e3e9b54f4db67e9a440552ce898bbd7daa62dce0f"
},
"\u0000bid\u0000PaintingAuction\u0000915a908c8f2c368f4a3aedd73176656af81ddfab000b11629503403f3d97b185\u0000": {
"org": "Org1MSP",
"hash": "a458df18b12dffe4ae6d56a270134c2d55bd53fface034bd24381d0073d46a45"
}
},
"revealedBids": {
"\u0000bid\u0000PaintingAuction\u000049466271ae879bd009e75a60730a12bfa986e75f263202ab81ccd3deec544a35\u0000": {
"objectType": "bid",
"price": 900,
"org": "Org2MSP",
"bidder": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"
},
"\u0000bid\u0000PaintingAuction\u00005e4e637c68833b178739575f6fe09820b019551a8cfbb43a4d172e0aa864dfad\u0000": {
"objectType": "bid",
"price": 700,
"org": "Org2MSP",
"bidder": "eDUwOTo6Q049YmlkZGVyMyxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL"
},
"\u0000bid\u0000PaintingAuction\u00008ef83011a5fb791f75ed008337839426f6b87981519e5d58ef5ada39c3044edd\u0000": {
"objectType": "bid",
"price": 800,
"org": "Org1MSP",
"bidder": "eDUwOTo6Q049YmlkZGVyMSxPVT1jbGllbnQrT1U9b3JnMStPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMS5leGFtcGxlLmNvbSxPPW9yZzEuZXhhbXBsZS5jb20sTD1EdXJoYW0sU1Q9Tm9ydGggQ2Fyb2xpbmEsQz1VUw=="
}
},
"winner": "eDUwOTo6Q049YmlkZGVyNCxPVT1jbGllbnQrT1U9b3JnMitPVT1kZXBhcnRtZW50MTo6Q049Y2Eub3JnMi5leGFtcGxlLmNvbSxPPW9yZzIuZXhhbXBsZS5jb20sTD1IdXJzbGV5LFNUPUhhbXBzaGlyZSxDPVVL",
"price": 900,
"status": "ended"
}
```
## Clean up
When your are done using the auction smart contract, you can bring down the network and clean up the environment. In the `auction/application-javascript` directory, run the following command to remove the wallets used to run the applications:
```
rm -rf wallet
```
You can then navigate to the test network directory and bring down the network:
````
cd ../../test-network/
./network.sh down
````

View file

@ -0,0 +1,112 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function bid(ccp,wallet,user,orgMSP,auctionID,price) {
try {
const gateway = new Gateway();
//connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: get your client ID');
let bidder = await contract.evaluateTransaction('GetID');
console.log('*** Result: Bidder ID is ' + bidder.toString());
let bidData = { objectType: 'bid', price: parseInt(price), org: orgMSP, bidder: bidder.toString()};
let statefulTxn = contract.createTransaction('Bid');
statefulTxn.setEndorsingOrganizations(orgMSP);
let tmapData = Buffer.from(JSON.stringify(bidData));
statefulTxn.setTransient({
bid: tmapData
});
let bidID = statefulTxn.getTransactionId();
console.log('\n--> Submit Transaction: Create the bid that is stored in your organization\'s private data collection');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('*** Result ***SAVE THIS VALUE*** BidID: ' + bidID.toString());
console.log('\n--> Evaluate Transaction: read the bid that was just created');
let result = await contract.evaluateTransaction('QueryBid',auctionID,bidID);
console.log('*** Result: Bid: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
async function main() {
try {
if (process.argv[2] == undefined || process.argv[3] == undefined
|| process.argv[4] == undefined || process.argv[5] == undefined) {
console.log("Usage: node bid.js org userID auctionID price");
process.exit(1);
}
const org = process.argv[2]
const user = process.argv[3];
const auctionID = process.argv[4];
const price = process.argv[5];
if (org == 'Org1' || org == 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await bid(ccp,wallet,user,orgMSP,auctionID,price);
}
else if (org == 'Org2' || org == 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await bid(ccp,wallet,user,orgMSP,auctionID,price);
} else {
console.log("Usage: node bid.js org userID auctionID price");
console.log("Org must be Org1 or Org2");
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
process.exit(1);
}
}
main();

View file

@ -0,0 +1,109 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function closeAuction(ccp,wallet,user,auctionID) {
try {
const gateway = new Gateway();
//connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
// Query the auction to get the list of endorsing orgs.
//console.log('\n--> Evaluate Transaction: query the auction you want to close');
let auctionString = await contract.evaluateTransaction('QueryAuction',auctionID);
//console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
var auctionJSON = JSON.parse(auctionString);
let statefulTxn = contract.createTransaction('CloseAuction');
if (auctionJSON.organizations.length == 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0],auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
console.log('\n--> Submit Transaction: close auction');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the updated auction');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main() {
try {
if (process.argv[2] == undefined || process.argv[3] == undefined
|| process.argv[4] == undefined) {
console.log("Usage: node closeAuction.js org userID auctionID");
process.exit(1);
}
const org = process.argv[2]
const user = process.argv[3];
const auctionID = process.argv[4];
if (org == 'Org1' || org == 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await closeAuction(ccp,wallet,user,auctionID);
}
else if (org == 'Org2' || org == 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await closeAuction(ccp,wallet,user,auctionID);
} else {
console.log("Usage: node closeAuction.js org userID auctionID ");
console.log("Org must be Org1 or Org2");
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -0,0 +1,93 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function createAuction(ccp,wallet,user,auctionID,item) {
try {
const gateway = new Gateway();
//connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
let statefulTxn = contract.createTransaction('CreateAuction');
console.log('\n--> Submit Transaction: Propose a new auction');
await statefulTxn.submit(auctionID,item);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the auction that was just created');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
}
}
async function main() {
try {
if (process.argv[2] == undefined || process.argv[3] == undefined
|| process.argv[4] == undefined || process.argv[5] == undefined) {
console.log("Usage: node createAuction.js org userID auctionID item");
process.exit(1);
}
const org = process.argv[2]
const user = process.argv[3];
const auctionID = process.argv[4];
const item = process.argv[5];
if (org == 'Org1' || org == 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await createAuction(ccp,wallet,user,auctionID,item);
}
else if (org == 'Org2' || org == 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await createAuction(ccp,wallet,user,auctionID,item);
} else {
console.log("Usage: node createAuction.js org userID auctionID item");
console.log("Org must be Org1 or Org2");
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();

View file

@ -0,0 +1,109 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function endAuction(ccp,wallet,user,auctionID) {
try {
const gateway = new Gateway();
//connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
// Query the auction to get the list of endorsing orgs.
//console.log('\n--> Evaluate Transaction: query the auction you want to end');
let auctionString = await contract.evaluateTransaction('QueryAuction',auctionID);
//console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
var auctionJSON = JSON.parse(auctionString);
let statefulTxn = contract.createTransaction('EndAuction');
if (auctionJSON.organizations.length == 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0],auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
console.log('\n--> Submit the transaction to end the auction');
await statefulTxn.submit(auctionID);
console.log('*** Result: committed');
console.log('\n--> Evaluate Transaction: query the updated auction');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main() {
try {
if (process.argv[2] == undefined || process.argv[3] == undefined
|| process.argv[4] == undefined) {
console.log("Usage: node endAuction.js org userID auctionID");
process.exit(1);
}
const org = process.argv[2]
const user = process.argv[3];
const auctionID = process.argv[4];
if (org == 'Org1' || org == 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await endAuction(ccp,wallet,user,auctionID);
}
else if (org == 'Org2' || org == 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await endAuction(ccp,wallet,user,auctionID);
} else {
console.log("Usage: node endAuction.js org userID auctionID");
console.log("Org must be Org1 or Org2");
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -0,0 +1,76 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, enrollAdmin } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const mspOrg1 = 'Org1MSP';
const mspOrg2 = 'Org2MSP';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function connectToOrg1CA() {
console.log('\n--> Enrolling the Org1 CA admin');
const ccpOrg1 = buildCCPOrg1();
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
const walletPathOrg1 = path.join(__dirname, 'wallet/org1');
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
await enrollAdmin(caOrg1Client, walletOrg1, mspOrg1);
}
async function connectToOrg2CA() {
console.log('\n--> Enrolling the Org2 CA admin');
const ccpOrg2 = buildCCPOrg2();
const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com');
const walletPathOrg2 = path.join(__dirname, 'wallet/org2');
const walletOrg2 = await buildWallet(Wallets, walletPathOrg2);
await enrollAdmin(caOrg2Client, walletOrg2, mspOrg2);
}
async function main() {
if (process.argv[2] == undefined) {
console.log("Usage: node enrollAdmin.js Org");
process.exit(1);
}
const org = process.argv[2];
try {
if (org == 'Org1' || org == 'org1') {
await connectToOrg1CA();
}
else if (org == 'Org2' || org == 'org2') {
await connectToOrg2CA();
} else {
console.log("Usage: node registerUser.js org userID");
console.log("Org must be Org1 or Org2");
}
} catch (error) {
console.error(`Error in enrolling admin: ${error}`);
process.exit(1);
}
}
main();

View file

@ -0,0 +1,16 @@
{
"name": "auction",
"version": "1.0.0",
"description": "auction application implemented in JavaScript",
"engines": {
"node": ">=12",
"npm": ">=5"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-ca-client": "^2.2.0",
"fabric-network": "^2.2.0"
}
}

View file

@ -0,0 +1,86 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function queryAuction(ccp,wallet,user,auctionID) {
try {
const gateway = new Gateway();
//connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: query the auction');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
}
}
async function main() {
try {
if (process.argv[2] == undefined || process.argv[3] == undefined
|| process.argv[4] == undefined) {
console.log("Usage: node queryAuction.js org userID auctionID");
process.exit(1);
}
const org = process.argv[2]
const user = process.argv[3];
const auctionID = process.argv[4];
if (org == 'Org1' || org == 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await queryAuction(ccp,wallet,user,auctionID);
}
else if (org == 'Org2' || org == 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await queryAuction(ccp,wallet,user,auctionID);
} else {
console.log("Usage: node queryAuction.js org userID auctionID");
console.log("Org must be Org1 or Org2");
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();

View file

@ -0,0 +1,87 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function queryBid(ccp,wallet,user,auctionID,bidID) {
try {
const gateway = new Gateway();
//connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: read bid from private data store');
let result = await contract.evaluateTransaction('QueryBid',auctionID,bidID);
console.log('*** Result: Bid: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
}
}
async function main() {
try {
if (process.argv[2] == undefined || process.argv[3] == undefined
|| process.argv[4] == undefined || process.argv[5] == undefined) {
console.log("Usage: node bid.js org userID auctionID bidID");
process.exit(1);
}
const org = process.argv[2]
const user = process.argv[3];
const auctionID = process.argv[4];
const bidID = process.argv[5];
if (org == 'Org1' || org == 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await queryBid(ccp,wallet,user,auctionID,bidID);
}
else if (org == 'Org2' || org == 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await queryBid(ccp,wallet,user,auctionID,bidID);
} else {
console.log("Usage: node bid.js org userID auctionID bidID");
console.log("Org must be Org1 or Org2");
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
}
}
main();

View file

@ -0,0 +1,77 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Wallets } = require('fabric-network');
const FabricCAServices = require('fabric-ca-client');
const path = require('path');
const { buildCAClient, registerAndEnrollUser } = require('../../test-application/javascript/CAUtil.js');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const mspOrg1 = 'Org1MSP';
const mspOrg2 = 'Org2MSP';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function connectToOrg1CA(UserID) {
console.log('\n--> Register and enrolling new user');
const ccpOrg1 = buildCCPOrg1();
const caOrg1Client = buildCAClient(FabricCAServices, ccpOrg1, 'ca.org1.example.com');
const walletPathOrg1 = path.join(__dirname, 'wallet/org1');
const walletOrg1 = await buildWallet(Wallets, walletPathOrg1);
await registerAndEnrollUser(caOrg1Client, walletOrg1, mspOrg1, UserID, 'org1.department1');
}
async function connectToOrg2CA(UserID) {
console.log('\n--> Register and enrolling new user');
const ccpOrg2 = buildCCPOrg2();
const caOrg2Client = buildCAClient(FabricCAServices, ccpOrg2, 'ca.org2.example.com');
const walletPathOrg2 = path.join(__dirname, 'wallet/org2');
const walletOrg2 = await buildWallet(Wallets, walletPathOrg2);
await registerAndEnrollUser(caOrg2Client, walletOrg2, mspOrg2, UserID, 'org2.department1');
}
async function main() {
if (process.argv[2] == undefined && process.argv[3] == undefined) {
console.log("Usage: node registerEnrollUser.js org userID");
process.exit(1);
}
const org = process.argv[2];
const userId = process.argv[3];
try {
if (org == 'Org1' || org == 'org1') {
await connectToOrg1CA(userId);
}
else if (org == 'Org2' || org == 'org2') {
await connectToOrg2CA(userId);
} else {
console.log("Usage: node registerEnrollUser.js org userID");
console.log("Org must be Org1 or Org2");
}
} catch (error) {
console.error(`Error in enrolling admin: ${error}`);
process.exit(1);
}
}
main();

View file

@ -0,0 +1,119 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function addBid(ccp,wallet,user,auctionID,bidID) {
try {
const gateway = new Gateway();
//connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: read your bid');
let bidString = await contract.evaluateTransaction('QueryBid',auctionID,bidID);
var bidJSON = JSON.parse(bidString);
//console.log('\n--> Evaluate Transaction: query the auction you want to join');
let auctionString = await contract.evaluateTransaction('QueryAuction',auctionID);
// console.log('*** Result: Bid: ' + prettyJSONString(auctionString.toString()));
var auctionJSON = JSON.parse(auctionString);
let bidData = { objectType: 'bid', price: parseInt(bidJSON.price), org: bidJSON.org, bidder: bidJSON.bidder};
console.log('*** Result: Bid: ' + JSON.stringify(bidData,null,2));
let statefulTxn = contract.createTransaction('RevealBid');
let tmapData = Buffer.from(JSON.stringify(bidData));
statefulTxn.setTransient({
bid: tmapData
});
if (auctionJSON.organizations.length == 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0],auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
await statefulTxn.submit(auctionID,bidID);
console.log('\n--> Evaluate Transaction: query the auction to see that our bid was added');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main() {
try {
if (process.argv[2] == undefined || process.argv[3] == undefined
|| process.argv[4] == undefined || process.argv[5] == undefined) {
console.log("Usage: node revealBid.js org userID auctionID bidID");
process.exit(1);
}
const org = process.argv[2]
const user = process.argv[3];
const auctionID = process.argv[4];
const bidID = process.argv[5];
if (org == 'Org1' || org == 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await addBid(ccp,wallet,user,auctionID,bidID);
}
else if (org == 'Org2' || org == 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await addBid(ccp,wallet,user,auctionID,bidID);
}
else {
console.log("Usage: node revealBid.js org userID auctionID bidID");
console.log("Org must be Org1 or Org2");
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -0,0 +1,108 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const { Gateway, Wallets } = require('fabric-network');
const path = require('path');
const { buildCCPOrg1, buildCCPOrg2, buildWallet } = require('../../test-application/javascript/AppUtil.js');
const myChannel = 'mychannel';
const myChaincodeName = 'auction';
function prettyJSONString(inputString) {
if (inputString) {
return JSON.stringify(JSON.parse(inputString), null, 2);
}
else {
return inputString;
}
}
async function submitBid(ccp,wallet,user,auctionID,bidID) {
try {
const gateway = new Gateway();
//connect using Discovery enabled
await gateway.connect(ccp,
{ wallet: wallet, identity: user, discovery: { enabled: true, asLocalhost: true } });
const network = await gateway.getNetwork(myChannel);
const contract = network.getContract(myChaincodeName);
console.log('\n--> Evaluate Transaction: query the auction you want to join');
let auctionString = await contract.evaluateTransaction('QueryAuction',auctionID);
var auctionJSON = JSON.parse(auctionString);
let statefulTxn = contract.createTransaction('SubmitBid');
if (auctionJSON.organizations.length == 2) {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0],auctionJSON.organizations[1]);
} else {
statefulTxn.setEndorsingOrganizations(auctionJSON.organizations[0]);
}
console.log('\n--> Submit Transaction: add bid to the auction');
await statefulTxn.submit(auctionID,bidID);
console.log('\n--> Evaluate Transaction: query the auction to see that our bid was added');
let result = await contract.evaluateTransaction('QueryAuction',auctionID);
console.log('*** Result: Auction: ' + prettyJSONString(result.toString()));
gateway.disconnect();
} catch (error) {
console.error(`******** FAILED to submit bid: ${error}`);
process.exit(1);
}
}
async function main() {
try {
if (process.argv[2] == undefined || process.argv[3] == undefined
|| process.argv[4] == undefined || process.argv[5] == undefined) {
console.log("Usage: node submitBid.js org userID auctionID bidID");
process.exit(1);
}
const org = process.argv[2]
const user = process.argv[3];
const auctionID = process.argv[4];
const bidID = process.argv[5];
if (org == 'Org1' || org == 'org1') {
const orgMSP = 'Org1MSP';
const ccp = buildCCPOrg1();
const walletPath = path.join(__dirname, 'wallet/org1');
const wallet = await buildWallet(Wallets, walletPath);
await submitBid(ccp,wallet,user,auctionID,bidID);
}
else if (org == 'Org2' || org == 'org2') {
const orgMSP = 'Org2MSP';
const ccp = buildCCPOrg2();
const walletPath = path.join(__dirname, 'wallet/org2');
const wallet = await buildWallet(Wallets, walletPath);
await submitBid(ccp,wallet,user,auctionID,bidID);
}
else {
console.log("Usage: node submitBid.js org userID auctionID bidID");
console.log("Org must be Org1 or Org2");
}
} catch (error) {
console.error(`******** FAILED to run the application: ${error}`);
if (error.stack) {
console.error(error.stack);
}
process.exit(1);
}
}
main();

View file

@ -0,0 +1,8 @@
module github.com/hyperledger/fabric-samples/auction/chaincode-go
go 1.15
require (
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200728190242-9b3ae92d8664
github.com/hyperledger/fabric-contract-api-go v1.1.0
)

139
auction/chaincode-go/go.sum Normal file
View file

@ -0,0 +1,139 @@
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0=
github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI=
github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
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/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
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/spec v0.19.4 h1:ixzUSnHTd6hCemgtAJgluaTSGYpLNpJY4mA2DIkdOAo=
github.com/go-openapi/spec v0.19.4/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
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-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/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/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/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/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
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/go.mod h1:N7H3sA7Tx4k/YzFq7U0EPdqJtqvM4Kild0JoCc7C0Dc=
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200728190242-9b3ae92d8664 h1:Pu/9SNpo71SJj5DGehCXOKD9QGQ3MsuWjpsLM9Mkdwg=
github.com/hyperledger/fabric-chaincode-go v0.0.0-20200728190242-9b3ae92d8664/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/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=
github.com/karrick/godirwalk v1.10.12/go.mod h1:RoGL9dQei4vP9ilrpETWE8CLOZ1kiN0LhBygSwrAsHA=
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/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/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/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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/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=
github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
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/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/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=
github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q=
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
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/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
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/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
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=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190515120540-06a5c4944438/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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/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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
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=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
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/grpc v1.23.0 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A=
google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
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/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.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10=
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=

View file

@ -0,0 +1,487 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"bytes"
"crypto/sha256"
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
type SmartContract struct {
contractapi.Contract
}
// Auction data
type Auction struct {
Type string `json:"objectType"`
ItemSold string `json:"item"`
Seller string `json:"seller"`
Orgs []string `json:"organizations"`
PrivateBids map[string]BidHash `json:"privateBids"`
RevealedBids map[string]FullBid `json:"revealedBids"`
Winner string `json:"winner"`
Price int `json:"price"`
Status string `json:"status"`
}
// FullBid is the structure of a revealed bid
type FullBid struct {
Type string `json:"objectType"`
Price int `json:"price"`
Org string `json:"org"`
Bidder string `json:"bidder"`
}
// BidHash is the structure of a private bid
type BidHash struct {
Org string `json:"org"`
Hash string `json:"hash"`
}
const bidKeyType = "bid"
// CreateAuction creates on auction on the public channel. The identity that
// submits the transacion becomes the seller of the auction
func (s *SmartContract) CreateAuction(ctx contractapi.TransactionContextInterface, auctionID string, itemsold string) error {
// get ID of submitting client
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
// get org of submitting client
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
// Create auction
bidders := make(map[string]BidHash)
revealedBids := make(map[string]FullBid)
auction := Auction{
Type: "auction",
ItemSold: itemsold,
Price: 0,
Seller: clientID,
Orgs: []string{clientOrgID},
PrivateBids: bidders,
RevealedBids: revealedBids,
Winner: "",
Status: "open",
}
auctionBytes, err := json.Marshal(auction)
if err != nil {
return err
}
// put auction into state
err = ctx.GetStub().PutState(auctionID, auctionBytes)
if err != nil {
return fmt.Errorf("failed to put auction in public data: %v", err)
}
// set the seller of the auction as an endorser
err = setAssetStateBasedEndorsement(ctx, auctionID, clientOrgID)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
}
return nil
}
// Bid is used to add a user's bid to the auction. The bid is stored in the private
// data collection on the peer of the bidder's organization. The function returns
// the transaction ID so that users can identify and query their bid
func (s *SmartContract) Bid(ctx contractapi.TransactionContextInterface, auctionID string) (string, error) {
// get bid from transient map
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return "", fmt.Errorf("error getting transient: %v", err)
}
BidJSON, ok := transientMap["bid"]
if !ok {
return "", fmt.Errorf("bid key not found in the transient map")
}
// get the implicit collection name using the bidder's organization ID
collection, err := getCollectionName(ctx)
if err != nil {
return "", fmt.Errorf("failed to get implicit collection name: %v", err)
}
// the bidder has to target their peer to store the bid
err = verifyClientOrgMatchesPeerOrg(ctx)
if err != nil {
return "", fmt.Errorf("Cannot store bid on this peer, not a member of this org: Error %v", err)
}
// the transaction ID is used as a unique index for the bid
txID := ctx.GetStub().GetTxID()
// create a composite key using the transaction ID
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return "", fmt.Errorf("failed to create composite key: %v", err)
}
// put the bid into the organization's implicit data collection
err = ctx.GetStub().PutPrivateData(collection, bidKey, BidJSON)
if err != nil {
return "", fmt.Errorf("failed to input price into collection: %v", err)
}
// return the trannsaction ID so that the uset can identify their bid
return txID, nil
}
// SubmitBid is used by the bidder to add the hash of that bid stored in private data to the
// auction. Note that this function alters the auction in private state, and needs
// to meet the auction endorsement policy. Transaction ID is used identify the bid
func (s *SmartContract) SubmitBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
// get the MSP ID of the bidder's org
clientOrgID, err := ctx.GetClientIdentity().GetMSPID()
if err != nil {
return fmt.Errorf("failed to get client MSP ID: %v", err)
}
// get the auction from state
auctionBytes, err := ctx.GetStub().GetState(auctionID)
var auctionJSON Auction
if auctionBytes == nil {
return fmt.Errorf("Auction not found: %v", auctionID)
}
err = json.Unmarshal(auctionBytes, &auctionJSON)
if err != nil {
return fmt.Errorf("failed to create auction object JSON: %v", err)
}
// the auction needs to be open for users to add their bid
Status := auctionJSON.Status
if Status != "open" {
return fmt.Errorf("cannot join closed or ended auction")
}
// get the inplicit collection name of bidder's org
collection, err := getCollectionName(ctx)
if err != nil {
return fmt.Errorf("failed to get implicit collection name: %v", err)
}
// use the transaction ID passed as a parameter to create composite bid key
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
// get the hash of the bid stored in private data collection
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid bash from collection: %v", err)
}
if bidHash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
// store the hash along with the bidder's organization
NewHash := BidHash{
Org: clientOrgID,
Hash: fmt.Sprintf("%x", bidHash),
}
bidders := make(map[string]BidHash)
bidders = auctionJSON.PrivateBids
bidders[bidKey] = NewHash
auctionJSON.PrivateBids = bidders
// Add the bidding organization to the list of participating organizations if it is not already
Orgs := auctionJSON.Orgs
if !(contains(Orgs, clientOrgID)) {
newOrgs := append(Orgs, clientOrgID)
auctionJSON.Orgs = newOrgs
err = addAssetStateBasedEndorsement(ctx, auctionID, clientOrgID)
if err != nil {
return fmt.Errorf("failed setting state based endorsement for new organization: %v", err)
}
}
newAuctionBytes, _ := json.Marshal(auctionJSON)
err = ctx.GetStub().PutState(auctionID, newAuctionBytes)
if err != nil {
return fmt.Errorf("failed to update auction: %v", err)
}
return nil
}
// RevealBid is used by a bidder to reveal their bid after the auction is closed
func (s *SmartContract) RevealBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) error {
// get bid from transient map
transientMap, err := ctx.GetStub().GetTransient()
if err != nil {
return fmt.Errorf("error getting transient: %v", err)
}
transientBidJSON, ok := transientMap["bid"]
if !ok {
return fmt.Errorf("bid key not found in the transient map")
}
// get implicit collection name of organization ID
collection, err := getCollectionName(ctx)
if err != nil {
return fmt.Errorf("failed to get implicit collection name: %v", err)
}
// use transaction ID to create composit bid key
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return fmt.Errorf("failed to create composite key: %v", err)
}
// get bid hash of bid if private bid on the public ledger
bidHash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid bash from collection: %v", err)
}
if bidHash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
// get auction from public state
auctionBytes, err := ctx.GetStub().GetState(auctionID)
if err != nil {
return fmt.Errorf("failed to get auction %v: %v", auctionID, err)
}
if auctionBytes == nil {
return fmt.Errorf("Auction interest object %v not found", auctionID)
}
var auctionJSON Auction
err = json.Unmarshal(auctionBytes, &auctionJSON)
if err != nil {
return fmt.Errorf("failed to create auction object JSON: %v", err)
}
// Complete a series of three checks before we add the bid to the auction
// check 1: check that the auction is closed. We cannot reveal a
// bid to an open auction
Status := auctionJSON.Status
if Status != "closed" {
return fmt.Errorf("cannot reveal bid for open or ended auction")
}
// check 2: check that hash of revealed bid matches hash of private bid
// on the public ledger. This checks that the bidder is telling the truth
// about the value of their bid
hash := sha256.New()
hash.Write(transientBidJSON)
calculatedBidJSONHash := hash.Sum(nil)
// verify that the hash of the passed immutable properties matches the on-chain hash
if !bytes.Equal(calculatedBidJSONHash, bidHash) {
return fmt.Errorf("hash %x for bid JSON %s does not match hash in auction: %x",
calculatedBidJSONHash,
transientBidJSON,
bidHash,
)
}
// check 3; check hash of relealed bid matches hash of private bid that was
// added earlier. This ensures that the bid has not changed since it
// was added to the auction
bidders := auctionJSON.PrivateBids
privateBidHashString := bidders[bidKey].Hash
onChainBidHashString := fmt.Sprintf("%x", bidHash)
if privateBidHashString != onChainBidHashString {
return fmt.Errorf("hash %s for bid JSON %s does not match hash in auction: %s, bidder must have changed bid",
privateBidHashString,
transientBidJSON,
onChainBidHashString,
)
}
// we can add the bid to the auction if all checks have passed
type transientBidInput struct {
Price int `json:"price"`
Org string `json:"org"`
Bidder string `json:"bidder"`
}
// unmarshal bid imput
var bidInput transientBidInput
err = json.Unmarshal(transientBidJSON, &bidInput)
if err != nil {
return fmt.Errorf("failed to unmarshal JSON: %v", err)
}
// Get ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
// marshal transient parameters and ID and MSPID into bid object
NewBid := FullBid{
Type: bidKeyType,
Price: bidInput.Price,
Org: bidInput.Org,
Bidder: bidInput.Bidder,
}
// check 4: make sure that the transaction is being submitted is the bidder
if bidInput.Bidder != clientID {
return fmt.Errorf("Permission denied, client id %v is not the owner of the bid", clientID)
}
revealedBids := make(map[string]FullBid)
revealedBids = auctionJSON.RevealedBids
revealedBids[bidKey] = NewBid
auctionJSON.RevealedBids = revealedBids
newAuctionBytes, _ := json.Marshal(auctionJSON)
// put auction with bid added back into state
err = ctx.GetStub().PutState(auctionID, newAuctionBytes)
if err != nil {
return fmt.Errorf("failed to update auction: %v", err)
}
return nil
}
// CloseAuction can be used by the seller to close the auction. This prevents
// bids from being added to the auction, and allows users to reveal their bid
func (s *SmartContract) CloseAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
auctionBytes, err := ctx.GetStub().GetState(auctionID)
if err != nil {
return fmt.Errorf("failed to get auction %v: %v", auctionID, err)
}
if auctionBytes == nil {
return fmt.Errorf("Auction interest object %v not found", auctionID)
}
var auctionJSON Auction
err = json.Unmarshal(auctionBytes, &auctionJSON)
if err != nil {
return fmt.Errorf("failed to create auction object JSON: %v", err)
}
// the auction can only be closed by the seller
// get ID of submitting client
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
Seller := auctionJSON.Seller
if Seller != clientID {
return fmt.Errorf("auction can only be closed by seller: %v", err)
}
Status := auctionJSON.Status
if Status != "open" {
return fmt.Errorf("cannot close auction that is not open")
}
auctionJSON.Status = string("closed")
closedAuction, _ := json.Marshal(auctionJSON)
err = ctx.GetStub().PutState(auctionID, closedAuction)
if err != nil {
return fmt.Errorf("failed to close auction: %v", err)
}
return nil
}
// EndAuction both changes the auction status to closed and calculates the winners
// of the auction
func (s *SmartContract) EndAuction(ctx contractapi.TransactionContextInterface, auctionID string) error {
auctionBytes, err := ctx.GetStub().GetState(auctionID)
if err != nil {
return fmt.Errorf("failed to get auction %v: %v", auctionID, err)
}
if auctionBytes == nil {
return fmt.Errorf("Auction interest object %v not found", auctionID)
}
var auctionJSON Auction
err = json.Unmarshal(auctionBytes, &auctionJSON)
if err != nil {
return fmt.Errorf("failed to create auction object JSON: %v", err)
}
// Check that the auction is being ended by the seller
// get ID of submitting client
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return fmt.Errorf("failed to get client identity %v", err)
}
Seller := auctionJSON.Seller
if Seller != clientID {
return fmt.Errorf("auction can only be ended by seller: %v", err)
}
Status := auctionJSON.Status
if Status != "closed" {
return fmt.Errorf("Can only end a closed auction")
}
// get the list of revealed bids
revealedBidMap := auctionJSON.RevealedBids
if len(auctionJSON.RevealedBids) == 0 {
return fmt.Errorf("No bids have been revealed, cannot end auction: %v", err)
}
// determine the highest bid
for _, bid := range revealedBidMap {
if bid.Price > auctionJSON.Price {
auctionJSON.Winner = bid.Bidder
auctionJSON.Price = bid.Price
}
}
// check if there is a winning bid that has yet to be revealed
err = queryAllBids(ctx, auctionJSON.Price, auctionJSON.RevealedBids, auctionJSON.PrivateBids)
if err != nil {
return fmt.Errorf("Cannot close auction: %v", err)
}
auctionJSON.Status = string("ended")
closedAuction, _ := json.Marshal(auctionJSON)
err = ctx.GetStub().PutState(auctionID, closedAuction)
if err != nil {
return fmt.Errorf("failed to close auction: %v", err)
}
return nil
}

View file

@ -0,0 +1,148 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"encoding/json"
"fmt"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// QueryAuction allows all members of the channel to read a public auction
func (s *SmartContract) QueryAuction(ctx contractapi.TransactionContextInterface, auctionID string) (*Auction, error) {
auctionJSON, err := ctx.GetStub().GetState(auctionID)
if err != nil {
return nil, fmt.Errorf("failed to get auction object %v: %v", auctionID, err)
}
if auctionJSON == nil {
return nil, fmt.Errorf("auction does not exist")
}
var auction *Auction
err = json.Unmarshal(auctionJSON, &auction)
if err != nil {
return nil, err
}
return auction, nil
}
// QueryBid allows the submitter of the bid to read their bid from public state
func (s *SmartContract) QueryBid(ctx contractapi.TransactionContextInterface, auctionID string, txID string) (*FullBid, error) {
err := verifyClientOrgMatchesPeerOrg(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get implicit collection name: %v", err)
}
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return nil, fmt.Errorf("failed to get client identity %v", err)
}
collection, err := getCollectionName(ctx)
if err != nil {
return nil, fmt.Errorf("failed to get implicit collection name: %v", err)
}
bidKey, err := ctx.GetStub().CreateCompositeKey(bidKeyType, []string{auctionID, txID})
if err != nil {
return nil, fmt.Errorf("failed to create composite key: %v", err)
}
bidJSON, err := ctx.GetStub().GetPrivateData(collection, bidKey)
if err != nil {
return nil, fmt.Errorf("failed to get bid %v: %v", bidKey, err)
}
if bidJSON == nil {
return nil, fmt.Errorf("bid %v does not exist", bidKey)
}
var bid *FullBid
err = json.Unmarshal(bidJSON, &bid)
if err != nil {
return nil, err
}
// check that the client querying the bid is the bid owner
if bid.Bidder != clientID {
return nil, fmt.Errorf("Permission denied, client id %v is not the owner of the bid", clientID)
}
return bid, nil
}
// GetID is an internal helper function to allow users to get their identity
func (s *SmartContract) GetID(ctx contractapi.TransactionContextInterface) (string, error) {
// Get the MSP ID of submitting client identity
clientID, err := ctx.GetClientIdentity().GetID()
if err != nil {
return "", fmt.Errorf("failed to get verified MSPID: %v", err)
}
return clientID, nil
}
// queryAllBids is an internal function that is used to determine if a winning bid has yet to be revealed
func queryAllBids(ctx contractapi.TransactionContextInterface, auctionPrice int, revealedBidders map[string]FullBid, bidders map[string]BidHash) error {
// Get MSP ID of peer org
peerMSPID, err := shim.GetMSPID()
if err != nil {
return fmt.Errorf("failed getting the peer's MSPID: %v", err)
}
var error error
error = nil
for bidKey, privateBid := range bidders {
if _, bidInAuction := revealedBidders[bidKey]; bidInAuction {
//bid is already revealed, no action to take
} else {
collection := "_implicit_org_" + privateBid.Org
if privateBid.Org == peerMSPID {
bidJSON, err := ctx.GetStub().GetPrivateData(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to get bid %v: %v", bidKey, err)
}
if bidJSON == nil {
return fmt.Errorf("bid %v does not exist", bidKey)
}
var bid *FullBid
err = json.Unmarshal(bidJSON, &bid)
if err != nil {
return err
}
if bid.Price > auctionPrice {
error = fmt.Errorf("Cannot close auction, bidder has a higher price: %v", err)
}
} else {
Hash, err := ctx.GetStub().GetPrivateDataHash(collection, bidKey)
if err != nil {
return fmt.Errorf("failed to read bid hash from collection: %v", err)
}
if Hash == nil {
return fmt.Errorf("bid hash does not exist: %s", bidKey)
}
}
}
}
return error
}

View file

@ -0,0 +1,107 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package auction
import (
"fmt"
"github.com/hyperledger/fabric-chaincode-go/pkg/statebased"
"github.com/hyperledger/fabric-chaincode-go/shim"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
)
// setAssetStateBasedEndorsement sets the endorsement policy of a new auction
func setAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, auctionID string, orgToEndorse string) error {
endorsementPolicy, err := statebased.NewStateEP(nil)
if err != nil {
return err
}
err = endorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgToEndorse)
if err != nil {
return fmt.Errorf("failed to add org to endorsement policy: %v", err)
}
policy, err := endorsementPolicy.Policy()
if err != nil {
return fmt.Errorf("failed to create endorsement policy bytes from org: %v", err)
}
err = ctx.GetStub().SetStateValidationParameter(auctionID, policy)
if err != nil {
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
}
return nil
}
// addAssetStateBasedEndorsement adds a new organization as an endorser of the auction
func addAssetStateBasedEndorsement(ctx contractapi.TransactionContextInterface, auctionID string, orgToEndorse string) error {
endorsementPolicy, err := ctx.GetStub().GetStateValidationParameter(auctionID)
if err != nil {
return err
}
newEndorsementPolicy, err := statebased.NewStateEP(endorsementPolicy)
if err != nil {
return err
}
err = newEndorsementPolicy.AddOrgs(statebased.RoleTypePeer, orgToEndorse)
if err != nil {
return fmt.Errorf("failed to add org to endorsement policy: %v", err)
}
policy, err := newEndorsementPolicy.Policy()
if err != nil {
return fmt.Errorf("failed to create endorsement policy bytes from org: %v", err)
}
err = ctx.GetStub().SetStateValidationParameter(auctionID, policy)
if err != nil {
return fmt.Errorf("failed to set validation parameter on auction: %v", err)
}
return nil
}
// getCollectionName is an internal helper function to get collection of submitting client identity.
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 MSPID: %v", err)
}
// Create the collection name
orgCollection := "_implicit_org_" + clientMSPID
return orgCollection, nil
}
// verifyClientOrgMatchesPeerOrg is an internal function used to verify that client org id matches peer org id.
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 the peer's MSPID: %v", err)
}
if clientMSPID != peerMSPID {
return fmt.Errorf("client from org %v is not authorized to read or write private data from an org %v peer", clientMSPID, peerMSPID)
}
return nil
}
func contains(sli []string, str string) bool {
for _, a := range sli {
if a == str {
return true
}
}
return false
}

View file

@ -0,0 +1,23 @@
/*
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"log"
"github.com/hyperledger/fabric-contract-api-go/contractapi"
"github.com/hyperledger/fabric-samples/auction/chaincode-go/smart-contract"
)
func main() {
auctionSmartContract, err := contractapi.NewChaincode(&auction.SmartContract{})
if err != nil {
log.Panicf("Error creating auction chaincode: %v", err)
}
if err := auctionSmartContract.Start(); err != nil {
log.Panicf("Error starting auction chaincode: %v", err)
}
}

View file

@ -4,8 +4,8 @@
"description": "FabCar contract implemented in JavaScript", "description": "FabCar contract implemented in JavaScript",
"main": "index.js", "main": "index.js",
"engines": { "engines": {
"node": ">=8", "node": ">=12",
"npm": ">=5" "npm": ">=6.9"
}, },
"scripts": { "scripts": {
"lint": "eslint .", "lint": "eslint .",

View file

@ -238,3 +238,18 @@ jobs:
- script: ../ci/scripts/run-test-network-secured.sh - script: ../ci/scripts/run-test-network-secured.sh
workingDirectory: test-network workingDirectory: test-network
displayName: Run Test Network Secured Chaincode displayName: Run Test Network Secured Chaincode
- job: TestNetworkEvents
displayName: Test Network
pool:
vmImage: ubuntu-18.04
strategy:
matrix:
Events-Javascript:
CHAINCODE_NAME: events
CHAINCODE_LANGUAGE: javascript
steps:
- template: templates/install-deps.yml
- script: ../ci/scripts/run-test-network-events.sh
workingDirectory: test-network
displayName: Run Test Network Events Chaincode

View file

@ -11,5 +11,5 @@ for image in baseos peer orderer ca tools orderer ccenv javaenv nodeenv tools; d
docker rmi -f "hyperledger-fabric.jfrog.io/fabric-${image}:${STABLE_TAG}" docker rmi -f "hyperledger-fabric.jfrog.io/fabric-${image}:${STABLE_TAG}"
done done
docker pull -q couchdb:3.1 docker pull -q couchdb:3.1.1
docker images | grep hyperledger docker images | grep hyperledger

View file

@ -24,13 +24,13 @@ function stopNetwork() {
} }
# Run Go application # Run Go application
#createNetwork createNetwork
#print "Initializing Go application" print "Initializing Go application"
#pushd ../asset-transfer-basic/application-go pushd ../asset-transfer-basic/application-go
#print "Executing AssetTransfer.go" print "Executing AssetTransfer.go"
#go run . go run .
#popd popd
#stopNetwork stopNetwork
# Run Java application # Run Java application
createNetwork createNetwork
@ -50,3 +50,15 @@ print "Executing app.js"
node app.js node app.js
popd popd
stopNetwork stopNetwork
# Run typescript application
createNetwork
print "Initializing Typescript application"
pushd ../asset-transfer-basic/application-typescript
npm install
print "Building app.ts"
npm run build
print "Running the output app"
node dist/app.js
popd
stopNetwork

View file

@ -0,0 +1,36 @@
set -euo pipefail
FABRIC_VERSION=${FABRIC_VERSION:-2.2}
CHAINCODE_LANGUAGE=${CHAINCODE_LANGUAGE:-javascript}
CHAINCODE_NAME=${CHAINCODE_NAME:-events}
function print() {
GREEN='\033[0;32m'
NC='\033[0m'
echo
echo -e "${GREEN}${1}${NC}"
}
function createNetwork() {
print "Creating network"
./network.sh up createChannel -ca
print "Deploying ${CHAINCODE_NAME} chaincode"
./network.sh deployCC -ccn "${CHAINCODE_NAME}" -ccl "${CHAINCODE_LANGUAGE}" -ccep "OR('Org1MSP.peer','Org2MSP.peer')"
}
function stopNetwork() {
print "Stopping network"
./network.sh down
}
# Run Javascript application
createNetwork
print "Initializing Javascript application"
pushd ../asset-transfer-events/application-javascript
npm install
print "Executing app.js"
node app.js
popd
stopNetwork
print "Remove wallet storage"
rm -R ../asset-transfer-events/application-javascript/wallet

View file

@ -9,7 +9,7 @@
* 1. Select an identity from a wallet * 1. Select an identity from a wallet
* 2. Connect to network gateway * 2. Connect to network gateway
* 3. Access PaperNet network * 3. Access PaperNet network
* 4. Construct request to issue commercial paper * 4. Construct request to buy commercial paper
* 5. Submit transaction * 5. Submit transaction
* 6. Process response * 6. Process response
*/ */

View file

@ -15,7 +15,7 @@ function _exit(){
: ${VERBOSE:="false"} : ${VERBOSE:="false"}
# Where am I? # Where am I?
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR=${PWD}
# Locate the test network # Locate the test network
cd "${DIR}/../../../test-network" cd "${DIR}/../../../test-network"
@ -36,4 +36,4 @@ env | sort | comm -1 -3 /tmp/env.orig - | sed -E 's/(.*)=(.*)/export \1="\2"/'
rm /tmp/env.orig rm /tmp/env.orig
cd ${DIR} cd ${DIR}

View file

@ -15,7 +15,7 @@ function _exit(){
: ${VERBOSE:="false"} : ${VERBOSE:="false"}
# Where am I? # Where am I?
DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" DIR=${PWD}
# Locate the test-network # Locate the test-network
cd "${DIR}/../../../test-network" cd "${DIR}/../../../test-network"
@ -35,4 +35,4 @@ export PATH="${DIR}/../../../bin:${PWD}:$PATH"
env | sort | comm -1 -3 /tmp/env.orig - | sed -E 's/(.*)=(.*)/export \1="\2"/' env | sort | comm -1 -3 /tmp/env.orig - | sed -E 's/(.*)=(.*)/export \1="\2"/'
rm /tmp/env.orig rm /tmp/env.orig
cd ${DIR} cd ${DIR}

View file

@ -3384,9 +3384,9 @@
} }
}, },
"yargs-parser": { "yargs-parser": {
"version": "13.1.1", "version": "13.1.2",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz",
"integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==",
"dev": true, "dev": true,
"requires": { "requires": {
"camelcase": "^5.0.0", "camelcase": "^5.0.0",

View file

@ -96,11 +96,6 @@ must be verified before approval and admittance to the chain.
## How ## How
This sample provides the chaincode and scripts required to run a high-throughput application on the Fabric test network. This sample provides the chaincode and scripts required to run a high-throughput application on the Fabric test network.
### Vendor the chaincode dependencies
1. Change into the chaincode directory, e.g. `cd ~/fabric-samples/high-throughput/chaincode`
2. Vendor the Go dependencies by running the following command: `GO111MODULE=on go mod vendor`
3. The chaincode directory will now contain a `vendor` directory.
### Start the network ### Start the network
You can use the `startFabric.sh` script to create an instance of the Fabric test network with a single channel named `mychannel`. The script then deploys the `high-throughput` chaincode to the channel by installing it on the test network peers and committing the chaincode definition to the channel. You can use the `startFabric.sh` script to create an instance of the Fabric test network with a single channel named `mychannel`. The script then deploys the `high-throughput` chaincode to the channel by installing it on the test network peers and committing the chaincode definition to the channel.
@ -112,56 +107,86 @@ Change back into the `high-throughput` directory in `fabic-samples`. Start the n
If successful, you will see messages of the Fabric test network being created and the chaincode being deployed, followed by the execution time of the script: If successful, you will see messages of the Fabric test network being created and the chaincode being deployed, followed by the execution time of the script:
``` ```
Total setup execution time : 141 secs ... Total setup execution time : 81 secs ...
``` ```
The `high-throughput` chaincode is now ready to receive invocations. The `high-throughput` chaincode is now ready to receive invocations.
### Invoke the chaincode ### Invoke the chaincode
All invocations are provided as scripts in `scripts` folder. You can use these scripts to create and remove assets that you put on the ledger.
You can invoke the `high-througput` chaincode using a Go application in the `application-go` folder. The Go application will allow us to submit many transactions to the network concurrently. Navigate to the application:
```
cd application-go
```
#### Update #### Update
The format for update is: `./scripts/update-invoke.sh name value operation` where `name` is the name of the variable to update, `value` is the value to The format for update is: `go run app.go update name value operation` where `name` is the name of the variable to update, `value` is the value to add to the variable, and `operation` is either `+` or `-` depending on what type of operation you'd like to add to the variable.
add to the variable, and `operation` is either `+` or `-` depending on what type of operation you'd like to add to the variable. In the future,
multiply/divide operations will be supported (or add them yourself to the chaincode as an exercise!)
Example: `./scripts/update-invoke.sh myvar 100 +` Example: `go run app.go update myvar 100 +`
#### Get #### Query
The format for get is: `./get-invoke.sh name` where `name` is the name of the variable to get. You can query the value of a variable by running `go run app.go get name` where `name` is the name of the variable to get.
Example: `./scripts/get-invoke.sh myvar` Example: `go run app.go get myvar`
#### Prune #### Prune
Pruning takes all the deltas generated for a variable and combines them all into a single row, deleting all previous rows. This helps cleanup Pruning takes all the deltas generated for a variable and combines them all into a single row, deleting all previous rows. This helps cleanup the ledger when many updates have been performed.
the ledger when many updates have been performed.
The format for pruning is: `./scripts/prune-invoke.sh name` where `name` is the name of the variable to prune. The format for pruning is: `go run app.go prune name` where `name` is the name of the variable to prune.
Example: `./scripts/prune-invoke.sh myvar` Example: `go run app.go prune myvar`
#### Delete #### Delete
The format for delete is: `./delete-invoke.sh name` where `name` is the name of the variable to delete. The format for delete is: `go run app.go delete name` where `name` is the name of the variable to delete.
Example: `./scripts/delete-invoke.sh myvar` Example: `go run app.go delete myvar`
### Test the Network ### Test the Network
Two scripts are provided to show the advantage of using this system when running many parallel transactions at once: `many-updates.sh` and
`many-updates-traditional.sh`. The first script accepts the same arguments as `update-invoke.sh` but duplicates the invocation 1000 times
and in parallel. The final value, therefore, should be the given update value * 1000. Run this script to confirm that your network is functioning
properly. You can confirm this by checking your peer and orderer logs and verifying that no invocations are rejected due to improper versions.
The second script, `many-updates-traditional.sh`, also sends 1000 transactions but using the traditional storage system. It'll update a single The application provides two methods that demonstrate the advantages of this system by submitting many concurrent transactions to the smart contract: `manyUpdates` and `manyUpdatesTraditional`. The first function accepts the same arguments as `update-invoke.sh` but runs the invocation 1000 times in parallel. The final value, therefore, should be the given update value * 1000.
row in the ledger 1000 times, with a value incrementing by one each time (i.e. the first invocation sets it to 0 and the last to 1000). The
expectation would be that the final value of the row is 999. However, the final value changes each time this script is run and you'll find
errors in the peer and orderer logs.
There are two other scripts, `get-traditional.sh`, which simply gets the value of a row in the traditional way, with no deltas, and `del-traditional.sh` will delete an asset in the traditional way. The second function, `manyUpdatesTraditional`, submits 1000 transactions that attempt to upddate the same key in the world state 1000 times.
Examples: Run the following command to create and update `testvar1` a 1000 times:
`./scripts/many-updates.sh testvar 100 +` --> final value from `./scripts/get-invoke.sh testvar` should be 100000 ```
go run app.go manyUpdates testvar1 100 +
```
`./scripts/many-updates-traditional.sh testvar` --> final value from `./scripts/get-traditional.sh testvar` is undefined The application will query the variable after submitting the transaction. The result should be `100000`.
We will now see what happens when you try to run 1000 concurrent updates using a traditional transaction. Run the following command to create a variable named `testvar2`:
```
go run app.go update testvar2 100 +
```
The variable will have a value of 100:
```
2020/10/27 18:01:45 Value of variable testvar2 : 100
```
Now lets try to update `testvar2` 1000 times in parallel:
```
go run app.go manyUpdatesTraditional testvar2 100 +
```
When the program ends, you may see that none of the updates succeeded.
```
2020/10/27 18:03:15 Final value of variable testvar2 : 100
```
The transactions failed because multiple transactions in each block updated the same key. Because of these transactions generated read/write conflicts, the transactions included in each block were rejected in the validation stage.
You can can examine the peer logs to view the messages generated by the rejected blocks:
`docker logs peer0.org1.example.com
[...]
2020-10-28 17:37:58.746 UTC [gossip.privdata] StoreBlock -> INFO 2190 [mychannel] Received block [407] from buffer
2020-10-28 17:37:58.749 UTC [committer.txvalidator] Validate -> INFO 2191 [mychannel] Validated block [407] in 2ms
2020-10-28 17:37:58.750 UTC [validation] validateAndPrepareBatch -> WARN 2192 Block [407] Transaction index [0] TxId [b6b14cf988b0d7d35d4e0d7a0d2ae0c9f5569bc10ec5010f03a28c22694b8ef6] marked as invalid by state validator. Reason code [MVCC_READ_CONFLICT]
2020-10-28 17:37:58.750 UTC [validation] validateAndPrepareBatch -> WARN 2193 Block [407] Transaction index [1] TxId [9d7c4f6ff95a0f22e01d6ffeda261227752e78db43f2673ad4ea6f0fdace44d1] marked as invalid by state validator. Reason code [MVCC_READ_CONFLICT]
2020-10-28 17:37:58.750 UTC [validation] validateAndPrepareBatch -> WARN 2194 Block [407] Transaction index [2] TxId [9cc228b61d8841208feb6160254aee098b1b3a903f645e62cfa12222e6f52e65] marked as invalid by state validator. Reason code [MVCC_READ_CONFLICT]
2020-10-28 17:37:58.750 UTC [validation] validateAndPrepareBatch -> WARN 2195 Block [407] Transaction index [3] TxId [2ae78d363c30b5f3445f2b028ccac7cf821f1d5d5c256d8c17bd42f33178e2ed] marked as invalid by state validator. Reason code [MVCC_READ_CONFLICT]
```
### Clean up ### Clean up

4
high-throughput/application-go/.gitignore vendored Executable file
View file

@ -0,0 +1,4 @@
wallet
!wallet/.gitkeep
keystore

View file

@ -0,0 +1,70 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package main
import (
"log"
"os"
f "github.com/hyperledger/fabric-samples/high-throughput/application-go/functions"
)
func main() {
var function, variableName, change, sign string
if len(os.Args) <= 2 {
log.Println("Usage: function variableName")
log.Fatalf("functions: update manyUpdates manyUpdatesTraditional get prune delete")
} else if (os.Args[1] == "update" || os.Args[1] == "manyUpdates" || os.Args[1] == "manyUpdatesTraditional") && len(os.Args) < 5 {
log.Fatalf("error: provide value and operation")
} else if len(os.Args) == 3 {
function = os.Args[1]
variableName = os.Args[2]
} else if len(os.Args) == 5 {
function = os.Args[1]
variableName = os.Args[2]
change = os.Args[3]
sign = os.Args[4]
}
// Handle different functions
if function == "update" {
result, err := f.Update(function, variableName, change, sign)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println("Value of variable", string(variableName), ": ", string(result))
} else if function == "delete" || function == "prune" || function == "delstandard" {
result, err := f.DeletePrune(function, variableName)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println(string(result))
} else if function == "get" || function == "getstandard" {
result, err := f.Query(function, variableName)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println("Value of variable", string(variableName), ": ", string(result))
} else if function == "manyUpdates" {
log.Println("submitting 1000 concurrent updates...")
result, err := f.ManyUpdates("update", variableName, change, sign)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println("Final value of variable", string(variableName), ": ", string(result))
} else if function == "manyUpdatesTraditional" {
log.Println("submitting 1000 concurrent updates...")
result, err := f.ManyUpdates("putstandard", variableName, change, sign)
if err != nil {
log.Fatalf("error: %v", err)
}
log.Println("Final value of variable", string(variableName), ": ", string(result))
}
}

View file

@ -0,0 +1,69 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"os"
"path/filepath"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)
// DeletePrune deletes or prunes a variable
func DeletePrune(function, variableName string) ([]byte, error) {
err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
if err != nil {
return nil, fmt.Errorf("error setting DISCOVERY_AS_LOCALHOST environemnt variable: %v", err)
}
wallet, err := gateway.NewFileSystemWallet("wallet")
if err != nil {
return nil, fmt.Errorf("failed to create wallet: %v", err)
}
if !wallet.Exists("appUser") {
err := populateWallet(wallet)
if err != nil {
return nil, fmt.Errorf("failed to populate wallet contents: %v", err)
}
}
ccpPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"connection-org1.yaml",
)
gw, err := gateway.Connect(
gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
gateway.WithIdentity(wallet, "appUser"),
)
if err != nil {
return nil, fmt.Errorf("failed to connect to gateway: %v", err)
}
defer gw.Close()
network, err := gw.GetNetwork("mychannel")
if err != nil {
return nil, fmt.Errorf("failed to get network: %v", err)
}
contract := network.GetContract("bigdatacc")
result, err := contract.SubmitTransaction(function, variableName)
if err != nil {
return result, fmt.Errorf("failed to Submit transaction: %v", err)
}
return result, err
}

View file

@ -0,0 +1,86 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"os"
"path/filepath"
"sync"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)
// ManyUpdates allows you to push many cuncurrent updates to a variable
func ManyUpdates(function, variableName, change, sign string) ([]byte, error) {
err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
if err != nil {
return nil, fmt.Errorf("error setting DISCOVERY_AS_LOCALHOST environemnt variable: %v", err)
}
wallet, err := gateway.NewFileSystemWallet("wallet")
if err != nil {
return nil, fmt.Errorf("failed to create wallet: %v", err)
}
if !wallet.Exists("appUser") {
err := populateWallet(wallet)
if err != nil {
return nil, fmt.Errorf("failed to populate wallet contents: %v", err)
}
}
ccpPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"connection-org1.yaml",
)
gw, err := gateway.Connect(
gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
gateway.WithIdentity(wallet, "appUser"),
)
if err != nil {
return nil, fmt.Errorf("failed to connect to gateway: %v", err)
}
defer gw.Close()
network, err := gw.GetNetwork("mychannel")
if err != nil {
return nil, fmt.Errorf("failed to get network: %v", err)
}
contract := network.GetContract("bigdatacc")
var wg sync.WaitGroup
for i := 0; i < 1000; i++ {
wg.Add(1)
go func() ([]byte, error) {
defer wg.Done()
result, err := contract.SubmitTransaction(function, variableName, change, sign)
if err != nil {
return result, fmt.Errorf("failed to evaluate transaction: %v", err)
}
return result, nil
}()
}
wg.Wait()
result, err := contract.EvaluateTransaction("get", variableName)
if err != nil {
return nil, fmt.Errorf("failed to evaluate transaction: %v", err)
}
return result, err
}

View file

@ -0,0 +1,69 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"os"
"path/filepath"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)
// Query can be used to read the latest value of a variable
func Query(function, variableName string) ([]byte, error) {
err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
if err != nil {
return nil, fmt.Errorf("error setting DISCOVERY_AS_LOCALHOST environemnt variable: %v", err)
}
wallet, err := gateway.NewFileSystemWallet("wallet")
if err != nil {
return nil, fmt.Errorf("failed to create wallet: %v", err)
}
if !wallet.Exists("appUser") {
err = populateWallet(wallet)
if err != nil {
return nil, fmt.Errorf("failed to populate wallet contents: %v", err)
}
}
ccpPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"connection-org1.yaml",
)
gw, err := gateway.Connect(
gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
gateway.WithIdentity(wallet, "appUser"),
)
if err != nil {
return nil, fmt.Errorf("failed to connect to gateway: %v", err)
}
defer gw.Close()
network, err := gw.GetNetwork("mychannel")
if err != nil {
return nil, fmt.Errorf("failed to get network: %v", err)
}
contract := network.GetContract("bigdatacc")
result, err := contract.EvaluateTransaction(function, variableName)
if err != nil {
return nil, fmt.Errorf("failed to evaluate transaction: %v", err)
}
return result, err
}

View file

@ -0,0 +1,74 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"os"
"path/filepath"
"github.com/hyperledger/fabric-sdk-go/pkg/core/config"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)
// Update can be used to update or prune the variable
func Update(function, variableName, change, sign string) ([]byte, error) {
err := os.Setenv("DISCOVERY_AS_LOCALHOST", "true")
if err != nil {
return nil, fmt.Errorf("error setting DISCOVERY_AS_LOCALHOST environemnt variable: %v", err)
}
wallet, err := gateway.NewFileSystemWallet("wallet")
if err != nil {
return nil, fmt.Errorf("failed to create wallet: %v", err)
}
if !wallet.Exists("appUser") {
err := populateWallet(wallet)
if err != nil {
return nil, fmt.Errorf("failed to populate wallet contents: %v", err)
}
}
ccpPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"connection-org1.yaml",
)
gw, err := gateway.Connect(
gateway.WithConfig(config.FromFile(filepath.Clean(ccpPath))),
gateway.WithIdentity(wallet, "appUser"),
)
if err != nil {
return nil, fmt.Errorf("failed to connect to gateway: %v", err)
}
defer gw.Close()
network, err := gw.GetNetwork("mychannel")
if err != nil {
return nil, fmt.Errorf("failed to get network: %v", err)
}
contract := network.GetContract("bigdatacc")
result, err := contract.SubmitTransaction(function, variableName, change, sign)
if err != nil {
return result, fmt.Errorf("failed to Submit transaction: %v", err)
}
result, err = contract.EvaluateTransaction("get", variableName)
if err != nil {
return nil, fmt.Errorf("failed to evaluate transaction: %v", err)
}
return result, err
}

View file

@ -0,0 +1,55 @@
/*
Copyright 2020 IBM All Rights Reserved.
SPDX-License-Identifier: Apache-2.0
*/
package functions
import (
"fmt"
"io/ioutil"
"path/filepath"
"github.com/hyperledger/fabric-sdk-go/pkg/gateway"
)
func populateWallet(wallet *gateway.Wallet) error {
credPath := filepath.Join(
"..",
"..",
"test-network",
"organizations",
"peerOrganizations",
"org1.example.com",
"users",
"User1@org1.example.com",
"msp",
)
certPath := filepath.Join(credPath, "signcerts", "cert.pem")
// read the certificate pem
cert, err := ioutil.ReadFile(filepath.Clean(certPath))
if err != nil {
return err
}
keyDir := filepath.Join(credPath, "keystore")
// there's a single file in this dir containing the private key
files, err := ioutil.ReadDir(keyDir)
if err != nil {
return err
}
if len(files) != 1 {
return fmt.Errorf("keystore folder should have contain one file")
}
keyPath := filepath.Join(keyDir, files[0].Name())
key, err := ioutil.ReadFile(filepath.Clean(keyPath))
if err != nil {
return err
}
identity := gateway.NewX509Identity("Org1MSP", string(cert), string(key))
return wallet.Put("appUser", identity)
}

View file

@ -0,0 +1,5 @@
module github.com/hyperledger/fabric-samples/high-throughput/application-go
go 1.14
require github.com/hyperledger/fabric-sdk-go v1.0.0-beta3.0.20201006151309-9c426dcc5096

View file

@ -0,0 +1,224 @@
bitbucket.org/liamstask/goose v0.0.0-20150115234039-8488cc47d90c/go.mod h1:hSVuE3qU7grINVSwrmzHfpg9k87ALBk+XaualNyUzI4=
cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0=
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/Knetic/govaluate v3.0.0+incompatible h1:7o6+MAPhYTCF0+fdvoz1xDedhRb4f6s9Tn1Tt7/WTEg=
github.com/Knetic/govaluate v3.0.0+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0=
github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc=
github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
github.com/certifi/gocertifi v0.0.0-20180118203423-deb3ae2ef261/go.mod h1:GJKEexRPVJrBSOjoqN5VNOIKJ5Q3RViH6eu3puDRwx4=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/cloudflare/backoff v0.0.0-20161212185259-647f3cdfc87a/go.mod h1:rzgs2ZOiguV6/NpiDgADjRLPNyZlApIWxKpkT+X8SdY=
github.com/cloudflare/cfssl v1.4.1 h1:vScfU2DrIUI9VPHBVeeAQ0q5A+9yshO1Gz+3QoUQiKw=
github.com/cloudflare/cfssl v1.4.1/go.mod h1:KManx/OJPb5QY+y0+o/898AMcM128sF0bURvoVUSjTo=
github.com/cloudflare/go-metrics v0.0.0-20151117154305-6a9aea36fb41/go.mod h1:eaZPlJWD+G9wseg1BuRXlHnjntPMrywMsyxf+LTOdP4=
github.com/cloudflare/redoctober v0.0.0-20171127175943-746a508df14c/go.mod h1:6Se34jNoqrd8bTxrmJB2Bg2aoZ2CdSXonils9NsiNgo=
github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
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.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 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/getsentry/raven-go v0.0.0-20180121060056-563b81fc02b7/go.mod h1:KungGk8q33+aIAZUIVWZDr2OfAEBsO49PX4NzFV5kcQ=
github.com/go-kit/kit v0.8.0 h1:Wz+5lgoB0kkuqLEc6NVmwRknTKP6dTGbSqvhZtBI/j0=
github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as=
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0 h1:MP4Eh7ZCb31lleYCFuwm0oe4/YGak+5l1vA2NOE80nA=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-sql-driver/mysql v1.3.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
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/mock v1.4.3 h1:GV+pQPG/EUUbkh47niozDcADz6go/dUwhVzdUQHIVRw=
github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.3.3 h1:gyjaxf+svBWX08ZjK86iN9geUJF0H6gp2IRKX6Nf6/I=
github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw=
github.com/google/certificate-transparency-go v1.0.21 h1:Yf1aXowfZ2nuboBsg7iYGLmwsOARdV86pfH3g95wXmE=
github.com/google/certificate-transparency-go v1.0.21/go.mod h1:QeJfpSbVSfYc7RgB3gJFj9cbuQMMchQxrWXz8Ruopmg=
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/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/hyperledger/fabric-config v0.0.5 h1:khRkm8U9Ghdg8VmZfptgzCFlCzrka8bPfUkM+/j6Zlg=
github.com/hyperledger/fabric-config v0.0.5/go.mod h1:YpITBI/+ZayA3XWY5lF302K7PAsFYjEEPM/zr3hegA8=
github.com/hyperledger/fabric-lib-go v1.0.0 h1:UL1w7c9LvHZUSkIvHTDGklxFv2kTeva1QI2emOVc324=
github.com/hyperledger/fabric-lib-go v1.0.0/go.mod h1:H362nMlunurmHwkYqR5uHL2UDWbQdbfz74n8kbCFsqc=
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/hyperledger/fabric-sdk-go v1.0.0-beta3.0.20201006151309-9c426dcc5096 h1:veml7LmfavSHqF8w8z/PGGlfdXvmx5SstQIH6Nyy87c=
github.com/hyperledger/fabric-sdk-go v1.0.0-beta3.0.20201006151309-9c426dcc5096/go.mod h1:qWE9Syfg1KbwNjtILk70bJLilnmCvllIYFCSY/pa1RU=
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/jmhodges/clock v0.0.0-20160418191101-880ee4c33548/go.mod h1:hGT6jSUVzF6no3QaDSMLGLEHtHSBSefs+MgcDWnmhmo=
github.com/jmoiron/sqlx v0.0.0-20180124204410-05cef0741ade/go.mod h1:IiEW3SEiiErVyFdH8NTuWjSifiEQKUoyK3LNqr2kCHU=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w=
github.com/kisielk/sqlstruct v0.0.0-20150923205031-648daed35d49/go.mod h1:yyMNCyc/Ib3bDTKd379tNMpB/7/H5TjM2Y9QJ5THLbE=
github.com/kisom/goutils v1.1.0/go.mod h1:+UBTfd78habUYWFbNWTJNG+jNG/i/lGURakr4A/yNRw=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kylelemons/go-gypsy v0.0.0-20160905020020-08cad365cd28/go.mod h1:T/T7jsxVqf9k/zYOqbgNAsANsjxTd1Yq3htjDhQ1H0c=
github.com/lib/pq v0.0.0-20180201184707-88edab080323/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc=
github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/miekg/pkcs11 v1.0.3/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs=
github.com/mitchellh/mapstructure v1.3.2 h1:mRS76wmkOn3KkKAyXDu42V+6ebnXWIztFSYGN7GeoRg=
github.com/mitchellh/mapstructure v1.3.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0=
github.com/mreiferson/go-httpclient v0.0.0-20160630210159-31f0106b4474/go.mod h1:OQA4XLvDbMgS8P0CevmM4m9Q3Jq4phKUzcocxuGJ5m8=
github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/gomega v1.9.0/go.mod h1:Ho0h+IUsWyvy1OpqCwxlQ/21gkhVunqlU8fDGcoTdcA=
github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk=
github.com/pelletier/go-toml v1.8.0 h1:Keo9qb7iRJs2voHvunFtuuYFsbWeOBh8/P9v/kVMFtw=
github.com/pelletier/go-toml v1.8.0/go.mod h1:D6yutnOGMveHEPV7VQOuvI/gXY61bv+9bAOTRnLElKs=
github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I=
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pkg/sftp v1.10.1/go.mod h1:lYOWFsE0bwd1+KfKJaKeuokY15vzFx25BLbzYYoAxZI=
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_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw=
github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo=
github.com/prometheus/client_golang v1.1.0 h1:BQ53HtBmfOitExawJ6LokA4x8ov/z0SYYb0+HxJfRI8=
github.com/prometheus/client_golang v1.1.0/go.mod h1:I1FGZT9+L76gKKOs5djB6ezCbFQP1xR9D75/vuwEF3g=
github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo=
github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4 h1:gQz4mCbXsO+nc9n1hCxHcGA3Zx3Eo+UHZoInFGUIXNM=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4=
github.com/prometheus/common v0.6.0 h1:kRhiuYSXR3+uv2IbVbZhUxK5zVD/2pp3Gd2PpvPkpEo=
github.com/prometheus/common v0.6.0/go.mod h1:eBmuwkDJBwy6iBfxCBob6t6dR6ENT/y+J+Zk0j9GMYc=
github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk=
github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA=
github.com/prometheus/procfs v0.0.3 h1:CTwfnzjQ+8dS6MhHHu4YswVAD99sL2wjPqP+VkURmKE=
github.com/prometheus/procfs v0.0.3/go.mod h1:4A/X28fw3Fc593LaREMrKMqOKvUAntwMDaekg4FpcdQ=
github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/sirupsen/logrus v1.3.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/spf13/afero v1.3.1 h1:GPTpEAuNr98px18yNQ66JllNil98wfRZ/5Ukny8FeQA=
github.com/spf13/afero v1.3.1/go.mod h1:5KUK8ByomD5Ti5Artl0RtHeI5pTF7MIDuXL3yY520V4=
github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.1.1 h1:/8JBRFO4eoHu1TmpsLgNBq1CQgRUg4GolYlEFieqJgo=
github.com/spf13/viper v1.1.1/go.mod h1:A8kyI5cUJhb8N+3pkfONlcEcZbueH6nhAm0Fq7SrnBM=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
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/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8=
github.com/weppos/publicsuffix-go v0.4.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/weppos/publicsuffix-go v0.5.0 h1:rutRtjBJViU/YjcI5d80t4JAVvDltS6bciJg2K1HrLU=
github.com/weppos/publicsuffix-go v0.5.0/go.mod h1:z3LCPQ38eedDQSwmsSRW4Y7t2L8Ln16JPQ02lHAdn5k=
github.com/ziutek/mymysql v1.5.4/go.mod h1:LMSpPZ6DbqWFxNCHW77HeMg9I646SAhApZ/wKdgO/C0=
github.com/zmap/rc2 v0.0.0-20131011165748-24b9757f5521/go.mod h1:3YZ9o3WnatTIZhuOtot4IcUfzoKVjUHqu6WALIyI0nE=
github.com/zmap/zcertificate v0.0.0-20180516150559-0e3d58b1bac4/go.mod h1:5iU54tB79AMBcySS0R2XIyZBAVmeHranShAFELYx7is=
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e h1:mvOa4+/DXStR4ZXOks/UsjeFdn5O5JpLUtzqk9U8xXw=
github.com/zmap/zcrypto v0.0.0-20190729165852-9051775e6a2e/go.mod h1:w7kd3qXHh8FNaczNjslXqvFQiv5mMWRXlL9klTUAHc8=
github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb h1:vxqkjztXSaPVDc8FQCdHTaejm2x747f6yPbnu1h2xkg=
github.com/zmap/zlint v0.0.0-20190806154020-fd021b4cfbeb/go.mod h1:29UiAJNsiVdvTBFCJW8e3q6dcDbOoPkhMgttOSCIMMY=
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d h1:1ZiEyfaQIg3Qh0EoqpwAakHVhecoE5wlSg5GjnafJGw=
golang.org/x/crypto v0.0.0-20200221231518-2aa609cf4a9d/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/lint v0.0.0-20190930215403-16217165b5de/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-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/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-20190613194153-d28f0bde5980 h1:dfGZHvZk057jK2MCeWus/TowKpJ8y4AmooUzdBSR9GU=
golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
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-20181221193216-37e7f081c4d4/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-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/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=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3 h1:4y9KwBHBgBNwDbtu44R5o1fdOCQUEXhbk/P4A9WmJq0=
golang.org/x/sys v0.0.0-20190801041406-cbf593c0f2f3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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/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-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/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-20190819201941-24fa4b261c55 h1:gSJIx1SDwno+2ElGhA4+qG2zF97qiUzTM+rQ0klBOcE=
google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
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.29.1 h1:EC2SB8S04d2r73uptxphDSUG+kTKVgjRPF+N3xpxRB4=
google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk=
gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
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/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys=
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
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.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=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=

View file

@ -7,9 +7,11 @@
# Exit on first error # Exit on first error
set -ex set -ex
rm -rf bigdatacc.tar.gz log.txt
# Bring the test network down # Bring the test network down
pushd ../test-network pushd ../test-network
./network.sh down ./network.sh down
popd popd
rm -rf application-go/wallet/
rm -rf application-go/keystore/

View file

@ -1,44 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
export CORE_PEER_TLS_ENABLED=true
echo "========== Query chaincode package ID =========="
export CORE_PEER_MSPCONFIGPATH=../test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=../test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
peer lifecycle chaincode queryinstalled >&log.txt
export PACKAGE_ID=`sed -n '/Package/{s/^Package ID: //; s/, Label:.*$//; p;}' log.txt`
echo "========== Approve definition for Org1 =========="
export CORE_PEER_MSPCONFIGPATH=../test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=../test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
peer lifecycle chaincode install bigdatacc.tar.gz
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --channelID mychannel --signature-policy "OR('Org1MSP.peer', 'Org2MSP.peer')" --name bigdatacc --version 0 --init-required --package-id ${PACKAGE_ID} --sequence 1
echo "========== Approve definition for Org2 =========="
export CORE_PEER_MSPCONFIGPATH=../test-network/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=../test-network/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
peer lifecycle chaincode approveformyorg -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --channelID mychannel --signature-policy "OR('Org1MSP.peer', 'Org2MSP.peer')" --name bigdatacc --version 0 --init-required --package-id ${PACKAGE_ID} --sequence 1
. scripts/check-commit-readiness.sh
checkCommitReadiness 1 "\"Org1MSP\": true" "\"Org2MSP\": true"
checkCommitReadiness 2 "\"Org1MSP\": true" "\"Org2MSP\": true"
echo "========== Commit the definition the mychannel =========="
peer lifecycle chaincode commit -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem --channelID mychannel --signature-policy "OR('Org1MSP.peer', 'Org2MSP.peer')" --name bigdatacc --version 0 --init-required --sequence 1 --waitForEvent --peerAddresses localhost:7051 --tlsRootCertFiles ../test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ../test-network/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
echo "========== Invoke the Init function =========="
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n bigdatacc --isInit -c '{"Args":["Init"]}'

View file

@ -1,64 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
setGlobals() {
ORG=$1
if [ $ORG -eq 1 ]; then
CORE_PEER_LOCALMSPID="Org1MSP"
CORE_PEER_TLS_ROOTCERT_FILE=../test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
CORE_PEER_MSPCONFIGPATH=../test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
CORE_PEER_ADDRESS=localhost:7051
elif [ $ORG -eq 2 ]; then
CORE_PEER_LOCALMSPID="Org2MSP"
CORE_PEER_TLS_ROOTCERT_FILE=../test-network/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
CORE_PEER_MSPCONFIGPATH=../test-network/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
CORE_PEER_ADDRESS=localhost:9051
else
echo "================== ERROR !!! ORG Unknown =================="
fi
if [ "$VERBOSE" == "true" ]; then
env | grep CORE
fi
}
checkCommitReadiness() {
ORG=$1
shift 3
setGlobals $ORG
echo "===================== Simulating the commit of the chaincode definition on peer${PEER}.org${ORG} ===================== "
local rc=1
local starttime=$(date +%s)
# continue to poll
# we either get a successful response, or reach TIMEOUT
while
test "$(($(date +%s) - starttime))" -lt "$TIMEOUT" -a $rc -ne 0
do
sleep $DELAY
echo "Attempting to check the commit readiness of the chaincode definition on peer0.org${ORG} ...$(($(date +%s) - starttime)) secs"
set -x
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name bigdatacc --signature-policy "OR('Org1MSP.peer', 'Org2MSP.peer')" --version 0 --init-required --sequence 1 >&log.txt
res=$?
{ set +x; } 2>/dev/null
test $res -eq 0 || continue
let rc=0
for var in "$@"
do
grep "$var" log.txt &>/dev/null || let rc=1
done
done
echo
cat log.txt
if test $rc -eq 0; then
echo "===================== Checking the commit readiness of the chaincode definition successful on peer0.org${ORG} ===================== "
else
echo "!!!!!!!!!!!!!!! Check commit readiness result on peer0.org${ORG} is INVALID !!!!!!!!!!!!!!!!"
echo "================== ERROR !!! FAILED to execute End-2-End Scenario =================="
echo
exit 1
fi
}

View file

@ -1,9 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
source scripts/setenv.sh
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n bigdatacc -c '{"Args":["delstandard","'$1'"]}'

View file

@ -1,9 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
source scripts/setenv.sh
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n bigdatacc -c '{"Args":["delete","'$1'"]}'

View file

@ -1,9 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
source scripts/setenv.sh
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n bigdatacc -c '{"Args":["get","'$1'"]}'

View file

@ -1,9 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
source scripts/setenv.sh
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n bigdatacc -c '{"Args":["getstandard","'$1'"]}'

View file

@ -1,28 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
export CORE_PEER_TLS_ENABLED=true
echo "========== Package a chaincode =========="
export CORE_PEER_MSPCONFIGPATH=../test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=../test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
peer lifecycle chaincode package bigdatacc.tar.gz --path chaincode/ --lang golang --label bigdatacc_0
echo "========== Installing chaincode on peer0.org1 =========="
export CORE_PEER_MSPCONFIGPATH=../test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=../test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
peer lifecycle chaincode install bigdatacc.tar.gz
echo "========== Installing chaincode on peer0.org2 =========="
export CORE_PEER_MSPCONFIGPATH=../test-network/organizations/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051
export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=../test-network/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
peer lifecycle chaincode install bigdatacc.tar.gz

View file

@ -1,12 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
source scripts/setenv.sh
for (( i = 0; i < 1000; ++i ))
do
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n bigdatacc -c '{"Args":["putstandard","'$1'","'$i'"]}'
done

View file

@ -1,12 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
source scripts/setenv.sh
for (( i = 0; i < 1000; ++i ))
do
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n bigdatacc -c '{"Args":["update","'$1'","'$2'","'$3'"]}'
done

View file

@ -1,9 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
source scripts/setenv.sh
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n bigdatacc -c '{"Args":["prune","'$1'"]}'

View file

@ -1,8 +0,0 @@
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
export CORE_PEER_TLS_ENABLED=true
export CORE_PEER_MSPCONFIGPATH=../test-network/organizations/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051
export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=../test-network/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt

View file

@ -1,9 +0,0 @@
#
# Copyright IBM Corp All Rights Reserved
#
# SPDX-License-Identifier: Apache-2.0
#
source scripts/setenv.sh
peer chaincode invoke -o localhost:7050 --ordererTLSHostnameOverride orderer.example.com --tls --cafile ../test-network/organizations/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem -C mychannel -n bigdatacc -c '{"Args":["update","'$1'","'$2'","'$3'"]}'

View file

@ -18,20 +18,9 @@ pushd ../test-network
./network.sh down ./network.sh down
echo "Bring up test network" echo "Bring up test network"
./network.sh up createChannel ./network.sh up createChannel -ca
./network.sh deployCC -ccn bigdatacc -ccp ../high-throughput/chaincode-go/ -ccep "OR('Org1MSP.peer','Org2MSP.peer')" -cci Init
popd popd
#set enviroment varialbes
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=$PWD/../config/
echo "Install high throughput chaincode on test network peers"
./scripts/install-chaincode.sh
echo "Deploy high throughput chaincode to the channel"
./scripts/approve-commit-chaincode.sh
cat <<EOF cat <<EOF
Total setup execution time : $(($(date +%s) - starttime)) secs ... Total setup execution time : $(($(date +%s) - starttime)) secs ...

View file

@ -10,9 +10,9 @@ CouchDB.
## Getting started ## Getting started
This sample uses Node Fabric SDK application code similar to the `fabcar` sample This sample uses Node Fabric SDK application code to connect to a running instance
to connect to a running instance of the Fabric test network. Make sure that you of the Fabric test network. Make sure that you are running the following
are running the following commands from the `off_chain_data` directory. commands from the `off_chain_data` directory.
### Starting the Network ### Starting the Network
@ -25,7 +25,8 @@ Use the following command to start the sample network:
This command will deploy an instance of the Fabric test network. The network This command will deploy an instance of the Fabric test network. The network
consists of an ordering service, two peer organizations with one peers each, and consists of an ordering service, two peer organizations with one peers each, and
a CA for each org. The command also creates a channel named `mychannel`. The a CA for each org. The command also creates a channel named `mychannel`. The
marbles chaincode will be installed on both peers and deployed to the channel. `asset-transfer-basic` chaincode will be installed on both peers and deployed to
the channel.
### Configuration ### Configuration
@ -55,10 +56,11 @@ If you set the "use_couchdb" option to true in `config.json`, you can run the
following command start a local instance of CouchDB using docker: following command start a local instance of CouchDB using docker:
``` ```
docker run --publish 5990:5984 --detach --name offchaindb couchdb docker run --publish 5990:5984 --detach --name offchaindb couchdb:2.3.1
docker start offchaindb docker start offchaindb
``` ```
### Install dependencies ### Install dependencies
You need to install Node.js version 8.9.x to use the sample application code. You need to install Node.js version 8.9.x to use the sample application code.
@ -92,53 +94,7 @@ node blockEventListener.js
If the command is successful, you should see the output of the listener reading If the command is successful, you should see the output of the listener reading
the configuration blocks of `mychannel` in addition to the blocks that recorded the configuration blocks of `mychannel` in addition to the blocks that recorded
the approval and commitment of the marbles chaincode definition. the approval and commitment of the assets chaincode definition.
```
Listening for block events, nextblock: 0
Added block 0 to ProcessingMap
Added block 1 to ProcessingMap
Added block 2 to ProcessingMap
Added block 3 to ProcessingMap
Added block 4 to ProcessingMap
Added block 5 to ProcessingMap
Added block 6 to ProcessingMap
------------------------------------------------
Block Number: 0
------------------------------------------------
Block Number: 1
------------------------------------------------
Block Number: 2
------------------------------------------------
Block Number: 3
Block Timestamp: 2019-08-08T19:47:56.148Z
ChaincodeID: _lifecycle
[]
------------------------------------------------
Block Number: 4
Block Timestamp: 2019-08-08T19:48:00.234Z
ChaincodeID: _lifecycle
[]
------------------------------------------------
Block Number: 5
Block Timestamp: 2019-08-08T19:48:14.092Z
ChaincodeID: _lifecycle
[ { key: 'namespaces/fields/marbles/Collections',
is_delete: false,
value: '\u0012\u0000' },
{ key: 'namespaces/fields/marbles/EndorsementInfo',
is_delete: false,
value: '\u0012\r\n\u00031.0\u0010\u0001\u001a\u0004escc' },
{ key: 'namespaces/fields/marbles/Sequence',
is_delete: false,
value: '\b\u0001' },
{ key: 'namespaces/fields/marbles/ValidationInfo',
is_delete: false,
value: '\u00122\n\u0004vscc\u0012*\n(\u0012\f\u0012\n\b\u0002\u0012\u0002\b\u0000\u0012\u0002\b\u0001\u001a\u000b\u0012\t\n\u0007Org1MSP\u001a\u000b\u0012\t\n\u0007Org2MSP' },
{ key: 'namespaces/metadata/marbles',
is_delete: false,
value: '\n\u0013ChaincodeDefinition\u0012\bSequence\u0012\u000fEndorsementInfo\u0012\u000eValidationInfo\u0012\u000bCollections' } ]
```
`blockEventListener.js` creates a listener named "offchain-listener" on the `blockEventListener.js` creates a listener named "offchain-listener" on the
channel `mychannel`. The listener writes each block added to the channel to a channel `mychannel`. The listener writes each block added to the channel to a
@ -157,7 +113,7 @@ read-write set.
The channel event listener also writes metadata from each block to a log file The channel event listener also writes metadata from each block to a log file
defined as channelid_chaincodeid.log. In this example, events will be written to defined as channelid_chaincodeid.log. In this example, events will be written to
a file named `mychannel_marbles.log`. This allows you to record a history of a file named `mychannel_basic.log`. This allows you to record a history of
changes made by each block for each key in addition to storing the latest value changes made by each block for each key in addition to storing the latest value
of the world state. of the world state.
@ -166,42 +122,42 @@ new window to execute the next parts of the demo.
### Generate data on the blockchain ### Generate data on the blockchain
Now that our listener is setup, we can generate data using the marbles chaincode Now that our listener is setup, we can generate data using the assets chaincode
and use our application to replicate the data to our database. Open a new and use our application to replicate the data to our database. Open a new
terminal and navigate to the `fabric-samples/off_chain_data` directory. terminal and navigate to the `fabric-samples/off_chain_data` directory.
You can use the `addMarbles.js` file to add random sample data to blockchain. You can use the `addAssets.js` file to add random sample data to blockchain.
The file uses the configuration information stored in `addMarbles.json` to The file uses the configuration information stored in `addAssets.json` to
create a series of marbles. This file will be created during the first execution create a series of assets. This file will be created during the first execution
of `addMarbles.js` if it does not exist. This program can be run multiple times of `addAssets.js` if it does not exist. This program can be run multiple times
without changing the properties. The `nextMarbleNumber` will be incremented and without changing the properties. The `nextAssetNumber` will be incremented and
stored in the `addMarbles.json` file. stored in the `addAssets.json` file.
``` ```
{ {
"nextMarbleNumber": 100, "nextAssetNumber": 100,
"numberMarblesToAdd": 20 "numberAssetsToAdd": 20
} }
``` ```
Open a new window and run the following command to add 20 marbles to the Open a new window and run the following command to add 20 assets to the
blockchain: blockchain:
``` ```
node addMarbles.js node addAssets.js
``` ```
After the marbles have been added to the ledger, use the following command to After the assets have been added to the ledger, use the following command to
transfer one of the marbles to a new owner: transfer one of the assets to a new owner:
``` ```
node transferMarble.js marble110 james node transferAsset.js asset110 james
``` ```
Now run the following command to delete the marble that was transferred: Now run the following command to delete the asset that was transferred:
``` ```
node deleteMarble.js marble110 node deleteAsset.js asset110
``` ```
## Offchain CouchDB storage: ## Offchain CouchDB storage:
@ -215,17 +171,17 @@ The first table is an offline representation of the current world state of the
blockchain ledger. This table was created using the read-write set data from blockchain ledger. This table was created using the read-write set data from
the blocks. If the listener is running, this table should be the same as the the blocks. If the listener is running, this table should be the same as the
latest values in the state database running on your peer. The table is named latest values in the state database running on your peer. The table is named
after the channelid and chaincodeid, and is named mychannel_marbles in this after the channelid and chaincodeid, and is named mychannel_basic in this
example. You can navigate to this table using your browser: example. You can navigate to this table using your browser:
http://127.0.0.1:5990/mychannel_marbles/_all_docs http://127.0.0.1:5990/mychannel_basic/_all_docs
A second table records each block as a historical record entry, and was created A second table records each block as a historical record entry, and was created
using the block data that was recorded in the log file. The table name appends using the block data that was recorded in the log file. The table name appends
history to the name of the first table, and is named mychannel_marbles_history history to the name of the first table, and is named mychannel_basic_history
in this example. You can also navigate to this table using your browser: in this example. You can also navigate to this table using your browser:
http://127.0.0.1:5990/mychannel_marbles_history/_all_docs http://127.0.0.1:5990/mychannel_basic_history/_all_docs
### Configure a map/reduce view for summarizing counts of marbles by color: ### Configure a map/reduce view for summarizing counts of assets by color:
Now that we have state and history data replicated to tables in CouchDB, we Now that we have state and history data replicated to tables in CouchDB, we
can use the following commands query our off-chain data. We will also add an can use the following commands query our off-chain data. We will also add an
@ -236,16 +192,16 @@ created when events are received.
Open a new terminal window and execute the following: Open a new terminal window and execute the following:
``` ```
curl -X PUT http://127.0.0.1:5990/mychannel_marbles/_design/colorviewdesign -d '{"views":{"colorview":{"map":"function (doc) { emit(doc.color, 1);}","reduce":"function ( keys , values , combine ) {return sum( values )}"}}}' -H 'Content-Type:application/json' curl -X PUT http://127.0.0.1:5990/mychannel_basic/_design/colorviewdesign -d '{"views":{"colorview":{"map":"function (doc) { emit(doc.color, 1);}","reduce":"function ( keys , values , combine ) {return sum( values )}"}}}' -H 'Content-Type:application/json'
``` ```
Execute a query to retrieve the total number of marbles (reduce function): Execute a query to retrieve the total number of assets (reduce function):
``` ```
curl -X GET http://127.0.0.1:5990/mychannel_marbles/_design/colorviewdesign/_view/colorview?reduce=true curl -X GET http://127.0.0.1:5990/mychannel_basic/_design/colorviewdesign/_view/colorview?reduce=true
``` ```
If successful, this command will return the number of marbles in the blockchain If successful, this command will return the number of assets in the blockchain
world state, without having to query the blockchain ledger: world state, without having to query the blockchain ledger:
``` ```
@ -254,13 +210,13 @@ world state, without having to query the blockchain ledger:
]} ]}
``` ```
Execute a new query to retrieve the number of marbles by color (map function): Execute a new query to retrieve the number of assets by color (map function):
``` ```
curl -X GET http://127.0.0.1:5990/mychannel_marbles/_design/colorviewdesign/_view/colorview?group=true curl -X GET http://127.0.0.1:5990/mychannel_basic/_design/colorviewdesign/_view/colorview?group=true
``` ```
The command will return a list of marbles by color from the CouchDB database. The command will return a list of assets by color from the CouchDB database.
``` ```
{"rows":[ {"rows":[
@ -275,27 +231,27 @@ The command will return a list of marbles by color from the CouchDB database.
To run a more complex command that reads through the block history database, we To run a more complex command that reads through the block history database, we
will create an index of the blocknumber, sequence, and key fields. This index will create an index of the blocknumber, sequence, and key fields. This index
will support a query that traces the history of each marble. Execute the will support a query that traces the history of each asset. Execute the
following command to create the index: following command to create the index:
``` ```
curl -X POST http://127.0.0.1:5990/mychannel_marbles_history/_index -d '{"index":{"fields":["blocknumber", "sequence", "key"]},"name":"marble_history"}' -H 'Content-Type:application/json' curl -X POST http://127.0.0.1:5990/mychannel_basic_history/_index -d '{"index":{"fields":["blocknumber", "sequence", "key"]},"name":"asset_history"}' -H 'Content-Type:application/json'
``` ```
Now execute a query to retrieve the history for the marble we transferred and Now execute a query to retrieve the history for the asset we transferred and
then deleted: then deleted:
``` ```
curl -X POST http://127.0.0.1:5990/mychannel_marbles_history/_find -d '{"selector":{"key":{"$eq":"marble110"}}, "fields":["blocknumber","is_delete","value"],"sort":[{"blocknumber":"asc"}, {"sequence":"asc"}]}' -H 'Content-Type:application/json' curl -X POST http://127.0.0.1:5990/mychannel_basic_history/_find -d '{"selector":{"key":{"$eq":"asset110"}}, "fields":["blocknumber","is_delete","value"],"sort":[{"blocknumber":"asc"}, {"sequence":"asc"}]}' -H 'Content-Type:application/json'
``` ```
You should see the transaction history of the marble that was created, You should see the transaction history of the asset that was created,
transferred, and then removed from the ledger. transferred, and then removed from the ledger.
``` ```
{"docs":[ {"docs":[
{"blocknumber":12,"is_delete":false,"value":"{\"docType\":\"marble\",\"name\":\"marble110\",\"color\":\"blue\",\"size\":60,\"owner\":\"debra\"}"}, {"blocknumber":12,"is_delete":false,"value":"{\"docType\":\"asset\",\"name\":\"asset110\",\"color\":\"blue\",\"size\":60,\"owner\":\"debra\"}"},
{"blocknumber":22,"is_delete":false,"value":"{\"docType\":\"marble\",\"name\":\"marble110\",\"color\":\"blue\",\"size\":60,\"owner\":\"james\"}"}, {"blocknumber":22,"is_delete":false,"value":"{\"docType\":\"asset\",\"name\":\"asset110\",\"color\":\"blue\",\"size\":60,\"owner\":\"james\"}"},
{"blocknumber":23,"is_delete":true,"value":""} {"blocknumber":23,"is_delete":true,"value":""}
]} ]}
``` ```
@ -309,19 +265,19 @@ have missed.
If you ran through the example steps above, navigate back to the terminal window If you ran through the example steps above, navigate back to the terminal window
where `blockEventListener.js` is running and close it. Once the listener is no where `blockEventListener.js` is running and close it. Once the listener is no
longer running, use the following command to add 20 more marbles to the longer running, use the following command to add 20 more assets to the
ledger: ledger:
``` ```
node addMarbles.js node addAssets.js
``` ```
The listener will not be able to add the new marbles to your CouchDB database. The listener will not be able to add the new assets to your CouchDB database.
If you check the current state table using the reduce command, you will only If you check the current state table using the reduce command, you will only
be able to see the original marbles in your database. be able to see the original assets in your database.
``` ```
curl -X GET http://127.0.0.1:5990/mychannel_marbles/_design/colorviewdesign/_view/colorview?reduce=true curl -X GET http://127.0.0.1:5990/mychannel_basic/_design/colorviewdesign/_view/colorview?reduce=true
``` ```
To add the new data to your off-chain database, remove the `nextblock.txt` To add the new data to your off-chain database, remove the `nextblock.txt`
@ -337,15 +293,15 @@ You can new re-run the channel listener to read every block from the channel:
node blockEventListener.js node blockEventListener.js
``` ```
This will rebuild the CouchDB tables and include the 20 marbles that have been This will rebuild the CouchDB tables and include the 20 assets that have been
added to the ledger. If you run the reduce command against your database one added to the ledger. If you run the reduce command against your database one
more time, more time,
``` ```
curl -X GET http://127.0.0.1:5990/mychannel_marbles/_design/colorviewdesign/_view/colorview?reduce=true curl -X GET http://127.0.0.1:5990/mychannel_basic/_design/colorviewdesign/_view/colorview?reduce=true
``` ```
you will be able to see that all of the marbles have been added to your you will be able to see that all of the assets have been added to your
database: database:
``` ```
@ -369,4 +325,4 @@ Running the script will complete the following actions:
* Remove the certificates you generated by deleting the `wallet` folder. * Remove the certificates you generated by deleting the `wallet` folder.
* Delete `nextblock.txt` so you can start with the first block next time you * Delete `nextblock.txt` so you can start with the first block next time you
operate the listener. operate the listener.
* Removes `addMarbles.json`. * Removes `addAssets.json`.

118
off_chain_data/addAssets.js Normal file
View file

@ -0,0 +1,118 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
/*
*
* addAssets.js will add random sample data to blockchain.
*
* $ node addAssets.js
*
* addAssets will add 10 Assets by default with a starting Asset name of "Asset100".
* Additional Assets will be added by incrementing the number at the end of the Asset name.
*
* The properties for adding Assets are stored in addAssets.json. This file will be created
* during the first execution of the utility if it does not exist. The utility can be run
* multiple times without changing the properties. The nextAssetNumber will be incremented and
* stored in the JSON file.
*
* {
* "nextAssetNumber": 100,
* "numberAssetsToAdd": 10
* }
*
*/
'use strict';
const { Wallets, Gateway } = require('fabric-network');
const fs = require('fs');
const path = require('path');
const addAssetsConfigFile = path.resolve(__dirname, 'addAssets.json');
const colors=[ 'blue', 'red', 'yellow', 'green', 'white', 'purple' ];
const owners=[ 'tom', 'fred', 'julie', 'james', 'janet', 'henry', 'alice', 'marie', 'sam', 'debra', 'nancy'];
const sizes=[ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ];
const appraisedValues=[ 300, 310, 320, 330, 340, 350, 360, 370, 380, 390 ];
const docType='asset'
const config = require('./config.json');
const channelid = config.channelid;
async function main() {
try {
let nextAssetNumber;
let numberAssetsToAdd;
let addAssetsConfig;
// check to see if there is a config json defined
if (fs.existsSync(addAssetsConfigFile)) {
// read file the next asset and number of assets to create
let addAssetsConfigJSON = fs.readFileSync(addAssetsConfigFile, 'utf8');
addAssetsConfig = JSON.parse(addAssetsConfigJSON);
nextAssetNumber = addAssetsConfig.nextAssetNumber;
numberAssetsToAdd = addAssetsConfig.numberAssetsToAdd;
} else {
nextAssetNumber = 100;
numberAssetsToAdd = 20;
// create a default config and save
addAssetsConfig = new Object;
addAssetsConfig.nextAssetNumber = nextAssetNumber;
addAssetsConfig.numberAssetsToAdd = numberAssetsToAdd;
fs.writeFileSync(addAssetsConfigFile, JSON.stringify(addAssetsConfig, null, 2));
}
// Parse the connection profile.
const ccpPath = path.resolve(__dirname, '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json');
const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
// Configure a wallet. This wallet must already be primed with an identity that
// the application can use to interact with the peer node.
const walletPath = path.resolve(__dirname, 'wallet');
const wallet = await Wallets.newFileSystemWallet(walletPath);
// Create a new gateway, and connect to the gateway peer node(s). The identity
// specified must already exist in the specified wallet.
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'appUser', discovery: { enabled: true, asLocalhost: true } });
// Get the network channel that the smart contract is deployed to.
const network = await gateway.getNetwork(channelid);
// Get the smart contract from the network channel.
const contract = network.getContract('basic');
for (var counter = nextAssetNumber; counter < nextAssetNumber + numberAssetsToAdd; counter++) {
var randomColor = Math.floor(Math.random() * (6));
var randomOwner = Math.floor(Math.random() * (11));
var randomSize = Math.floor(Math.random() * (10));
var randomValue = Math.floor(Math.random() * (9));
// Submit the 'CreateAsset' transaction to the smart contract, and wait for it
// to be committed to the ledger.
await contract.submitTransaction('CreateAsset', docType+counter, colors[randomColor], ''+sizes[randomSize], owners[randomOwner],appraisedValues[randomValue]);
console.log("Adding asset: " + docType + counter + " owner:" + owners[randomOwner] + " color:" + colors[randomColor] + " size:" + '' + sizes[randomSize] + " appraised value:" + '' + appraisedValues[randomValue] );
}
await gateway.disconnect();
addAssetsConfig.nextAssetNumber = nextAssetNumber + numberAssetsToAdd;
fs.writeFileSync(addAssetsConfigFile, JSON.stringify(addAssetsConfig, null, 2));
} catch (error) {
console.error(`Failed to submit transaction: ${error}`);
process.exit(1);
}
}
main();

View file

@ -1,117 +0,0 @@
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*
*/
/*
*
* addMarbles.js will add random sample data to blockchain.
*
* $ node addMarbles.js
*
* addMarbles will add 10 marbles by default with a starting marble name of "marble100".
* Additional marbles will be added by incrementing the number at the end of the marble name.
*
* The properties for adding marbles are stored in addMarbles.json. This file will be created
* during the first execution of the utility if it does not exist. The utility can be run
* multiple times without changing the properties. The nextMarbleNumber will be incremented and
* stored in the JSON file.
*
* {
* "nextMarbleNumber": 100,
* "numberMarblesToAdd": 10
* }
*
*/
'use strict';
const { Wallets, Gateway } = require('fabric-network');
const fs = require('fs');
const path = require('path');
const addMarblesConfigFile = path.resolve(__dirname, 'addMarbles.json');
const colors=[ 'blue', 'red', 'yellow', 'green', 'white', 'purple' ];
const owners=[ 'tom', 'fred', 'julie', 'james', 'janet', 'henry', 'alice', 'marie', 'sam', 'debra', 'nancy'];
const sizes=[ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ];
const docType='marble'
const config = require('./config.json');
const channelid = config.channelid;
async function main() {
try {
let nextMarbleNumber;
let numberMarblesToAdd;
let addMarblesConfig;
// check to see if there is a config json defined
if (fs.existsSync(addMarblesConfigFile)) {
// read file the next marble and number of marbles to create
let addMarblesConfigJSON = fs.readFileSync(addMarblesConfigFile, 'utf8');
addMarblesConfig = JSON.parse(addMarblesConfigJSON);
nextMarbleNumber = addMarblesConfig.nextMarbleNumber;
numberMarblesToAdd = addMarblesConfig.numberMarblesToAdd;
} else {
nextMarbleNumber = 100;
numberMarblesToAdd = 20;
// create a default config and save
addMarblesConfig = new Object;
addMarblesConfig.nextMarbleNumber = nextMarbleNumber;
addMarblesConfig.numberMarblesToAdd = numberMarblesToAdd;
fs.writeFileSync(addMarblesConfigFile, JSON.stringify(addMarblesConfig, null, 2));
}
// Parse the connection profile. This would be the path to the file downloaded
// from the IBM Blockchain Platform operational console.
const ccpPath = path.resolve(__dirname, '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json');
const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
// Configure a wallet. This wallet must already be primed with an identity that
// the application can use to interact with the peer node.
const walletPath = path.resolve(__dirname, 'wallet');
const wallet = await Wallets.newFileSystemWallet(walletPath);
// Create a new gateway, and connect to the gateway peer node(s). The identity
// specified must already exist in the specified wallet.
const gateway = new Gateway();
await gateway.connect(ccp, { wallet, identity: 'appUser', discovery: { enabled: true, asLocalhost: true } });
// Get the network channel that the smart contract is deployed to.
const network = await gateway.getNetwork(channelid);
// Get the smart contract from the network channel.
const contract = network.getContract('marbles');
for (var counter = nextMarbleNumber; counter < nextMarbleNumber + numberMarblesToAdd; counter++) {
var randomColor = Math.floor(Math.random() * (6));
var randomOwner = Math.floor(Math.random() * (11));
var randomSize = Math.floor(Math.random() * (10));
// Submit the 'initMarble' transaction to the smart contract, and wait for it
// to be committed to the ledger.
await contract.submitTransaction('initMarble', docType+counter, colors[randomColor], ''+sizes[randomSize], owners[randomOwner]);
console.log("Adding marble: " + docType + counter + " owner:" + owners[randomOwner] + " color:" + colors[randomColor] + " size:" + '' + sizes[randomSize] );
}
await gateway.disconnect();
addMarblesConfig.nextMarbleNumber = nextMarbleNumber + numberMarblesToAdd;
fs.writeFileSync(addMarblesConfigFile, JSON.stringify(addMarblesConfig, null, 2));
} catch (error) {
console.error(`Failed to submit transaction: ${error}`);
process.exit(1);
}
}
main();

View file

@ -7,9 +7,9 @@
/* /*
* *
* deleteMarble.js will delete a specified marble. Example: * deleteAsset.js will delete a specified asset. Example:
* *
* $ node deleteMarble.js marble100 * $ node deleteAsset.js asset100
* *
* The utility is meant to demonstrate delete block events. * The utility is meant to demonstrate delete block events.
*/ */
@ -26,7 +26,7 @@ const channelid = config.channelid;
async function main() { async function main() {
if (process.argv[2] == undefined) { if (process.argv[2] == undefined) {
console.log("Usage: node deleteMarble marbleId"); console.log("Usage: node deleteAsset AssetId");
process.exit(1); process.exit(1);
} }
@ -34,8 +34,7 @@ async function main() {
try { try {
// Parse the connection profile. This would be the path to the file downloaded // Parse the connection profile.
// from the IBM Blockchain Platform operational console.
const ccpPath = path.resolve(__dirname, '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json'); const ccpPath = path.resolve(__dirname, '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json');
const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8')); const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
@ -53,10 +52,10 @@ async function main() {
const network = await gateway.getNetwork(channelid); const network = await gateway.getNetwork(channelid);
// Get the smart contract from the network channel. // Get the smart contract from the network channel.
const contract = network.getContract('marbles'); const contract = network.getContract('basic');
await contract.submitTransaction('delete', deletekey); await contract.submitTransaction('DeleteAsset', deletekey);
console.log("Deleted marble: " + deletekey); console.log("Deleted asset: " + deletekey);
await gateway.disconnect(); await gateway.disconnect();

View file

@ -14,7 +14,7 @@ popd
# clean out any old identites in the wallets # clean out any old identites in the wallets
rm -rf wallet rm -rf wallet
rm -rf addMarbles.json mychannel_marbles.log mychannel__lifecycle.log nextblock.txt rm -rf addAssets.json mychannel_basic.log mychannel__lifecycle.log nextblock.txt
docker stop offchaindb docker stop offchaindb
docker rm offchaindb docker rm offchaindb

View file

@ -15,8 +15,8 @@
"author": "Hyperledger", "author": "Hyperledger",
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"fabric-ca-client": "^2.1.0", "fabric-ca-client": "^2.2.0",
"fabric-network": "^2.1.0" "fabric-network": "^2.2.0"
}, },
"devDependencies": { "devDependencies": {
"chai": "^4.2.0", "chai": "^4.2.0",

View file

@ -7,161 +7,13 @@
# Exit on first error # Exit on first error
set -e pipefail set -e pipefail
# don't rewrite paths for Windows Git Bash users
export MSYS_NO_PATHCONV=1
starttime=$(date +%s) starttime=$(date +%s)
CC_SRC_LANGUAGE=golang
CC_RUNTIME_LANGUAGE=golang
CC_SRC_PATH=../chaincode/marbles02/go
echo Vendoring Go dependencies ...
pushd ../chaincode/marbles02/go
GO111MODULE=on go mod vendor
popd
echo Finished vendoring Go dependencies
# launch network; create channel and join peer to channel # launch network; create channel and join peer to channel
pushd ../test-network pushd ../test-network
./network.sh down ./network.sh down
./network.sh up createChannel -ca -s couchdb ./network.sh up createChannel -ca -s couchdb
./network.sh deployCC
export PATH=${PWD}/../bin:${PWD}:$PATH
export FABRIC_CFG_PATH=${PWD}/../config
# import environment variables
. scripts/envVar.sh
echo "Packaging the marbles smart contract"
setGlobals 1
peer lifecycle chaincode package marbles.tar.gz \
--path $CC_SRC_PATH \
--lang $CC_RUNTIME_LANGUAGE \
--label marblesv1
echo "Installing smart contract on peer0.org1.example.com"
peer lifecycle chaincode install marbles.tar.gz
echo "Installing smart contract on peer0.org2.example.com"
setGlobals 2
peer lifecycle chaincode install marbles.tar.gz
echo "Query the chaincode package id"
setGlobals 1
peer lifecycle chaincode queryinstalled >&log.txt
PACKAGE_ID=$(sed -n "/marblesv1/{s/^Package ID: //; s/, Label:.*$//; p;}" log.txt)
echo "Approving the chaincode definition for org1.example.com"
peer lifecycle chaincode approveformyorg \
-o localhost:7050 \
--ordererTLSHostnameOverride orderer.example.com \
--channelID mychannel \
--name marbles \
--version 1.0 \
--init-required \
--signature-policy AND"('Org1MSP.member','Org2MSP.member')" \
--sequence 1 \
--package-id $PACKAGE_ID \
--tls \
--cafile ${ORDERER_CA}
echo "Approving the chaincode definition for org2.example.com"
setGlobals 2
peer lifecycle chaincode approveformyorg \
-o localhost:7050 \
--ordererTLSHostnameOverride orderer.example.com \
--channelID mychannel \
--name marbles \
--version 1.0 \
--init-required \
--signature-policy AND"('Org1MSP.member','Org2MSP.member')" \
--sequence 1 \
--package-id $PACKAGE_ID \
--tls \
--cafile ${ORDERER_CA}
echo "Checking if the chaincode definition is ready to commit"
peer lifecycle chaincode checkcommitreadiness \
--channelID mychannel \
--name marbles \
--version 1.0 \
--sequence 1 \
--output json \
--init-required \
--signature-policy AND"('Org1MSP.member','Org2MSP.member')" >&log.txt
rc=0
for var in "\"Org1MSP\": true" "\"Org2MSP\": true"
do
grep "$var" log.txt &>/dev/null || let rc=1
done
if test $rc -eq 0; then
echo "Chaincode definition is ready to commit"
else
sleep 10
fi
parsePeerConnectionParameters 1 2
echo "Commit the chaincode definition to the channel"
peer lifecycle chaincode commit \
-o localhost:7050 \
--ordererTLSHostnameOverride orderer.example.com \
--channelID mychannel \
--name marbles \
--version 1.0 \
--init-required \
--signature-policy AND"('Org1MSP.member','Org2MSP.member')" \
--sequence 1 \
--tls \
--cafile ${ORDERER_CA} \
$PEER_CONN_PARMS
echo "Check if the chaincode has been committed to the channel ..."
peer lifecycle chaincode querycommitted \
--channelID mychannel \
--name marbles >&log.txt
EXPECTED_RESULT="Version: 1.0, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc"
VALUE=$(grep -o "Version: 1.0, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc" log.txt)
echo "$VALUE"
if [ "$VALUE" = "Version: 1.0, Sequence: 1, Endorsement Plugin: escc, Validation Plugin: vscc" ] ; then
echo "chaincode has been committed"
else
sleep 10
fi
echo "invoke the marbles chaincode init function ... "
peer chaincode invoke \
-o localhost:7050 \
--ordererTLSHostnameOverride orderer.example.com \
-C mychannel \
-n marbles \
--isInit \
-c '{"Args":["Init"]}' \
--tls \
--cafile ${ORDERER_CA} \
$PEER_CONN_PARMS
rm log.txt
popd popd

View file

@ -6,9 +6,9 @@
*/ */
/* /*
* tranferMarble.js will transfer ownership a specified marble to a new ownder. Example: * tranferAsset.js will transfer ownership a specified asset to a new ownder. Example:
* *
* $ node transferMarble.js marble102 jimmy * $ node transferAsset.js asset102 jimmy
* *
* The utility is meant to demonstrate update block events. * The utility is meant to demonstrate update block events.
*/ */
@ -25,7 +25,7 @@ const channelid = config.channelid;
async function main() { async function main() {
if (process.argv[2] == undefined && process.argv[3] == undefined) { if (process.argv[2] == undefined && process.argv[3] == undefined) {
console.log("Usage: node changeMarbleOwner.js marbleId owner"); console.log("Usage: node transferAsset.js assetId owner");
process.exit(1); process.exit(1);
} }
@ -34,8 +34,7 @@ async function main() {
try { try {
// Parse the connection profile. This would be the path to the file downloaded // Parse the connection profile.
// from the IBM Blockchain Platform operational console.
const ccpPath = path.resolve(__dirname, '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json'); const ccpPath = path.resolve(__dirname, '..', 'test-network','organizations','peerOrganizations','org1.example.com', 'connection-org1.json');
const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8')); const ccp = JSON.parse(fs.readFileSync(ccpPath, 'utf8'));
@ -53,10 +52,10 @@ async function main() {
const network = await gateway.getNetwork(channelid); const network = await gateway.getNetwork(channelid);
// Get the smart contract from the network channel. // Get the smart contract from the network channel.
const contract = network.getContract('marbles'); const contract = network.getContract('basic');
await contract.submitTransaction('transferMarble', updatekey, newowner); await contract.submitTransaction('TransferAsset', updatekey, newowner);
console.log("Transferred marble " + updatekey + " to " + newowner); console.log("Transferred asset " + updatekey + " to " + newowner);
await gateway.disconnect(); await gateway.disconnect();

View file

@ -11,7 +11,7 @@ networks:
services: services:
couchdb4: couchdb4:
container_name: couchdb4 container_name: couchdb4
image: couchdb:3.1 image: couchdb:3.1.1
# Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password
# for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode.
environment: environment:

View file

@ -217,6 +217,14 @@ Orderer: &OrdererDefaults
# Orderer Type: The orderer implementation to start # Orderer Type: The orderer implementation to start
OrdererType: etcdraft OrdererType: etcdraft
# Addresses used to be the list of orderer addresses that clients and peers
# could connect to. However, this does not allow clients to associate orderer
# addresses and orderer organizations which can be useful for things such
# as TLS validation. The preferred way to specify orderer addresses is now
# to include the OrdererEndpoints item in your org definition
Addresses:
- orderer.example.com:7050
EtcdRaft: EtcdRaft:
Consenters: Consenters:

View file

@ -11,7 +11,7 @@ networks:
services: services:
couchdb0: couchdb0:
container_name: couchdb0 container_name: couchdb0
image: couchdb:3.1 image: couchdb:3.1.1
# Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password
# for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode.
environment: environment:
@ -38,7 +38,7 @@ services:
couchdb1: couchdb1:
container_name: couchdb1 container_name: couchdb1
image: couchdb:3.1 image: couchdb:3.1.1
# Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password # Populate the COUCHDB_USER and COUCHDB_PASSWORD to set an admin user and password
# for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode.
environment: environment:

View file

@ -18,57 +18,6 @@ export VERBOSE=false
source scriptUtils.sh source scriptUtils.sh
# Print the usage message
function printHelp() {
println "Usage: "
println " network.sh <Mode> [Flags]"
println " Modes:"
println " \e[0;32mup\e[0m - bring up fabric orderer and peer nodes. No channel is created"
println " \e[0;32mup createChannel\e[0m - bring up fabric network with one channel"
println " \e[0;32mcreateChannel\e[0m - create and join a channel after the network is created"
println " \e[0;32mdeployCC\e[0m - deploy the asset transfer basic chaincode on the channel or specify"
println " \e[0;32mdown\e[0m - clear the network with docker-compose down"
println " \e[0;32mrestart\e[0m - restart the network"
println
println " Flags:"
println " Used with \e[0;32mnetwork.sh up\e[0m, \e[0;32mnetwork.sh createChannel\e[0m:"
println " -ca <use CAs> - create Certificate Authorities to generate the crypto material"
println " -c <channel name> - channel name to use (defaults to \"mychannel\")"
println " -s <dbtype> - the database backend to use: goleveldb (default) or couchdb"
println " -r <max retry> - CLI times out after certain number of attempts (defaults to 5)"
println " -d <delay> - delay duration in seconds (defaults to 3)"
println " -i <imagetag> - the tag to be used to launch the network (defaults to \"latest\")"
println " -cai <ca_imagetag> - the image tag to be used for CA (defaults to \"${CA_IMAGETAG}\")"
println " -verbose - verbose mode"
println " Used with \e[0;32mnetwork.sh deployCC\e[0m"
println " -c <channel name> - deploy chaincode to channel"
println " -ccn <name> - the short name of the chaincode to deploy: basic (default),ledger, private, sbe, secured"
println " -ccl <language> - the programming language of the chaincode to deploy: go (default), java, javascript, typescript"
println " -ccv <version> - chaincode version. 1.0 (default)"
println " -ccs <sequence> - chaincode definition sequence. Must be an integer, 1 (default), 2, 3, etc"
println " -ccp <path> - Optional, path to the chaincode. When provided the -ccn will be used as the deployed name and not the short name of the known chaincodes."
println " -ccep <policy> - Optional, chaincode endorsement policy, using signature policy syntax. The default policy requires an endorsement from Org1 and Org2"
println " -cccg <collection-config> - Optional, path to a private data collections configuration file"
println " -cci <fcn name> - Optional, chaincode init required function to invoke. When provided this function will be invoked after deployment of the chaincode and will define the chaincode as initialization required."
println
println " -h - print this message"
println
println " Possible Mode and flag combinations"
println " \e[0;32mup\e[0m -ca -c -r -d -s -i -verbose"
println " \e[0;32mup createChannel\e[0m -ca -c -r -d -s -i -verbose"
println " \e[0;32mcreateChannel\e[0m -c -r -d -verbose"
println " \e[0;32mdeployCC\e[0m -ccn -ccl -ccv -ccs -ccp -cci -r -d -verbose"
println
println " Taking all defaults:"
println " network.sh up"
println
println " Examples:"
println " network.sh up createChannel -ca -c mychannel -s couchdb -i 2.0.0"
println " network.sh createChannel -c channelName"
println " network.sh deployCC -ccn basic -ccl javascript"
println " network.sh deployCC -ccn mychaincode -ccp ./user/mychaincode -ccv 1 -ccl javascript"
}
# Obtain CONTAINER_IDS and remove them # Obtain CONTAINER_IDS and remove them
# TODO Might want to make this optional - could clear other containers # TODO Might want to make this optional - could clear other containers
# This function is called when you bring a network down # This function is called when you bring a network down
@ -362,7 +311,7 @@ function createChannel() {
} }
## Call the script to install and instantiate a chaincode on the channel ## Call the script to deploy a chaincode to the channel
function deployCC() { function deployCC() {
scripts/deployCC.sh $CHANNEL_NAME $CC_NAME $CC_SRC_PATH $CC_SRC_LANGUAGE $CC_VERSION $CC_SEQUENCE $CC_INIT_FCN $CC_END_POLICY $CC_COLL_CONFIG $CLI_DELAY $MAX_RETRY $VERBOSE scripts/deployCC.sh $CHANNEL_NAME $CC_NAME $CC_SRC_PATH $CC_SRC_LANGUAGE $CC_VERSION $CC_SEQUENCE $CC_INIT_FCN $CC_END_POLICY $CC_COLL_CONFIG $CLI_DELAY $MAX_RETRY $VERBOSE
@ -472,7 +421,7 @@ while [[ $# -ge 1 ]] ; do
key="$1" key="$1"
case $key in case $key in
-h ) -h )
printHelp printHelp $MODE
exit 0 exit 0
;; ;;
-c ) -c )
@ -579,9 +528,6 @@ elif [ "${MODE}" == "deployCC" ]; then
deployCC deployCC
elif [ "${MODE}" == "down" ]; then elif [ "${MODE}" == "down" ]; then
networkDown networkDown
elif [ "${MODE}" == "restart" ]; then
networkDown
networkUp
else else
printHelp printHelp
exit 1 exit 1

Some files were not shown because too many files have changed in this diff Show more