diff --git a/README.md b/README.md index 5b973748..81b69893 100644 --- a/README.md +++ b/README.md @@ -25,9 +25,9 @@ transfer an asset in a more realistic transfer scenario. | **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 | -| [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 | | [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) | | [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. | | | [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** | | diff --git a/asset-transfer-basic/application-go/assetTransfer.go b/asset-transfer-basic/application-go/assetTransfer.go index 10d40968..b9a100bb 100644 --- a/asset-transfer-basic/application-go/assetTransfer.go +++ b/asset-transfer-basic/application-go/assetTransfer.go @@ -63,12 +63,14 @@ func main() { contract := network.GetContract("basic") + log.Println("--> Submit Transaction: InitLedger, function creates the initial set of assets on the ledger") result, err := contract.SubmitTransaction("InitLedger") 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("--> Evaluate Transaction: GetAllAssets, function returns all the current assets on the ledger") result, err = contract.EvaluateTransaction("GetAllAssets") if err != nil { 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") result, err = contract.SubmitTransaction("CreateAsset", "asset13", "yellow", "5", "Tom", "1300") 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("--> Evaluate Transaction: ReadAsset, function returns an asset with a given assetID") result, err = contract.EvaluateTransaction("ReadAsset", "asset13") 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("--> Evaluate Transaction: AssetExists, function returns 'true' if an asset with given assetID exist") result, err = contract.EvaluateTransaction("AssetExists", "asset1") 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("--> Submit Transaction: TransferAsset asset1, transfer to new owner of Tom") _, err = contract.SubmitTransaction("TransferAsset", "asset1", "Tom") 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") diff --git a/asset-transfer-basic/application-go/go.mod b/asset-transfer-basic/application-go/go.mod index 345e094c..92928e17 100644 --- a/asset-transfer-basic/application-go/go.mod +++ b/asset-transfer-basic/application-go/go.mod @@ -2,4 +2,4 @@ module asset-transfer-basic 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 diff --git a/asset-transfer-basic/application-go/go.sum b/asset-transfer-basic/application-go/go.sum index d5ad7d7c..19c5b312 100644 --- a/asset-transfer-basic/application-go/go.sum +++ b/asset-transfer-basic/application-go/go.sum @@ -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= 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 h1:xJ4a3vCFaGF/jqvzLMYoU8P317H5OQ+Via4RmuPwCS0= 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 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 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 h1:72R+M5VuhED/KujmZVcIquuo8mBgX4oVda//DQb3PXo= 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.2.0 h1:28o5sBqPkBsMGnC6b4MvE2TzSr5/AT4c/1fLqVGIwlk= 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.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/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/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.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/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/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-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-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/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/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/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.7.6 h1:U+1DqNen04MdEPgFiIwdOUiqZ8qPa37xgogX/sd3+54= 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/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= 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/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/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/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/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.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.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/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/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/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/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/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/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/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/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.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/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/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 h1:oWX7TPOiFAMXLq8o0ikBYfCJVlRHBcsciT5bXOrH628= 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-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/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 h1:1BGLXjeY4akVXGgbC9HugT3Jv3hCI0z56oJR5vAMgBU= 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/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-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-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 h1:AzbTB6ux+okLTzP8Ru1Xs41C303zdcfEht7MQnYJt5A= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.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 h1:mUhvW9EsL+naU5Q3cakzfE91YhliOondGd6ZrsDBHQE= 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= diff --git a/asset-transfer-basic/application-javascript/app.js b/asset-transfer-basic/application-javascript/app.js index 43fd6260..28311489 100644 --- a/asset-transfer-basic/application-javascript/app.js +++ b/asset-transfer-basic/application-javascript/app.js @@ -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 // 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'); + result = await contract.submitTransaction('CreateAsset', 'asset13', 'yellow', '5', 'Tom', '1300'); + // 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'); result = await contract.evaluateTransaction('ReadAsset', 'asset13'); diff --git a/asset-transfer-basic/application-typescript/.gitignore b/asset-transfer-basic/application-typescript/.gitignore new file mode 100644 index 00000000..48285d12 --- /dev/null +++ b/asset-transfer-basic/application-typescript/.gitignore @@ -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 + diff --git a/asset-transfer-basic/application-typescript/package-lock.json b/asset-transfer-basic/application-typescript/package-lock.json new file mode 100644 index 00000000..8a71b6bf --- /dev/null +++ b/asset-transfer-basic/application-typescript/package-lock.json @@ -0,0 +1,2601 @@ +{ + "name": "asset-transfer-basic", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", + "dev": true, + "requires": { + "@babel/highlight": "^7.10.4" + } + }, + "@babel/generator": { + "version": "7.11.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.11.6.tgz", + "integrity": "sha512-DWtQ1PV3r+cLbySoHrwn9RWEgKMBLLma4OBQloPRyDYvc5msJM9kvTLo1YnlJd1P/ZuKbdli3ijr5q3FvAF3uA==", + "dev": true, + "requires": { + "@babel/types": "^7.11.5", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-function-name": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.10.4.tgz", + "integrity": "sha512-YdaSyz1n8gY44EmN7x44zBn9zQ1Ry2Y+3GTA+3vH6Mizke1Vw0aWDM66FOYEPw8//qKkmqOckrGgTYa+6sceqQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.10.4", + "@babel/template": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.10.4.tgz", + "integrity": "sha512-EkN3YDB+SRDgiIUnNgcmiD361ti+AVbL3f3Henf6dqqUyr5dMsorno0lJWJuLhDhkI5sYEpgj6y9kB8AOU1I2A==", + "dev": true, + "requires": { + "@babel/types": "^7.10.4" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.11.0.tgz", + "integrity": "sha512-74Vejvp6mHkGE+m+k5vHY93FX2cAtrw1zXrZXRlG4l410Nm9PxfEiVTn1PjDPV5SnmieiueY4AFg2xqhNFuuZg==", + "dev": true, + "requires": { + "@babel/types": "^7.11.0" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==", + "dev": true + }, + "@babel/highlight": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.11.5.tgz", + "integrity": "sha512-X9rD8qqm695vgmeaQ4fvz/o3+Wk4ZzQvSHkDBgpYKxpD4qTAUm88ZKtHkVqIOsYFFbIQ6wQYhC6q7pjqVK0E0Q==", + "dev": true + }, + "@babel/template": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.10.4.tgz", + "integrity": "sha512-ZCjD27cGJFUB6nmCB1Enki3r+L5kJveX9pq1SvAUKoICy6CZ9yD8xO086YXdYhvNjBdnekm4ZnaP5yC8Cs/1tA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/parser": "^7.10.4", + "@babel/types": "^7.10.4" + } + }, + "@babel/traverse": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.11.5.tgz", + "integrity": "sha512-EjiPXt+r7LiCZXEfRpSJd+jUMnBd4/9OUv7Nx3+0u9+eimMwJmG0Q98lw4/289JCoxSE8OolDMNZaaF/JZ69WQ==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.10.4", + "@babel/generator": "^7.11.5", + "@babel/helper-function-name": "^7.10.4", + "@babel/helper-split-export-declaration": "^7.11.0", + "@babel/parser": "^7.11.5", + "@babel/types": "^7.11.5", + "debug": "^4.1.0", + "globals": "^11.1.0", + "lodash": "^4.17.19" + } + }, + "@babel/types": { + "version": "7.11.5", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.11.5.tgz", + "integrity": "sha512-bvM7Qz6eKnJVFIn+1LPtjlBFPVN5jNDc1XmN15vWe7Q3DPBufWWsLiIvUu7xW87uTG6QoggpIDnUgLQvPheU+Q==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.10.4", + "lodash": "^4.17.19", + "to-fast-properties": "^2.0.0" + } + }, + "@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha1-m4sMxmPWaafY9vXQiToU00jzD78=" + }, + "@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==" + }, + "@protobufjs/codegen": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.4.tgz", + "integrity": "sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==" + }, + "@protobufjs/eventemitter": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.0.tgz", + "integrity": "sha1-NVy8mLr61ZePntCV85diHx0Ga3A=" + }, + "@protobufjs/fetch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.0.tgz", + "integrity": "sha1-upn7WYYUr2VwDBYZ/wbUVLDYTEU=", + "requires": { + "@protobufjs/aspromise": "^1.1.1", + "@protobufjs/inquire": "^1.1.0" + } + }, + "@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha1-Xp4avctz/Ap8uLKR33jIy9l7h9E=" + }, + "@protobufjs/inquire": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.0.tgz", + "integrity": "sha1-/yAOPnzyQp4tyvwRQIKOjMY48Ik=" + }, + "@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha1-bMKyDFya1q0NzP0hynZz2Nf79o0=" + }, + "@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha1-Cf0V8tbTq/qbZbw2ZQbWrXhG/1Q=" + }, + "@protobufjs/utf8": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", + "integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA=" + }, + "@sinonjs/commons": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.1.tgz", + "integrity": "sha512-892K+kWUUi3cl+LlqEWIDrhvLgdL79tECi8JZUyq6IviKy/DNhuzCRlbHUjxK89f4ypPMMaFnFuR9Ie6DoIMsw==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/formatio": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", + "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" + } + }, + "@sinonjs/samsam": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", + "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" + } + }, + "@sinonjs/text-encoding": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", + "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "dev": true + }, + "@types/caseless": { + "version": "0.12.2", + "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz", + "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w==" + }, + "@types/chai": { + "version": "4.2.12", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz", + "integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ==", + "dev": true + }, + "@types/long": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.1.tgz", + "integrity": "sha512-5tXH6Bx/kNGd3MgffdmP4dy2Z+G4eaXw0SE81Tq3BNadtnMR5/ySMzX4SLEzHJzSmPNn4HIdpQsBvXMUykr58w==" + }, + "@types/mocha": { + "version": "5.2.7", + "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", + "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", + "dev": true + }, + "@types/node": { + "version": "10.17.35", + "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.35.tgz", + "integrity": "sha512-gXx7jAWpMddu0f7a+L+txMplp3FnHl53OhQIF9puXKq3hDGY/GjH+MF04oWnV/adPSCrbtHumDCFwzq2VhltWA==" + }, + "@types/request": { + "version": "2.48.5", + "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.5.tgz", + "integrity": "sha512-/LO7xRVnL3DxJ1WkPGDQrp4VTV1reX9RkC85mJ+Qzykj2Bdw+mG15aAfDahc76HtknjzE16SX/Yddn6MxVbmGQ==", + "requires": { + "@types/caseless": "*", + "@types/node": "*", + "@types/tough-cookie": "*", + "form-data": "^2.5.0" + } + }, + "@types/sinon": { + "version": "5.0.7", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-5.0.7.tgz", + "integrity": "sha512-opwMHufhUwkn/UUDk35LDbKJpA2VBsZT8WLU8NjayvRLGPxQkN+8XmfC2Xl35MAscBE8469koLLBjaI3XLEIww==", + "dev": true + }, + "@types/sinon-chai": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/@types/sinon-chai/-/sinon-chai-3.2.4.tgz", + "integrity": "sha512-xq5KOWNg70PRC7dnR2VOxgYQ6paumW+4pTZP+6uTSdhpYsAUEeeT5bw6rRHHQrZ4KyR+M5ojOR+lje6TGSpUxA==", + "dev": true, + "requires": { + "@types/chai": "*", + "@types/sinon": "*" + } + }, + "@types/tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-I99sngh224D0M7XgW1s120zxCt3VYQ3IQsuw3P3jbq5GG4yc79+ZjyKznyOGIQrflfylLgcfekeZW/vk0yng6A==" + }, + "ajv": { + "version": "6.12.5", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.5.tgz", + "integrity": "sha512-lRF8RORchjpKG50/WFf8xmg7sgCLFiYNNnqdKflk63whMQcWR5ngGjiSXkL9bjxy6B2npOK2HSMN49jEBMSkag==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "append-transform": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz", + "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==", + "dev": true, + "requires": { + "default-require-extensions": "^2.0.0" + } + }, + "archy": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", + "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array-from": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", + "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", + "dev": true + }, + "asn1": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "requires": { + "safer-buffer": "~2.1.0" + } + }, + "assert-plus": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" + }, + "aws-sign2": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" + }, + "aws4": { + "version": "1.10.1", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.10.1.tgz", + "integrity": "sha512-zg7Hz2k5lI8kb7U32998pRRFin7zJlkfezGJjUc2heaD4Pw2wObakCDVzkKztTm/Ln7eiVvYsjqak0Ed4LkMDA==" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "bcrypt-pbkdf": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", + "requires": { + "tweetnacl": "^0.14.3" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + } + } + }, + "bn.js": { + "version": "4.11.9", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", + "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8=" + }, + "browser-request": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/browser-request/-/browser-request-0.3.3.tgz", + "integrity": "sha1-ns5bWsqJopkyJC4Yv5M975h2zBc=" + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "caching-transform": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz", + "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==", + "dev": true, + "requires": { + "hasha": "^3.0.0", + "make-dir": "^2.0.0", + "package-hash": "^3.0.0", + "write-file-atomic": "^2.4.2" + } + }, + "callsite": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/callsite/-/callsite-1.0.0.tgz", + "integrity": "sha1-KAOY5dZkvXQDi28JBRU+borxvCA=" + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "dev": true + }, + "caseless": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" + }, + "chai": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz", + "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==", + "dev": true, + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.0", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", + "dev": true + }, + "cloudant-follow": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/cloudant-follow/-/cloudant-follow-0.18.2.tgz", + "integrity": "sha512-qu/AmKxDqJds+UmT77+0NbM7Yab2K3w0qSeJRzsq5dRWJTEJdWeb+XpG4OpKuTE9RKOa/Awn2gR3TTnvNr3TeA==", + "requires": { + "browser-request": "~0.3.0", + "debug": "^4.0.1", + "request": "^2.88.0" + } + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "2.15.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + }, + "dependencies": { + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + } + } + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + }, + "cp-file": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz", + "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "make-dir": "^2.0.0", + "nested-error-stacks": "^2.0.0", + "pify": "^4.0.1", + "safe-buffer": "^5.0.1" + } + }, + "cross-spawn": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz", + "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=", + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + }, + "dependencies": { + "lru-cache": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz", + "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==", + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "yallist": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", + "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=", + "dev": true + } + } + }, + "cycle": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", + "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=" + }, + "dashdash": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" + }, + "deep-eql": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", + "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "dev": true, + "requires": { + "type-detect": "^4.0.0" + } + }, + "default-require-extensions": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz", + "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=", + "dev": true, + "requires": { + "strip-bom": "^3.0.0" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "dev": true + }, + "ecc-jsbn": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "requires": { + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" + } + }, + "elliptic": { + "version": "6.5.3", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.5.3.tgz", + "integrity": "sha512-IMqzv5wNQf+E6aHeIqATs0tOLeOTwj1QKbRcS3jBbYkl5oLAserA8yJTT7/VyHUYG91PRmPyeQDObKLPpeS4dw==", + "requires": { + "bn.js": "^4.4.0", + "brorand": "^1.0.1", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.0" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + }, + "dependencies": { + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + } + } + }, + "errs": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/errs/-/errs-0.3.2.tgz", + "integrity": "sha1-eYCZstvTfKK8dJ5TinwTB9C1BJk=" + }, + "es6-error": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", + "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "extend": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", + "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==" + }, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" + }, + "eyes": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", + "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=" + }, + "fabric-ca-client": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/fabric-ca-client/-/fabric-ca-client-2.2.2.tgz", + "integrity": "sha512-hC762b6kEjot7Z9mBfzZDkloKAQ6rNJiKPg3F9o56XfF10xeJ1wj0p6ULMKgQesSYMmre1g9TcALVdnH1NqS8w==", + "requires": { + "fabric-common": "2.2.2", + "jsrsasign": "^8.0.20", + "url": "^0.11.0", + "winston": "^2.4.0" + }, + "dependencies": { + "async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, + "winston": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.5.tgz", + "integrity": "sha512-TWoamHt5yYvsMarGlGEQE59SbJHqGsZV8/lwC+iCcGeAe0vUaOh+Lv6SYM17ouzC/a/LB1/hz/7sxFBtlu1l4A==", + "requires": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + } + } + } + }, + "fabric-common": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/fabric-common/-/fabric-common-2.2.2.tgz", + "integrity": "sha512-OhIvZTTis7tSRzztCzss3+BM6R274ZIM1yFc6VZHqubW7SSAy5qkGyzROSHQEim/6HM5meGgEKS5aHKrLzkMtw==", + "requires": { + "callsite": "^1.0.0", + "elliptic": "^6.5.2", + "fabric-protos": "2.2.2", + "js-sha3": "^0.7.0", + "jsrsasign": "^8.0.20", + "nconf": "^0.10.0", + "pkcs11js": "^1.0.6", + "promise-settle": "^0.3.0", + "sjcl": "1.0.7", + "winston": "^2.4.0", + "yn": "^3.1.0" + }, + "dependencies": { + "async": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", + "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=" + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, + "winston": { + "version": "2.4.5", + "resolved": "https://registry.npmjs.org/winston/-/winston-2.4.5.tgz", + "integrity": "sha512-TWoamHt5yYvsMarGlGEQE59SbJHqGsZV8/lwC+iCcGeAe0vUaOh+Lv6SYM17ouzC/a/LB1/hz/7sxFBtlu1l4A==", + "requires": { + "async": "~1.0.0", + "colors": "1.0.x", + "cycle": "1.0.x", + "eyes": "0.1.x", + "isstream": "0.1.x", + "stack-trace": "0.0.x" + } + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==" + } + } + }, + "fabric-network": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/fabric-network/-/fabric-network-2.2.2.tgz", + "integrity": "sha512-zPZ+yrSyHSVzDVS7f9EEMxN9nKOQ7aDg0RX76efevH7PQ7atDogijMnw2EdYnHBcw/t/ATMlMqaIxkKJNBoPjg==", + "requires": { + "fabric-common": "2.2.2", + "fabric-protos": "2.2.2", + "long": "^4.0.0", + "nano": "^8.2.2" + } + }, + "fabric-protos": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/fabric-protos/-/fabric-protos-2.2.2.tgz", + "integrity": "sha512-deXDIC4l9opKyu7zqZMXqiszxSOULKP66HmWZ435dgmQZyeo0rzFVO7dCwBZYdAVmz9pE4Ze8/qEU2GQ3suCJg==", + "requires": { + "@grpc/grpc-js": "1.0.3", + "@grpc/proto-loader": "0.5.4", + "protobufjs": "^6.9.0" + }, + "dependencies": { + "@grpc/grpc-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@grpc/grpc-js/-/grpc-js-1.0.3.tgz", + "integrity": "sha512-JKV3f5Bv2TZxK6eJSB9EarsZrnLxrvcFNwI9goq0YRXa3S6NNoCSnI3cG3lkXVIJ03Wng1WXe76kc2JQtRe7AQ==", + "requires": { + "semver": "^6.2.0" + } + }, + "@grpc/proto-loader": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/@grpc/proto-loader/-/proto-loader-0.5.4.tgz", + "integrity": "sha512-HTM4QpI9B2XFkPz7pjwMyMgZchJ93TVkL3kWPW8GDMDKYxsMnmf4w2TNMJK7+KNiYHS5cJrCEAFlF+AwtXWVPA==", + "requires": { + "lodash.camelcase": "^4.3.0", + "protobufjs": "^6.8.6" + } + } + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "find-cache-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz", + "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^2.0.0", + "pkg-dir": "^3.0.0" + } + }, + "foreground-child": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz", + "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=", + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "forever-agent": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" + }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", + "dev": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", + "requires": { + "assert-plus": "^1.0.0" + } + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "dev": true + }, + "har-schema": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" + }, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "requires": { + "ajv": "^6.12.3", + "har-schema": "^2.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "hash.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "requires": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, + "hasha": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz", + "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=", + "dev": true, + "requires": { + "is-stream": "^1.0.1" + }, + "dependencies": { + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=", + "dev": true + } + } + }, + "he": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", + "dev": true + }, + "hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha1-0nRXAQJabHdabFRXk+1QL8DGSaE=", + "requires": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "dev": true + }, + "html-escaper": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz", + "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", + "dev": true + }, + "http-signature": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "requires": { + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ini": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", + "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + }, + "invert-kv": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", + "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" + }, + "istanbul-lib-coverage": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz", + "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==", + "dev": true + }, + "istanbul-lib-hook": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz", + "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==", + "dev": true, + "requires": { + "append-transform": "^1.0.0" + } + }, + "istanbul-lib-instrument": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz", + "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==", + "dev": true, + "requires": { + "@babel/generator": "^7.4.0", + "@babel/parser": "^7.4.3", + "@babel/template": "^7.4.0", + "@babel/traverse": "^7.4.3", + "@babel/types": "^7.4.0", + "istanbul-lib-coverage": "^2.0.5", + "semver": "^6.0.0" + } + }, + "istanbul-lib-report": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz", + "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==", + "dev": true, + "requires": { + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "supports-color": "^6.1.0" + }, + "dependencies": { + "supports-color": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz", + "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz", + "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==", + "dev": true, + "requires": { + "debug": "^4.1.1", + "istanbul-lib-coverage": "^2.0.5", + "make-dir": "^2.1.0", + "rimraf": "^2.6.3", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "istanbul-reports": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.7.tgz", + "integrity": "sha512-uu1F/L1o5Y6LzPVSVZXNOoD/KXpJue9aeLRd0sM9uMXfZvzomB0WxVamWb5ue8kA2vVWEmW7EG+A5n3f1kqHKg==", + "dev": true, + "requires": { + "html-escaper": "^2.0.0" + } + }, + "js-sha3": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.7.0.tgz", + "integrity": "sha512-Wpks3yBDm0UcL5qlVhwW9Jr9n9i4FfeWBFOOXP5puDS/SiudJGhw7DPyBqn3487qD4F0lsC0q3zxink37f7zeA==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "js-yaml": { + "version": "3.14.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.0.tgz", + "integrity": "sha512-/4IbIeHcD9VMHFqDR/gQ7EdZdLimOvW2DdcxFjdyyZ9NsbS+ccrXqVWDtab/lRl5AlUqmpBx8EhPaWR+OtY17A==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "jsbn": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-schema": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", + "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=" + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stringify-safe": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" + }, + "jsprim": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", + "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", + "requires": { + "assert-plus": "1.0.0", + "extsprintf": "1.3.0", + "json-schema": "0.2.3", + "verror": "1.10.0" + } + }, + "jsrsasign": { + "version": "8.0.24", + "resolved": "https://registry.npmjs.org/jsrsasign/-/jsrsasign-8.0.24.tgz", + "integrity": "sha512-u45jAyusqUpyGbFc2IbHoeE4rSkoBWQgLe/w99temHenX+GyCz4nflU5sjK7ajU1ffZTezl6le7u43Yjr/lkQg==" + }, + "just-extend": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.1.1.tgz", + "integrity": "sha512-aWgeGFW67BP3e5181Ep1Fv2v8z//iBJfrvyTnq8wG86vEESwmonn1zPBJ0VfmT9CJq2FIT0VsETtrNFm2a+SHA==", + "dev": true + }, + "lcid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", + "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "lodash.camelcase": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", + "integrity": "sha1-soqmKIorn8ZRA1x3EfZathkDMaY=" + }, + "lodash.flattendeep": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz", + "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=", + "dev": true + }, + "lolex": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", + "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", + "dev": true + }, + "long": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", + "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" + }, + "make-dir": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz", + "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==", + "dev": true, + "requires": { + "pify": "^4.0.1", + "semver": "^5.6.0" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "make-error": { + "version": "1.3.6", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", + "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", + "dev": true + }, + "merge-source-map": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz", + "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==", + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "mime-db": { + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==" + }, + "mime-types": { + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "requires": { + "mime-db": "1.44.0" + } + }, + "minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" + }, + "minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", + "dev": true + }, + "mkdirp": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "mocha": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-5.2.0.tgz", + "integrity": "sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==", + "dev": true, + "requires": { + "browser-stdout": "1.3.1", + "commander": "2.15.1", + "debug": "3.1.0", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "glob": "7.1.2", + "growl": "1.10.5", + "he": "1.1.1", + "minimatch": "3.0.4", + "mkdirp": "0.5.1", + "supports-color": "5.4.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + } + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nan": { + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.1.tgz", + "integrity": "sha512-isWHgVjnFjh2x2yuJ/tj3JbwoHu3UC2dX5G/88Cm24yB6YopVgxvBObDY7n5xW6ExmFhJpSEQqFPvq9zaXc8Jw==", + "optional": true + }, + "nano": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/nano/-/nano-8.2.2.tgz", + "integrity": "sha512-1/rAvpd1J0Os0SazgutWQBx2buAq3KwJpmdIylPDqOwy73iQeAhTSCq3uzbGzvcNNW16Vv/BLXkk+DYcdcH+aw==", + "requires": { + "@types/request": "^2.48.4", + "cloudant-follow": "^0.18.2", + "debug": "^4.1.1", + "errs": "^0.3.2", + "request": "^2.88.0" + } + }, + "nconf": { + "version": "0.10.0", + "resolved": "https://registry.npmjs.org/nconf/-/nconf-0.10.0.tgz", + "integrity": "sha512-fKiXMQrpP7CYWJQzKkPPx9hPgmq+YLDyxcG9N8RpiE9FoCkCbzD0NyW0YhE3xn3Aupe7nnDeIx4PFzYehpHT9Q==", + "requires": { + "async": "^1.4.0", + "ini": "^1.3.0", + "secure-keys": "^1.0.0", + "yargs": "^3.19.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "camelcase": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" + }, + "cliui": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", + "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wrap-ansi": "^2.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "wrap-ansi": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + } + }, + "y18n": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", + "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + }, + "yargs": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-3.32.0.tgz", + "integrity": "sha1-AwiOnr+edWtpdRYR0qXvWRSCyZU=", + "requires": { + "camelcase": "^2.0.1", + "cliui": "^3.0.3", + "decamelize": "^1.1.1", + "os-locale": "^1.4.0", + "string-width": "^1.0.1", + "window-size": "^0.1.4", + "y18n": "^3.2.0" + } + } + } + }, + "nested-error-stacks": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz", + "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==", + "dev": true + }, + "nise": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", + "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "dev": true, + "requires": { + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^5.0.1", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "lolex": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", + "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.7.0" + } + } + } + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" + }, + "nyc": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz", + "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==", + "dev": true, + "requires": { + "archy": "^1.0.0", + "caching-transform": "^3.0.2", + "convert-source-map": "^1.6.0", + "cp-file": "^6.2.0", + "find-cache-dir": "^2.1.0", + "find-up": "^3.0.0", + "foreground-child": "^1.5.6", + "glob": "^7.1.3", + "istanbul-lib-coverage": "^2.0.5", + "istanbul-lib-hook": "^2.0.7", + "istanbul-lib-instrument": "^3.3.0", + "istanbul-lib-report": "^2.0.8", + "istanbul-lib-source-maps": "^3.0.6", + "istanbul-reports": "^2.2.4", + "js-yaml": "^3.13.1", + "make-dir": "^2.1.0", + "merge-source-map": "^1.1.0", + "resolve-from": "^4.0.0", + "rimraf": "^2.6.3", + "signal-exit": "^3.0.2", + "spawn-wrap": "^1.4.2", + "test-exclude": "^5.2.3", + "uuid": "^3.3.2", + "yargs": "^13.2.2", + "yargs-parser": "^13.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz", + "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dev": true, + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==", + "dev": true + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dev": true, + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dev": true, + "requires": { + "ansi-regex": "^4.1.0" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + } + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dev": true, + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dev": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "package-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz", + "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.15", + "hasha": "^3.0.0", + "lodash.flattendeep": "^4.4.0", + "release-zalgo": "^1.0.0" + } + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "path-to-regexp": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", + "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", + "dev": true, + "requires": { + "isarray": "0.0.1" + }, + "dependencies": { + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "dev": true + } + } + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "dev": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + } + } + }, + "pathval": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz", + "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=", + "dev": true + }, + "performance-now": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" + }, + "pify": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", + "dev": true + }, + "pkcs11js": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/pkcs11js/-/pkcs11js-1.0.22.tgz", + "integrity": "sha512-g0gOCCkKSa8YfoQPZFKk8VnbPvK+y3Gfx4O9NplzG6ntUX+1HSu491l8IUouxx45Jm3oEQXdDMtdTKH9t695aQ==", + "optional": true, + "requires": { + "nan": "^2.14.1" + } + }, + "pkg-dir": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", + "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==", + "dev": true, + "requires": { + "find-up": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "promise-settle": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/promise-settle/-/promise-settle-0.3.0.tgz", + "integrity": "sha1-tO/VcqHrdM95T4KM00naQKCOTpY=" + }, + "protobufjs": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.10.1.tgz", + "integrity": "sha512-pb8kTchL+1Ceg4lFd5XUpK8PdWacbvV5SK2ULH2ebrYtl4GjJmS24m6CKME67jzV53tbJxHlnNOSqQHbTsR9JQ==", + "requires": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/long": "^4.0.1", + "@types/node": "^13.7.0", + "long": "^4.0.0" + }, + "dependencies": { + "@types/node": { + "version": "13.13.21", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.21.tgz", + "integrity": "sha512-tlFWakSzBITITJSxHV4hg4KvrhR/7h3xbJdSFbYJBVzKubrASbnnIFuSgolUh7qKGo/ZeJPKUfbZ0WS6Jp14DQ==" + } + } + }, + "pseudomap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=", + "dev": true + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "querystring": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=" + }, + "read-pkg": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", + "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "dev": true, + "requires": { + "load-json-file": "^4.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^3.0.0" + } + }, + "read-pkg-up": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz", + "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==", + "dev": true, + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^3.0.0" + }, + "dependencies": { + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dev": true, + "requires": { + "locate-path": "^3.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dev": true, + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dev": true, + "requires": { + "p-limit": "^2.0.0" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + } + } + }, + "release-zalgo": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", + "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=", + "dev": true, + "requires": { + "es6-error": "^4.0.1" + } + }, + "request": { + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "requires": { + "aws-sign2": "~0.7.0", + "aws4": "^1.8.0", + "caseless": "~0.12.0", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", + "forever-agent": "~0.6.1", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", + "http-signature": "~1.2.0", + "is-typedarray": "~1.0.0", + "isstream": "~0.1.2", + "json-stringify-safe": "~5.0.1", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", + "performance-now": "^2.1.0", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", + "tunnel-agent": "^0.6.0", + "uuid": "^3.3.2" + }, + "dependencies": { + "form-data": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + } + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "dev": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "dev": true + }, + "resolve": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", + "integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", + "dev": true, + "requires": { + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "secure-keys": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/secure-keys/-/secure-keys-1.0.0.tgz", + "integrity": "sha1-8MgtmKOxOah3aogIBQuCRDEIf8o=" + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "sinon": { + "version": "7.5.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", + "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", + "dev": true, + "requires": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.3", + "diff": "^3.5.0", + "lolex": "^4.2.0", + "nise": "^1.5.2", + "supports-color": "^5.5.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "sinon-chai": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/sinon-chai/-/sinon-chai-3.5.0.tgz", + "integrity": "sha512-IifbusYiQBpUxxFJkR3wTU68xzBN0+bxCScEaKMjBvAQERg6FnTTc1F17rseLb1tjmkJ23730AXpFI0c47FgAg==", + "dev": true + }, + "sjcl": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/sjcl/-/sjcl-1.0.7.tgz", + "integrity": "sha1-MrNlpQ3Ju6JriLo8nfjqNCF9n0U=" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "spawn-wrap": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz", + "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==", + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==", + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.6.tgz", + "integrity": "sha512-+orQK83kyMva3WyPf59k1+Y525csj5JejicWut55zeTWANuN17qSiSLUXWtzHeNWORSvT7GLDJ/E/XiIWoXBTw==", + "dev": true + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "sshpk": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "requires": { + "asn1": "~0.2.3", + "assert-plus": "^1.0.0", + "bcrypt-pbkdf": "^1.0.0", + "dashdash": "^1.12.0", + "ecc-jsbn": "~0.1.1", + "getpass": "^0.1.1", + "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", + "tweetnacl": "~0.14.0" + }, + "dependencies": { + "tweetnacl": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", + "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" + } + } + }, + "stack-trace": { + "version": "0.0.10", + "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", + "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=" + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "supports-color": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", + "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "test-exclude": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz", + "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==", + "dev": true, + "requires": { + "glob": "^7.1.3", + "minimatch": "^3.0.4", + "read-pkg-up": "^4.0.0", + "require-main-filename": "^2.0.0" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "tough-cookie": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "requires": { + "psl": "^1.1.28", + "punycode": "^2.1.1" + } + }, + "ts-node": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-7.0.1.tgz", + "integrity": "sha512-BVwVbPJRspzNh2yfslyT1PSbl5uIk03EZlb493RKHN4qej/D06n1cEhjlOJG69oFsE7OT8XjpTUcYf6pKTLMhw==", + "dev": true, + "requires": { + "arrify": "^1.0.0", + "buffer-from": "^1.1.0", + "diff": "^3.1.0", + "make-error": "^1.1.1", + "minimist": "^1.2.0", + "mkdirp": "^0.5.1", + "source-map-support": "^0.5.6", + "yn": "^2.0.0" + }, + "dependencies": { + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "dev": true + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + } + } + }, + "tslib": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", + "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", + "dev": true + }, + "tslint": { + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.20.1.tgz", + "integrity": "sha512-EcMxhzCFt8k+/UP5r8waCf/lzmeSyVlqxqMEDQE7rWYiQky8KpIBz1JAoYXfROHrPZ1XXd43q8yQnULOLiBRQg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.1", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.8.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", + "requires": { + "safe-buffer": "^5.0.1" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "dev": true + }, + "typescript": { + "version": "3.9.7", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.9.7.tgz", + "integrity": "sha512-BLbiRkiBzAwsjut4x/dsibSTB6yWpwT5qWmC2OfuCg3GgVQCSgMs4vEctYPhsaGtd0AeuuHMkjZ2h2WG8MSzRw==", + "dev": true + }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "requires": { + "punycode": "^2.1.0" + } + }, + "url": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", + "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", + "requires": { + "punycode": "1.3.2", + "querystring": "0.2.0" + }, + "dependencies": { + "punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=" + } + } + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", + "dev": true + }, + "window-size": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/window-size/-/window-size-0.1.4.tgz", + "integrity": "sha1-+OGqHuWlPsW/FR/6CXQqatdpeHY=" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "write-file-atomic": { + "version": "2.4.3", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz", + "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "signal-exit": "^3.0.2" + } + }, + "y18n": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz", + "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==", + "dev": true + }, + "yn": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/yn/-/yn-2.0.0.tgz", + "integrity": "sha1-5a2ryKz0CPY4X8dklWhMiOavaJo=", + "dev": true + } + } +} diff --git a/asset-transfer-basic/application-typescript/package.json b/asset-transfer-basic/application-typescript/package.json new file mode 100644 index 00000000..beac7df9 --- /dev/null +++ b/asset-transfer-basic/application-typescript/package.json @@ -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 + } +} diff --git a/asset-transfer-basic/application-typescript/src/app.ts b/asset-transfer-basic/application-typescript/src/app.ts new file mode 100644 index 00000000..cd73a346 --- /dev/null +++ b/asset-transfer-basic/application-typescript/src/app.ts @@ -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(); diff --git a/asset-transfer-basic/application-typescript/src/utils/AppUtil.ts b/asset-transfer-basic/application-typescript/src/utils/AppUtil.ts new file mode 100644 index 00000000..f284e30f --- /dev/null +++ b/asset-transfer-basic/application-typescript/src/utils/AppUtil.ts @@ -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 => { + // 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 => { + // 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 => { + // 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, +}; diff --git a/asset-transfer-basic/application-typescript/src/utils/CAUtil.ts b/asset-transfer-basic/application-typescript/src/utils/CAUtil.ts new file mode 100644 index 00000000..665dd0e2 --- /dev/null +++ b/asset-transfer-basic/application-typescript/src/utils/CAUtil.ts @@ -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, 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 => { + 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 => { + 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, +}; diff --git a/asset-transfer-basic/application-typescript/tsconfig.json b/asset-transfer-basic/application-typescript/tsconfig.json new file mode 100644 index 00000000..8d357489 --- /dev/null +++ b/asset-transfer-basic/application-typescript/tsconfig.json @@ -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" + ] +} diff --git a/asset-transfer-basic/application-typescript/tslint.json b/asset-transfer-basic/application-typescript/tslint.json new file mode 100644 index 00000000..a52c3ee2 --- /dev/null +++ b/asset-transfer-basic/application-typescript/tslint.json @@ -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": [] +} diff --git a/asset-transfer-basic/chaincode-external/README.md b/asset-transfer-basic/chaincode-external/README.md index e5b6bed9..1b4a294e 100755 --- a/asset-transfer-basic/chaincode-external/README.md +++ b/asset-transfer-basic/chaincode-external/README.md @@ -1,8 +1,6 @@ # 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 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. +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. **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. diff --git a/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js b/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js index 6927eca1..d3a4fc47 100644 --- a/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js +++ b/asset-transfer-basic/chaincode-javascript/lib/assetTransfer.js @@ -72,7 +72,8 @@ class AssetTransfer extends Contract { Owner: owner, 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. diff --git a/asset-transfer-events/application-javascript/.eslintignore b/asset-transfer-events/application-javascript/.eslintignore new file mode 100644 index 00000000..15958470 --- /dev/null +++ b/asset-transfer-events/application-javascript/.eslintignore @@ -0,0 +1,5 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +coverage diff --git a/asset-transfer-events/application-javascript/.eslintrc.js b/asset-transfer-events/application-javascript/.eslintrc.js new file mode 100644 index 00000000..8422f8a8 --- /dev/null +++ b/asset-transfer-events/application-javascript/.eslintrc.js @@ -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'] + } +}; diff --git a/asset-transfer-events/application-javascript/.gitignore b/asset-transfer-events/application-javascript/.gitignore new file mode 100644 index 00000000..21b287f7 --- /dev/null +++ b/asset-transfer-events/application-javascript/.gitignore @@ -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 diff --git a/asset-transfer-events/application-javascript/app.js b/asset-transfer-events/application-javascript/app.js new file mode 100644 index 00000000..6ff641ac --- /dev/null +++ b/asset-transfer-events/application-javascript/app.js @@ -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(); diff --git a/asset-transfer-events/application-javascript/package.json b/asset-transfer-events/application-javascript/package.json new file mode 100644 index 00000000..2767aede --- /dev/null +++ b/asset-transfer-events/application-javascript/package.json @@ -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" + } +} diff --git a/asset-transfer-events/chaincode-javascript/.eslintignore b/asset-transfer-events/chaincode-javascript/.eslintignore new file mode 100644 index 00000000..15958470 --- /dev/null +++ b/asset-transfer-events/chaincode-javascript/.eslintignore @@ -0,0 +1,5 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +coverage diff --git a/asset-transfer-events/chaincode-javascript/.eslintrc.js b/asset-transfer-events/chaincode-javascript/.eslintrc.js new file mode 100644 index 00000000..cb00fa96 --- /dev/null +++ b/asset-transfer-events/chaincode-javascript/.eslintrc.js @@ -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 }] + } +}; diff --git a/asset-transfer-events/chaincode-javascript/.gitignore b/asset-transfer-events/chaincode-javascript/.gitignore new file mode 100644 index 00000000..eeace290 --- /dev/null +++ b/asset-transfer-events/chaincode-javascript/.gitignore @@ -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 diff --git a/asset-transfer-events/chaincode-javascript/index.js b/asset-transfer-events/chaincode-javascript/index.js new file mode 100644 index 00000000..3244cedf --- /dev/null +++ b/asset-transfer-events/chaincode-javascript/index.js @@ -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]; diff --git a/asset-transfer-events/chaincode-javascript/lib/assetTransferEvents.js b/asset-transfer-events/chaincode-javascript/lib/assetTransferEvents.js new file mode 100644 index 00000000..27c5acbd --- /dev/null +++ b/asset-transfer-events/chaincode-javascript/lib/assetTransferEvents.js @@ -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; diff --git a/asset-transfer-events/chaincode-javascript/package.json b/asset-transfer-events/chaincode-javascript/package.json new file mode 100644 index 00000000..143b35c7 --- /dev/null +++ b/asset-transfer-events/chaincode-javascript/package.json @@ -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 + } +} diff --git a/asset-transfer-events/chaincode-javascript/test/assetTransferEvents.test.js b/asset-transfer-events/chaincode-javascript/test/assetTransferEvents.test.js new file mode 100644 index 00000000..b243cdb9 --- /dev/null +++ b/asset-transfer-events/chaincode-javascript/test/assetTransferEvents.test.js @@ -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'})); + }); + }); +}); diff --git a/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js b/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js index 83a62049..31174f4e 100644 --- a/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js +++ b/asset-transfer-ledger-queries/chaincode-javascript/lib/asset_transfer_ledger_chaincode.js @@ -386,7 +386,7 @@ class Chaincode extends Contract { }, ]; - for (let asset in assets) { + for (const asset of assets) { await this.CreateAsset( ctx, asset.assetID, diff --git a/asset-transfer-private-data/application-javascript/app.js b/asset-transfer-private-data/application-javascript/app.js index 4c0edb26..0c861514 100644 --- a/asset-transfer-private-data/application-javascript/app.js +++ b/asset-transfer-private-data/application-javascript/app.js @@ -22,6 +22,11 @@ const mspOrg1 = 'Org1MSP'; const mspOrg2 = 'Org2MSP'; const Org1UserId = 'appUser1'; 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) { 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() { 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) @@ -115,8 +181,10 @@ async function main() { try { // Sample transactions are listed below // Add few sample Assets & transfers one of the asset from Org1 to Org2 as the new owner - let assetID1 = 'asset1'; - let assetID2 = 'asset2'; + let randomNumber = Math.floor(Math.random() * 1000) + 1; + // use a random key so that we can run multiple times + let assetID1 = `asset${randomNumber}`; + let assetID2 = `asset${randomNumber + 1}`; const assetType = 'ValuableAsset'; let result; 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'); // GetAssetByRange returns assets on the ledger with ID in the range of startKey (inclusive) and endKey (exclusive) result = await contractOrg1.evaluateTransaction('GetAssetByRange', 'asset0', 'asset9'); - console.log(' result: ' + prettyJSONString(result.toString())); - + console.log(`<-- result: ${prettyJSONString(result.toString())}`); + if (!result || result.length === 0) { + doFail('recieved empty query list for GetAssetByRange'); + } console.log('\n--> Evaluate Transaction: ReadAssetPrivateDetails from ' + org1PrivateCollectionName); // ReadAssetPrivateDetails reads data from Org's private collection. Args: collectionName, assetID result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); - console.log(' result: ' + prettyJSONString(result.toString())); + console.log(`<-- result: ${prettyJSONString(result.toString())}`); + verifyAssetPrivateDetails(result, assetID1, 100); // Attempt Transfer the asset to Org2 , without Org2 adding AgreeToTransfer // // Transaction should return an error: "failed transfer verification ..." @@ -171,9 +242,9 @@ async function main() { console.log('\n~~~~~~~~~~~~~~~~ As Org2 Client ~~~~~~~~~~~~~~~~'); console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID1); result = await contractOrg2.evaluateTransaction('ReadAsset', assetID1); - console.log(' result: ' + prettyJSONString(result.toString())); - let assetOwner = JSON.parse(result.toString()).owner; - console.log(' Asset owner: ' + Buffer.from(assetOwner, 'base64').toString()); + console.log(`<-- result: ${prettyJSONString(result.toString())}`); + verifyAssetData(mspOrg2, result, assetID1, 'green', 20, userOrg1IdentityString); + // Org2 cannot ReadAssetPrivateDetails from Org1's private collection due to Collection policy // 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 console.log('\n--> Evaluate Transaction: 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 // // 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: console.log('\n--> Evaluate Transaction: ReadAsset ' + assetID1); result = await contractOrg1.evaluateTransaction('ReadAsset', assetID1); - console.log(' result: ' + prettyJSONString(result.toString())); - assetOwner = JSON.parse(result.toString()).owner; - console.log(' Asset owner: ' + Buffer.from(assetOwner, 'base64').toString()); + console.log(`<-- result: ${prettyJSONString(result.toString())}`); + verifyAssetData(mspOrg1, result, assetID1, 'green', 20, userOrg2IdentityString); //Confirm that transfer removed the private details from the Org1 collection: console.log('\n--> Evaluate Transaction: ReadAssetPrivateDetails'); // ReadAssetPrivateDetails reads data from Org's private collection: Should return empty result = await contractOrg1.evaluateTransaction('ReadAssetPrivateDetails', org1PrivateCollectionName, assetID1); - console.log(' result: ' + prettyJSONString(result.toString())); - + console.log(`<-- result: ${prettyJSONString(result.toString())}`); + if (result && result.length > 0) { + doFail('Expected empty data from ReadAssetPrivateDetails'); + } console.log('\n--> Evaluate Transaction: 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 **************'); let dataForDelete = { assetID: assetID2 }; @@ -259,13 +332,17 @@ async function main() { console.log('\n--> Evaluate Transaction: 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 ~~~~~~~~~~~~~~~~'); // 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); result = await contractOrg2.evaluateTransaction('ReadAssetPrivateDetails', org2PrivateCollectionName, assetID1); - console.log(' result: ' + prettyJSONString(result.toString())); + console.log(`<-- result: ${prettyJSONString(result.toString())}`); + verifyAssetPrivateDetails(result, assetID1, 100); } finally { // Disconnect from the gateway peer when all work for this client identity is complete gatewayOrg1.disconnect(); diff --git a/asset-transfer-private-data/chaincode-go/META-INF/statedb/couchdb/collections/assetCollection/indexes/indexOwner.json b/asset-transfer-private-data/chaincode-go/META-INF/statedb/couchdb/collections/assetCollection/indexes/indexOwner.json index cef82360..e2d1d087 100644 --- a/asset-transfer-private-data/chaincode-go/META-INF/statedb/couchdb/collections/assetCollection/indexes/indexOwner.json +++ b/asset-transfer-private-data/chaincode-go/META-INF/statedb/couchdb/collections/assetCollection/indexes/indexOwner.json @@ -1 +1,12 @@ -{"index":{"fields":["type","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} +{ + "index": { + "fields": [ + "objectType", + "owner" + ] + }, + "ddoc": "indexOwnerDoc", + "name": "indexOwner", + "type": "json" +} + diff --git a/asset-transfer-private-data/chaincode-go/README.md b/asset-transfer-private-data/chaincode-go/README.md index b6eebfef..de65bbf4 100644 --- a/asset-transfer-private-data/chaincode-go/README.md +++ b/asset-transfer-private-data/chaincode-go/README.md @@ -1,246 +1 @@ -# Private data asset transfer scenario - -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 -``` +[Using Private Data tutorial](https://hyperledger-fabric.readthedocs.io/en/latest/private_data_tutorial.html) \ No newline at end of file diff --git a/asset-transfer-sbe/README.md b/asset-transfer-sbe/README.md index 576eda8d..d4b1cad1 100644 --- a/asset-transfer-sbe/README.md +++ b/asset-transfer-sbe/README.md @@ -19,7 +19,7 @@ state-based endorsement, visit the [Endorsement Policies](https://hyperledger-fabric.readthedocs.io/en/release-2.2/endorsement-policies.html) 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 diff --git a/asset-transfer-sbe/chaincode-java/src/main/java/org/hyperledger/fabric/samples/sbe/AssetContract.java b/asset-transfer-sbe/chaincode-java/src/main/java/org/hyperledger/fabric/samples/sbe/AssetContract.java index a99d2404..9843ee3b 100644 --- a/asset-transfer-sbe/chaincode-java/src/main/java/org/hyperledger/fabric/samples/sbe/AssetContract.java +++ b/asset-transfer-sbe/chaincode-java/src/main/java/org/hyperledger/fabric/samples/sbe/AssetContract.java @@ -70,11 +70,11 @@ public final class AssetContract implements ContractInterface { String assetJSON = genson.serialize(asset); stub.putStringState(assetId, assetJSON); - // Set the endorsement policy of the assetId Key, such that current owner Org Peer is required to endorse future updates - setAssetStateBasedEndorsement(ctx, assetId, new String[]{ownerOrg}); + // Set the endorsement policy of the assetId Key, such that current owner Org is required to endorse future updates + 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 - // setAssetStateBasedEndorsementWithNOutOfPolicy(ctx, assetId, 1, new String[]{"Org1MSP", "Org2MSP"}); + // 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; } @@ -164,7 +164,11 @@ public final class AssetContract implements ContractInterface { String updatedAssetJSON = genson.serialize(asset); 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; } @@ -195,13 +199,13 @@ public final class AssetContract implements ContractInterface { /** * 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 assetId the id of the asset * @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.addOrgs(StateBasedEndorsement.RoleType.RoleTypeMember, ownerOrgs); ctx.getStub().setStateValidationParameter(assetId, stateBasedEndorsement.policy()); @@ -209,21 +213,21 @@ public final class AssetContract implements ContractInterface { /** * 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 assetId the id of the asset * @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 */ - 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))); } /** - * 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 */ private static byte[] policy(final int nOrgs, final List mspids) { diff --git a/asset-transfer-sbe/chaincode-typescript/src/assetContract.ts b/asset-transfer-sbe/chaincode-typescript/src/assetContract.ts index 19921a11..493f85cb 100644 --- a/asset-transfer-sbe/chaincode-typescript/src/assetContract.ts +++ b/asset-transfer-sbe/chaincode-typescript/src/assetContract.ts @@ -5,6 +5,7 @@ import { Context, Contract, Info, Transaction } from 'fabric-contract-api'; import { Asset } from './asset'; 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' }) export class AssetContract extends Contract { @@ -26,8 +27,12 @@ export class AssetContract extends Contract { const buffer = Buffer.from(JSON.stringify(asset)); // Create the asset 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 @@ -78,7 +83,10 @@ export class AssetContract extends Contract { // Update the 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 - 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 @@ -87,16 +95,58 @@ export class AssetContract extends Contract { return (!!buffer && buffer.length > 0); } - // setAssetStateBasedEndorsement sets an endorsement policy to the assetId Key - // setAssetStateBasedEndorsement enforces that the owner Org Peers must endorse future update transactions for the specified assetId Key - private static async setAssetStateBasedEndorsement(ctx: Context, assetId: string, ownerOrgs: string[]): Promise { + // getClientOrgId gets the client's OrgId (MSPID) + private static getClientOrgId(ctx: Context): string { + 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 { const ep = new KeyEndorsementPolicy(); ep.addOrgs('MEMBER', ...ownerOrgs); await ctx.stub.setStateValidationParameter(assetId, ep.getPolicy()); } - // getClientOrgId gets the client's OrgId (MSPID) - private static getClientOrgId(ctx: Context): string { - return ctx.clientIdentity.getMSPID(); + // setStateBasedEndorsementNOutOf sets an endorsement policy to the assetId Key + // setStateBasedEndorsementNOutOf enforces that a given number of Orgs (N) out of the specified Orgs must endorse future update transactions for the specified assetId Key. + private static async setStateBasedEndorsementNOutOf(ctx: Context, assetId: string, nOrgs:number, ownerOrgs: string[]): Promise { + 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(); } } diff --git a/auction/README.md b/auction/README.md new file mode 100644 index 00000000..1ab0cfc9 --- /dev/null +++ b/auction/README.md @@ -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 +```` diff --git a/auction/application-javascript/bid.js b/auction/application-javascript/bid.js new file mode 100644 index 00000000..a5540d80 --- /dev/null +++ b/auction/application-javascript/bid.js @@ -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(); diff --git a/auction/application-javascript/closeAuction.js b/auction/application-javascript/closeAuction.js new file mode 100644 index 00000000..7934fbf5 --- /dev/null +++ b/auction/application-javascript/closeAuction.js @@ -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(); diff --git a/auction/application-javascript/createAuction.js b/auction/application-javascript/createAuction.js new file mode 100644 index 00000000..8d083d70 --- /dev/null +++ b/auction/application-javascript/createAuction.js @@ -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(); diff --git a/auction/application-javascript/endAuction.js b/auction/application-javascript/endAuction.js new file mode 100644 index 00000000..3c16c7fc --- /dev/null +++ b/auction/application-javascript/endAuction.js @@ -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(); diff --git a/auction/application-javascript/enrollAdmin.js b/auction/application-javascript/enrollAdmin.js new file mode 100644 index 00000000..e2df9cfb --- /dev/null +++ b/auction/application-javascript/enrollAdmin.js @@ -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(); diff --git a/auction/application-javascript/package.json b/auction/application-javascript/package.json new file mode 100644 index 00000000..b9d531c1 --- /dev/null +++ b/auction/application-javascript/package.json @@ -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" + } +} diff --git a/auction/application-javascript/queryAuction.js b/auction/application-javascript/queryAuction.js new file mode 100644 index 00000000..258d19c1 --- /dev/null +++ b/auction/application-javascript/queryAuction.js @@ -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(); diff --git a/auction/application-javascript/queryBid.js b/auction/application-javascript/queryBid.js new file mode 100644 index 00000000..b4ffea69 --- /dev/null +++ b/auction/application-javascript/queryBid.js @@ -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(); diff --git a/auction/application-javascript/registerEnrollUser.js b/auction/application-javascript/registerEnrollUser.js new file mode 100644 index 00000000..3ee97a6a --- /dev/null +++ b/auction/application-javascript/registerEnrollUser.js @@ -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(); diff --git a/auction/application-javascript/revealBid.js b/auction/application-javascript/revealBid.js new file mode 100644 index 00000000..8b397439 --- /dev/null +++ b/auction/application-javascript/revealBid.js @@ -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(); diff --git a/auction/application-javascript/submitBid.js b/auction/application-javascript/submitBid.js new file mode 100644 index 00000000..ef6cf2a4 --- /dev/null +++ b/auction/application-javascript/submitBid.js @@ -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(); diff --git a/auction/chaincode-go/go.mod b/auction/chaincode-go/go.mod new file mode 100644 index 00000000..26f43afa --- /dev/null +++ b/auction/chaincode-go/go.mod @@ -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 +) diff --git a/auction/chaincode-go/go.sum b/auction/chaincode-go/go.sum new file mode 100644 index 00000000..f052c527 --- /dev/null +++ b/auction/chaincode-go/go.sum @@ -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= diff --git a/auction/chaincode-go/smart-contract/auction.go b/auction/chaincode-go/smart-contract/auction.go new file mode 100644 index 00000000..c197e7e5 --- /dev/null +++ b/auction/chaincode-go/smart-contract/auction.go @@ -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 +} diff --git a/auction/chaincode-go/smart-contract/auctionQueries.go b/auction/chaincode-go/smart-contract/auctionQueries.go new file mode 100644 index 00000000..64eb45bf --- /dev/null +++ b/auction/chaincode-go/smart-contract/auctionQueries.go @@ -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 +} diff --git a/auction/chaincode-go/smart-contract/utils.go b/auction/chaincode-go/smart-contract/utils.go new file mode 100644 index 00000000..8c305246 --- /dev/null +++ b/auction/chaincode-go/smart-contract/utils.go @@ -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 +} diff --git a/auction/chaincode-go/smartContract.go b/auction/chaincode-go/smartContract.go new file mode 100644 index 00000000..8aa5637b --- /dev/null +++ b/auction/chaincode-go/smartContract.go @@ -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) + } +} diff --git a/chaincode/fabcar/javascript/package.json b/chaincode/fabcar/javascript/package.json index 8403de8e..1adb28b6 100644 --- a/chaincode/fabcar/javascript/package.json +++ b/chaincode/fabcar/javascript/package.json @@ -4,8 +4,8 @@ "description": "FabCar contract implemented in JavaScript", "main": "index.js", "engines": { - "node": ">=8", - "npm": ">=5" + "node": ">=12", + "npm": ">=6.9" }, "scripts": { "lint": "eslint .", diff --git a/ci/azure-pipelines.yml b/ci/azure-pipelines.yml index 0314cf39..5d314a8f 100644 --- a/ci/azure-pipelines.yml +++ b/ci/azure-pipelines.yml @@ -238,3 +238,18 @@ jobs: - script: ../ci/scripts/run-test-network-secured.sh workingDirectory: test-network 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 diff --git a/ci/scripts/pullFabricImages.sh b/ci/scripts/pullFabricImages.sh index f58566b3..38cb8e2d 100755 --- a/ci/scripts/pullFabricImages.sh +++ b/ci/scripts/pullFabricImages.sh @@ -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}" done -docker pull -q couchdb:3.1 +docker pull -q couchdb:3.1.1 docker images | grep hyperledger diff --git a/ci/scripts/run-test-network-basic.sh b/ci/scripts/run-test-network-basic.sh index 58cf8168..e6e38d66 100755 --- a/ci/scripts/run-test-network-basic.sh +++ b/ci/scripts/run-test-network-basic.sh @@ -24,13 +24,13 @@ function stopNetwork() { } # Run Go application -#createNetwork -#print "Initializing Go application" -#pushd ../asset-transfer-basic/application-go -#print "Executing AssetTransfer.go" -#go run . -#popd -#stopNetwork +createNetwork +print "Initializing Go application" +pushd ../asset-transfer-basic/application-go +print "Executing AssetTransfer.go" +go run . +popd +stopNetwork # Run Java application createNetwork @@ -50,3 +50,15 @@ print "Executing app.js" node app.js popd 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 diff --git a/ci/scripts/run-test-network-events.sh b/ci/scripts/run-test-network-events.sh new file mode 100755 index 00000000..5899c0c4 --- /dev/null +++ b/ci/scripts/run-test-network-events.sh @@ -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 diff --git a/commercial-paper/organization/digibank/application/buy.js b/commercial-paper/organization/digibank/application/buy.js index f71508b6..b052b75d 100644 --- a/commercial-paper/organization/digibank/application/buy.js +++ b/commercial-paper/organization/digibank/application/buy.js @@ -9,7 +9,7 @@ * 1. Select an identity from a wallet * 2. Connect to network gateway * 3. Access PaperNet network - * 4. Construct request to issue commercial paper + * 4. Construct request to buy commercial paper * 5. Submit transaction * 6. Process response */ diff --git a/commercial-paper/organization/digibank/digibank.sh b/commercial-paper/organization/digibank/digibank.sh index 3aad75d2..414d4d2e 100755 --- a/commercial-paper/organization/digibank/digibank.sh +++ b/commercial-paper/organization/digibank/digibank.sh @@ -15,7 +15,7 @@ function _exit(){ : ${VERBOSE:="false"} # Where am I? -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +DIR=${PWD} # Locate the 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 -cd ${DIR} \ No newline at end of file +cd ${DIR} diff --git a/commercial-paper/organization/magnetocorp/magnetocorp.sh b/commercial-paper/organization/magnetocorp/magnetocorp.sh index af456384..784602ca 100755 --- a/commercial-paper/organization/magnetocorp/magnetocorp.sh +++ b/commercial-paper/organization/magnetocorp/magnetocorp.sh @@ -15,7 +15,7 @@ function _exit(){ : ${VERBOSE:="false"} # Where am I? -DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +DIR=${PWD} # Locate the 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"/' rm /tmp/env.orig -cd ${DIR} \ No newline at end of file +cd ${DIR} diff --git a/fabcar/javascript/package-lock.json b/fabcar/javascript/package-lock.json index 065751b8..ab6f7cc9 100644 --- a/fabcar/javascript/package-lock.json +++ b/fabcar/javascript/package-lock.json @@ -3384,9 +3384,9 @@ } }, "yargs-parser": { - "version": "13.1.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz", - "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==", + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", "dev": true, "requires": { "camelcase": "^5.0.0", diff --git a/high-throughput/README.md b/high-throughput/README.md index 0badf673..1314b56c 100644 --- a/high-throughput/README.md +++ b/high-throughput/README.md @@ -96,11 +96,6 @@ must be verified before approval and admittance to the chain. ## How 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 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: ``` -Total setup execution time : 141 secs ... +Total setup execution time : 81 secs ... ``` The `high-throughput` chaincode is now ready to receive invocations. ### 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 -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 -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!) +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. -Example: `./scripts/update-invoke.sh myvar 100 +` +Example: `go run app.go update myvar 100 +` -#### Get -The format for get is: `./get-invoke.sh name` where `name` is the name of the variable to get. +#### Query +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 -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. +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 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 -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 -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 -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. +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. -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: -`./scripts/many-updates.sh testvar 100 +` --> final value from `./scripts/get-invoke.sh testvar` should be 100000 +Run the following command to create and update `testvar1` a 1000 times: +``` +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 diff --git a/high-throughput/application-go/.gitignore b/high-throughput/application-go/.gitignore new file mode 100755 index 00000000..e9fec12f --- /dev/null +++ b/high-throughput/application-go/.gitignore @@ -0,0 +1,4 @@ +wallet +!wallet/.gitkeep + +keystore diff --git a/high-throughput/application-go/app.go b/high-throughput/application-go/app.go new file mode 100644 index 00000000..100d7c8e --- /dev/null +++ b/high-throughput/application-go/app.go @@ -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)) + } +} diff --git a/high-throughput/application-go/functions/deletePrune.go b/high-throughput/application-go/functions/deletePrune.go new file mode 100644 index 00000000..12492660 --- /dev/null +++ b/high-throughput/application-go/functions/deletePrune.go @@ -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 +} diff --git a/high-throughput/application-go/functions/manyUpdates.go b/high-throughput/application-go/functions/manyUpdates.go new file mode 100644 index 00000000..521b2e47 --- /dev/null +++ b/high-throughput/application-go/functions/manyUpdates.go @@ -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 +} diff --git a/high-throughput/application-go/functions/query.go b/high-throughput/application-go/functions/query.go new file mode 100644 index 00000000..94716524 --- /dev/null +++ b/high-throughput/application-go/functions/query.go @@ -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 +} diff --git a/high-throughput/application-go/functions/update.go b/high-throughput/application-go/functions/update.go new file mode 100644 index 00000000..eb77cd5d --- /dev/null +++ b/high-throughput/application-go/functions/update.go @@ -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 +} diff --git a/high-throughput/application-go/functions/util.go b/high-throughput/application-go/functions/util.go new file mode 100644 index 00000000..3447f57e --- /dev/null +++ b/high-throughput/application-go/functions/util.go @@ -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) +} diff --git a/high-throughput/application-go/go.mod b/high-throughput/application-go/go.mod new file mode 100644 index 00000000..1b2dcd25 --- /dev/null +++ b/high-throughput/application-go/go.mod @@ -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 diff --git a/high-throughput/application-go/go.sum b/high-throughput/application-go/go.sum new file mode 100644 index 00000000..506e0a6b --- /dev/null +++ b/high-throughput/application-go/go.sum @@ -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= diff --git a/high-throughput/chaincode/go.mod b/high-throughput/chaincode-go/go.mod similarity index 100% rename from high-throughput/chaincode/go.mod rename to high-throughput/chaincode-go/go.mod diff --git a/high-throughput/chaincode/go.sum b/high-throughput/chaincode-go/go.sum similarity index 100% rename from high-throughput/chaincode/go.sum rename to high-throughput/chaincode-go/go.sum diff --git a/high-throughput/chaincode/high-throughput.go b/high-throughput/chaincode-go/high-throughput.go similarity index 100% rename from high-throughput/chaincode/high-throughput.go rename to high-throughput/chaincode-go/high-throughput.go diff --git a/high-throughput/networkDown.sh b/high-throughput/networkDown.sh index 5798f011..6e898f50 100755 --- a/high-throughput/networkDown.sh +++ b/high-throughput/networkDown.sh @@ -7,9 +7,11 @@ # Exit on first error set -ex -rm -rf bigdatacc.tar.gz log.txt - # Bring the test network down pushd ../test-network ./network.sh down popd + + +rm -rf application-go/wallet/ +rm -rf application-go/keystore/ \ No newline at end of file diff --git a/high-throughput/scripts/approve-commit-chaincode.sh b/high-throughput/scripts/approve-commit-chaincode.sh deleted file mode 100755 index 054ddb53..00000000 --- a/high-throughput/scripts/approve-commit-chaincode.sh +++ /dev/null @@ -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"]}' diff --git a/high-throughput/scripts/check-commit-readiness.sh b/high-throughput/scripts/check-commit-readiness.sh deleted file mode 100755 index 7d4b9c39..00000000 --- a/high-throughput/scripts/check-commit-readiness.sh +++ /dev/null @@ -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 -} diff --git a/high-throughput/scripts/del-traditional.sh b/high-throughput/scripts/del-traditional.sh deleted file mode 100644 index e54d7aeb..00000000 --- a/high-throughput/scripts/del-traditional.sh +++ /dev/null @@ -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'"]}' diff --git a/high-throughput/scripts/delete-invoke.sh b/high-throughput/scripts/delete-invoke.sh deleted file mode 100755 index ced25628..00000000 --- a/high-throughput/scripts/delete-invoke.sh +++ /dev/null @@ -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'"]}' diff --git a/high-throughput/scripts/get-invoke.sh b/high-throughput/scripts/get-invoke.sh deleted file mode 100755 index 53375abf..00000000 --- a/high-throughput/scripts/get-invoke.sh +++ /dev/null @@ -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'"]}' diff --git a/high-throughput/scripts/get-traditional.sh b/high-throughput/scripts/get-traditional.sh deleted file mode 100755 index fa5c1b68..00000000 --- a/high-throughput/scripts/get-traditional.sh +++ /dev/null @@ -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'"]}' diff --git a/high-throughput/scripts/install-chaincode.sh b/high-throughput/scripts/install-chaincode.sh deleted file mode 100755 index cac7f4ac..00000000 --- a/high-throughput/scripts/install-chaincode.sh +++ /dev/null @@ -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 diff --git a/high-throughput/scripts/many-updates-traditional.sh b/high-throughput/scripts/many-updates-traditional.sh deleted file mode 100755 index dd67792f..00000000 --- a/high-throughput/scripts/many-updates-traditional.sh +++ /dev/null @@ -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 diff --git a/high-throughput/scripts/many-updates.sh b/high-throughput/scripts/many-updates.sh deleted file mode 100755 index 7d05272f..00000000 --- a/high-throughput/scripts/many-updates.sh +++ /dev/null @@ -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 diff --git a/high-throughput/scripts/prune-invoke.sh b/high-throughput/scripts/prune-invoke.sh deleted file mode 100755 index 832843a3..00000000 --- a/high-throughput/scripts/prune-invoke.sh +++ /dev/null @@ -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'"]}' diff --git a/high-throughput/scripts/setenv.sh b/high-throughput/scripts/setenv.sh deleted file mode 100644 index c924f4fa..00000000 --- a/high-throughput/scripts/setenv.sh +++ /dev/null @@ -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 diff --git a/high-throughput/scripts/update-invoke.sh b/high-throughput/scripts/update-invoke.sh deleted file mode 100755 index 86351a68..00000000 --- a/high-throughput/scripts/update-invoke.sh +++ /dev/null @@ -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'"]}' diff --git a/high-throughput/startFabric.sh b/high-throughput/startFabric.sh index eb4dd8ef..50fe2324 100755 --- a/high-throughput/startFabric.sh +++ b/high-throughput/startFabric.sh @@ -18,20 +18,9 @@ pushd ../test-network ./network.sh down 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 - -#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 <&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 +./network.sh deployCC popd diff --git a/off_chain_data/transferMarble.js b/off_chain_data/transferAsset.js similarity index 75% rename from off_chain_data/transferMarble.js rename to off_chain_data/transferAsset.js index 93af01dc..42c07b6e 100644 --- a/off_chain_data/transferMarble.js +++ b/off_chain_data/transferAsset.js @@ -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. */ @@ -25,7 +25,7 @@ const channelid = config.channelid; async function main() { 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); } @@ -34,8 +34,7 @@ async function main() { try { - // Parse the connection profile. This would be the path to the file downloaded - // from the IBM Blockchain Platform operational console. + // 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')); @@ -53,10 +52,10 @@ async function main() { const network = await gateway.getNetwork(channelid); // Get the smart contract from the network channel. - const contract = network.getContract('marbles'); + const contract = network.getContract('basic'); - await contract.submitTransaction('transferMarble', updatekey, newowner); - console.log("Transferred marble " + updatekey + " to " + newowner); + await contract.submitTransaction('TransferAsset', updatekey, newowner); + console.log("Transferred asset " + updatekey + " to " + newowner); await gateway.disconnect(); diff --git a/test-network/addOrg3/docker/docker-compose-couch-org3.yaml b/test-network/addOrg3/docker/docker-compose-couch-org3.yaml index cad09f28..8c08a24e 100644 --- a/test-network/addOrg3/docker/docker-compose-couch-org3.yaml +++ b/test-network/addOrg3/docker/docker-compose-couch-org3.yaml @@ -11,7 +11,7 @@ networks: services: 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 # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. environment: diff --git a/test-network/configtx/configtx.yaml b/test-network/configtx/configtx.yaml index d280ccbc..ad72c1e7 100644 --- a/test-network/configtx/configtx.yaml +++ b/test-network/configtx/configtx.yaml @@ -217,6 +217,14 @@ Orderer: &OrdererDefaults # Orderer Type: The orderer implementation to start 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: Consenters: diff --git a/test-network/docker/docker-compose-couch.yaml b/test-network/docker/docker-compose-couch.yaml index c0906fd0..1b84e129 100644 --- a/test-network/docker/docker-compose-couch.yaml +++ b/test-network/docker/docker-compose-couch.yaml @@ -11,7 +11,7 @@ networks: services: 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 # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. environment: @@ -38,7 +38,7 @@ services: 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 # for CouchDB. This will prevent CouchDB from operating in an "Admin Party" mode. environment: diff --git a/test-network/network.sh b/test-network/network.sh index 4f3818c1..4cb19ddc 100755 --- a/test-network/network.sh +++ b/test-network/network.sh @@ -18,57 +18,6 @@ export VERBOSE=false source scriptUtils.sh -# Print the usage message -function printHelp() { - println "Usage: " - println " network.sh [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 - create Certificate Authorities to generate the crypto material" - println " -c - channel name to use (defaults to \"mychannel\")" - println " -s - the database backend to use: goleveldb (default) or couchdb" - println " -r - CLI times out after certain number of attempts (defaults to 5)" - println " -d - delay duration in seconds (defaults to 3)" - println " -i - the tag to be used to launch the network (defaults to \"latest\")" - println " -cai - 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 - deploy chaincode to channel" - println " -ccn - the short name of the chaincode to deploy: basic (default),ledger, private, sbe, secured" - println " -ccl - the programming language of the chaincode to deploy: go (default), java, javascript, typescript" - println " -ccv - chaincode version. 1.0 (default)" - println " -ccs - chaincode definition sequence. Must be an integer, 1 (default), 2, 3, etc" - println " -ccp - 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 - Optional, chaincode endorsement policy, using signature policy syntax. The default policy requires an endorsement from Org1 and Org2" - println " -cccg - Optional, path to a private data collections configuration file" - println " -cci - 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 # TODO Might want to make this optional - could clear other containers # 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() { 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" case $key in -h ) - printHelp + printHelp $MODE exit 0 ;; -c ) @@ -579,9 +528,6 @@ elif [ "${MODE}" == "deployCC" ]; then deployCC elif [ "${MODE}" == "down" ]; then networkDown -elif [ "${MODE}" == "restart" ]; then - networkDown - networkUp else printHelp exit 1 diff --git a/test-network/scriptUtils.sh b/test-network/scriptUtils.sh index 3b46a1a8..043d33f0 100755 --- a/test-network/scriptUtils.sh +++ b/test-network/scriptUtils.sh @@ -6,6 +6,119 @@ C_GREEN='\033[0;32m' C_BLUE='\033[0;34m' C_YELLOW='\033[1;33m' +# Print the usage message +function printHelp() { + USAGE="$1" + if [ "$USAGE" == "up" ]; then + println "Usage: " + println " network.sh \033[0;32mup\033[0m [Flags]" + println + println " Flags:" + println " -ca - Use Certificate Authorities to generate network crypto material" + println " -c - Name of channel to create (defaults to \"mychannel\")" + println " -s - Peer state database to deploy: goleveldb (default) or couchdb" + println " -r - CLI times out after certain number of attempts (defaults to 5)" + println " -d - CLI delays for a certain number of seconds (defaults to 3)" + println " -i - Docker image tag of Fabric to deploy (defaults to \"latest\")" + println " -cai - Docker image tag of Fabric CA to deploy (defaults to \"${CA_IMAGETAG}\")" + println " -verbose - Verbose mode" + println + println " -h - Print this message" + println + println " Possible Mode and flag combinations" + println " \033[0;32mup\033[0m -ca -r -d -s -i -cai -verbose" + println " \033[0;32mup createChannel\033[0m -ca -c -r -d -s -i -cai -verbose" + println + println " Examples:" + println " network.sh up createChannel -ca -c mychannel -s couchdb -i 2.0.0" + elif [ "$USAGE" == "createChannel" ]; then + println "Usage: " + println " network.sh \033[0;32mcreateChannel\033[0m [Flags]" + println + println " Flags:" + println " -c - Name of channel to create (defaults to \"mychannel\")" + println " -r - CLI times out after certain number of attempts (defaults to 5)" + println " -d - CLI delays for a certain number of seconds (defaults to 3)" + println " -verbose - Verbose mode" + println + println " -h - Print this message" + println + println " Possible Mode and flag combinations" + println " \033[0;32mcreateChannel\033[0m -c -r -d -verbose" + println + println " Examples:" + println " network.sh createChannel -c channelName" + elif [ "$USAGE" == "deployCC" ]; then + println "Usage: " + println " network.sh \033[0;32mdeployCC\033[0m [Flags]" + println + println " Flags:" + println " -c - Name of channel to deploy chaincode to" + println " -ccn - Chaincode name. This flag can be used to deploy one of the asset transfer samples to a channel. Sample options: basic (default),ledger, private, sbe, secured" + println " -ccl - Programming language of chaincode to deploy: go (default), java, javascript, typescript" + println " -ccv - Chaincode version. 1.0 (default), v2, version3.x, etc" + println " -ccs - Chaincode definition sequence. Must be an integer, 1 (default), 2, 3, etc" + println " -ccp - (Optional) File path to the chaincode. When provided, the -ccn flag will be used only for the chaincode name." + println " -ccep - (Optional) Chaincode endorsement policy using signature policy syntax. The default policy requires an endorsement from Org1 and Org2" + println " -cccg - (Optional) File path to private data collections configuration file" + println " -cci - (Optional) Name of chaincode initialization function. When a function is provided, the execution of init will be requested and the function will be invoked." + println + println " -h - Print this message" + println + println " Possible Mode and flag combinations" + println " \033[0;32mdeployCC\033[0m -ccn -ccl -ccv -ccs -ccp -cci -r -d -verbose" + println + println " Examples:" + println " network.sh deployCC -ccn basic -ccl javascript" + println " network.sh deployCC -ccn mychaincode -ccp ./user/mychaincode -ccv 1 -ccl javascript" + else + println "Usage: " + println " network.sh [Flags]" + println " Modes:" + println " \033[0;32mup\033[0m - Bring up Fabric orderer and peer nodes. No channel is created" + println " \033[0;32mup createChannel\033[0m - Bring up fabric network with one channel" + println " \033[0;32mcreateChannel\033[0m - Create and join a channel after the network is created" + println " \033[0;32mdeployCC\033[0m - Deploy a chaincode to a channel (defaults to asset-transfer-basic)" + println " \033[0;32mdown\033[0m - Bring down the network" + println + println " Flags:" + println " Used with \033[0;32mnetwork.sh up\033[0m, \033[0;32mnetwork.sh createChannel\033[0m:" + println " -ca - Use Certificate Authorities to generate network crypto material" + println " -c - Name of channel to create (defaults to \"mychannel\")" + println " -s - Peer state database to deploy: goleveldb (default) or couchdb" + println " -r - CLI times out after certain number of attempts (defaults to 5)" + println " -d - CLI delays for a certain number of seconds (defaults to 3)" + println " -i - Docker image tag of Fabric to deploy (defaults to \"latest\")" + println " -cai - Docker image tag of Fabric CA to deploy (defaults to \"${CA_IMAGETAG}\")" + println " -verbose - Verbose mode" + println + println " Used with \033[0;32mnetwork.sh deployCC\033[0m" + println " -c - Name of channel to deploy chaincode to" + println " -ccn - Chaincode name. This flag can be used to deploy one of the asset transfer samples to a channel. Sample options: basic (default),ledger, private, sbe, secured" + println " -ccl - Programming language of the chaincode to deploy: go (default), java, javascript, typescript" + println " -ccv - Chaincode version. 1.0 (default), v2, version3.x, etc" + println " -ccs - Chaincode definition sequence. Must be an integer, 1 (default), 2, 3, etc" + println " -ccp - (Optional) File path to the chaincode. When provided, the -ccn flag will be used only for the chaincode name." + println " -ccep - (Optional) Chaincode endorsement policy using signature policy syntax. The default policy requires an endorsement from Org1 and Org2" + println " -cccg - (Optional) File path to private data collections configuration file" + println " -cci - (Optional) Name of chaincode initialization function. When a function is provided, the execution of init will be requested and the function will be invoked." + println + println " -h - Print this message" + println + println " Possible Mode and flag combinations" + println " \033[0;32mup\033[0m -ca -r -d -s -i -cai -verbose" + println " \033[0;32mup createChannel\033[0m -ca -c -r -d -s -i -cai -verbose" + println " \033[0;32mcreateChannel\033[0m -c -r -d -verbose" + println " \033[0;32mdeployCC\033[0m -ccn -ccl -ccv -ccs -ccp -cci -r -d -verbose" + 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" + fi +} + # println echos string function println() { echo -e "$1" diff --git a/test-network/scripts/deployCC.sh b/test-network/scripts/deployCC.sh index 1b75ce62..edea87a6 100755 --- a/test-network/scripts/deployCC.sh +++ b/test-network/scripts/deployCC.sh @@ -42,6 +42,9 @@ if [ "$CC_SRC_PATH" = "NA" ]; then if [ "$CC_NAME" = "basic" ]; then println $'\e[0;32m'asset-transfer-basic$'\e[0m' chaincode CC_SRC_PATH="../asset-transfer-basic" + elif [ "$CC_NAME" = "events" ]; then + println $'\e[0;32m'asset-transfer-events$'\e[0m' chaincode + CC_SRC_PATH="../asset-transfer-events" elif [ "$CC_NAME" = "secured" ]; then println $'\e[0;32m'asset-transfer-secured-agreeement$'\e[0m' chaincode CC_SRC_PATH="../asset-transfer-secured-agreement" @@ -55,7 +58,7 @@ if [ "$CC_SRC_PATH" = "NA" ]; then println $'\e[0;32m'asset-transfer-sbe$'\e[0m' chaincode CC_SRC_PATH="../asset-transfer-sbe" else - fatalln "The chaincode name ${CC_NAME} is not supported by this script. Supported chaincode names are: basic, ledger, private, sbe, secured" + fatalln "The chaincode name ${CC_NAME} is not supported by this script. Supported chaincode names are: basic, events, ledger, private, sbe, secured" fi # now see what language it is written in @@ -134,10 +137,6 @@ else CC_COLL_CONFIG="--collections-config $CC_COLL_CONFIG" fi -#if [ "$CC_INIT_FCN" = "NA" ]; then -# INIT_REQUIRED="" -#fi - # import utils . scripts/envVar.sh diff --git a/token-erc-20/README.md b/token-erc-20/README.md new file mode 100644 index 00000000..2655f173 --- /dev/null +++ b/token-erc-20/README.md @@ -0,0 +1,403 @@ +# ERC-20 token scenario + +The ERC-20 token smart contract demonstrates how to create and transfer fungible tokens using an account-based model. In an ERC-20 account-based model, there is an account for each participant that holds a balance of tokens. +A mint transaction creates tokens in an account, while a transfer transaction debits the caller's account and credits another account. + +In this sample it is assumed that only one organization (played by Org1) is in a central banker role and can mint new tokens into their account, while any organization can transfer tokens from their account to a recipient's account. +Accounts could be defined at the organization level or client identity level. In this sample accounts are defined at the client identity level, where every authorized client with an enrollment certificate from their organization implicitly has an account ID that matches their client ID. +The client ID is simply a base64-encoded concatenation of the issuer and subject from the client identity's enrollment certificate. The client ID can therefore be considered the account ID that is used as the payment address of a recipient. + +In this tutorial, you will mint and transfer tokens as follows: + +- A member of Org1 uses the `Mint` function to create new tokens into their account. The `Mint` smart contract function reads the certificate information of the client identity that submitted the transaction using the `GetClientIdentity.GetID()` API and credits the account associated with the client ID with the requested number of tokens. +- The same minter client will then use the `Transfer` function to transfer the requested number of tokens to the recipient's account. It is assumed that the recipient has provided their account ID to the transfer caller out of band. The recipient can then transfer tokens to other registered users in the same fashion. + +## Bring up the test network + +You can run the ERC-20 token transfer scenario using the Fabric test network. Open a command terminal and navigate to the 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 start the test network: +``` +./network.sh up createChannel -ca +``` + +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 ERC-20 token contract to the channel that was just created. Deploy the smart contract to `mychannel` using the following command: + +**For a Go Contract:** +``` +./network.sh deployCC -ccn token_erc20 -ccp ../token-erc-20/chaincode-go/ +``` + +**For a JavaScript Contract:** +``` +./network.sh deployCC -ccn token_erc20 -ccp ../token-erc-20/chaincode-javascript/ -ccl javascript +``` + +The above command deploys the go chaincode with short name `token_erc20`. The smart contract will use the default endorsement policy of majority of channel members. +Since the channel has two members, this implies that we'll need to get peer endorsements from 2 out of the 2 channel members. + +Now you are ready to call the deployed smart contract via peer CLI calls. But let's first create the client identities for our scenario. + +## Register identities + +The smart contract supports accounts owned by individual client identities from organizations that are members of the channel. In our scenario, the minter of the tokens will be a member of Org1, while the recipient 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 Fabric CA client (and subsequent commands). +``` +export PATH=${PWD}/../bin:${PWD}:$PATH +export FABRIC_CFG_PATH=$PWD/../config/ +``` + +The terminal we have been using will represent Org1. We will use the Org1 CA to create the minter identity. 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 minter client identity using the `fabric-ca-client` tool: +``` +fabric-ca-client register --caname ca-org1 --id.name minter --id.secret minterpw --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 minter's enroll name and secret to the enroll command: +``` +fabric-ca-client enroll -u https://minter:minterpw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@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 minter identity MSP folder. +``` +cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp/config.yaml +``` + +Open a new terminal to represent Org2 and navigate to fabric-samples/test-network. We'll use the Org2 CA to create the Org2 recipient identity. Set the Fabric CA client home to the MSP of the Org2 CA admin: +``` +cd fabric-samples/test-network +export PATH=${PWD}/../bin:${PWD}:$PATH +export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/ +``` + +You can register a recipient client identity using the `fabric-ca-client` tool: +``` +fabric-ca-client register --caname ca-org2 --id.name recipient --id.secret recipientpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem +``` + +We can now enroll to generate the recipient's identity certificates and MSP folder: +``` +fabric-ca-client enroll -u https://recipient:recipientpw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@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 recipient identity MSP folder. +``` +cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp/config.yaml +``` + +## Mint some tokens + +Now that we have created the identity of the minter, we can invoke the smart contract to mint some tokens. +Shift back to the Org1 terminal, we'll set the following environment variables to operate the `peer` CLI as the minter 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/minter@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 +export TARGET_TLS_OPTIONS="-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 --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" +``` + +The last environment variable above will be utilized within the CLI invoke commands to set the target peers for endorsement, and the target ordering service endpoint and TLS options. + +We can then invoke the smart contract to mint 5000 tokens: +``` +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc20 -c '{"function":"Mint","Args":["5000"]}' +``` + +The mint function validated that the client is a member of the minter organization, and then credited the minter client's account with 5000 tokens. We can check the minter client's account balance by calling the `ClientAccountBalance` function. +``` +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountBalance","Args":[]}' +``` + +The function queries the balance of the account associated with the minter client ID and returns: +``` +5000 +``` + +## Transfer tokens + +The minter intends to transfer 100 tokens to the Org2 recipient, but first the Org2 recipient needs to provide their own account ID as the payment address. +A client can derive their account ID from their own public certificate, but to be sure the account ID is accurate, the contract has a `ClientAccountID` utility function that simply looks at the callers certificate and returns the calling client's ID, which will be used as the account ID. +Let's prepare the Org2 terminal by setting the environment variables for the Org2 recipient user. +``` +export FABRIC_CFG_PATH=$PWD/../config/ +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_LOCALMSPID="Org2MSP" +export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@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 +``` + +Using the Org2 terminal, the Org2 recipient user can retrieve their own account ID: +``` +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountID","Args":[]}' +``` + +**For a Go Contract:** + +The function returns of recipient's account ID: +``` +eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw== +``` + +Let's base64 decode the account ID to make sure it represents the Org2 recipient user: +``` +echo eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw== | base64 --decode +``` + +The result shows that the subject and issuer is indeed the recipient user from Org2: +``` +x509::CN=recipient,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK +``` + +**For a JavaScript Contract:** + +The function returns of recipient's client ID. +The result shows that the subject and issuer is indeed the recipient user from Org2: +``` +x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com +``` + +After the Org2 recipient provides their account ID to the minter, the minter can initiate a transfer from their account to the recipient's account. +Back in the Org1 terminal, request the transfer of 100 tokens to the recipient account: + +**For a Go Contract:** +``` +export RECIPIENT="eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw==" +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc20 -c '{"function":"Transfer","Args":[ "'"$RECIPIENT"'","100"]}' +``` + +**For a JavaScript Contract:** +``` +export RECIPIENT="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com" +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc20 -c '{"function":"Transfer","Args":[ "'"$RECIPIENT"'","100"]}' +``` + +The `Transfer` function validates that the account associated with the calling client ID has sufficient funds for the transfer. +It will then debit the caller's account and credit the recipient's account. Note that the sample contract will automatically create an account with zero balance for the recipient, if one does not yet exist. + +While still in the Org1 terminal, let's request the minter's account balance again: +``` +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountBalance","Args":[]}' +``` + +The function queries the balance of the account associated with the minter client ID and returns: +``` +4900 +``` + +And then using the Org2 terminal, let's request the recipient's balance: +``` +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountBalance","Args":[]}' +``` + +The function queries the balance of the account associated with the recipient client ID and returns: +``` +100 +``` + +Congratulations, you've transferred 100 tokens! The Org2 recipient can now transfer tokens to other registered users in the same manner. + +## 3rd party transfers (TransferFrom) + +This sample has another ERC-20 transfer method called `TransferFrom`, which allows an approved 3rd party spender to transfer fungible tokens on behalf of the account owner. This scenario demonstrates how to approve the spender and transfer fungible tokens. + +In this scenario, you will approve the spender and transfer tokens as follows: + +- A minter has already created tokens according to the scenario above. +- The same minter client uses the `Approve` function to set the allowance of tokens a spender client can transfer on behalf of the minter. It is assumed that the spender has provided their client ID to the `Approve` caller out of band. +- The spender client will then use the `TransferFrom` function to transfer the requested number of tokens to the recipient's account on behalf of the minter. It is assumed that the recipient has provided their client ID to the `TransferFrom` caller out of band. + +## Register identity for 3rd party spender + +You have already brought up the network and deployed the smart contract to the channel. We will use the same network and smart contract. + +We will use the Org1 CA to create the spender identity. +Back in the Org1 terminal, you can register a new spender client identity using the `fabric-ca-client` tool: +``` +fabric-ca-client register --caname ca-org1 --id.name spender --id.secret spenderpw --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 spender's enroll name and secret to the enroll command: +``` +fabric-ca-client enroll -u https://spender:spenderpw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/spender@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 spender identity MSP folder. +``` +cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/spender@org1.example.com/msp/config.yaml +``` + +## Approve a spender + +The minter intends to approve 500 tokens to be transferred by the spender, but first the spender needs to provide their own client ID as the payment address. + +Open a 3rd terminal to represent the spender in Org1 and navigate to fabric-samples/test-network. Set the the environment variables for the Org1 spender user. + +``` +export PATH=${PWD}/../bin:${PWD}:$PATH +export FABRIC_CFG_PATH=$PWD/../config/ +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_LOCALMSPID="Org1MSP" +export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org1.example.com/users/spender@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 +export TARGET_TLS_OPTIONS="-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 --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" +``` + +Now the Org1 spender can retrieve their own client ID: +``` +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountID","Args":[]}' +``` + +**For a Go Contract:** + +The function returns of spender's account ID: +``` +eDUwOTo6Q049c3BlbmRlcixPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT +``` + +**For a JavaScript Contract:** + +The function returns of spender's client ID. +The result shows that the subject and issuer is indeed the recipient user from Org2: +``` +x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=spender::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com +``` + +After the Org1 spender provides their client ID to the minter, the minter can approve a spender. +Back in the Org1 minter terminal, request the approval of 500 tokens to be withdrawn by the spender. + +**For a Go Contract:** + +``` +export SPENDER="eDUwOTo6Q049c3BlbmRlcixPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT" +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc20 -c '{"function":"Approve","Args":[ "'"$SPENDER"'","500"]}' +``` + +**For a JavaScript Contract:** + +``` +export SPENDER="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=spender::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com" +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc20 -c '{"function":"Approve","Args":["'"$SPENDER"'", "500"]}' +``` + +The approve function specified that the spender client can transfer 500 tokens on behalf of the minter. We can check the spender client's allowance from the minter by calling the `allowance` function. + +Let's request the spender's allowance from the Org1 minter terminal. + +**For a Go Contract:** + +``` +export MINTER="eDUwOTo6Q049bWludGVyLE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzEuZXhhbXBsZS5jb20sTz1vcmcxLmV4YW1wbGUuY29tLEw9RHVyaGFtLFNUPU5vcnRoIENhcm9saW5hLEM9VVM=" +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"Allowance","Args":["'"$MINTER"'", "'"$SPENDER"'"]}' +``` + +**For a JavaScript Contract:** + +``` +export MINTER="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=minter::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com" +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"Allowance","Args":["'"$MINTER"'", "'"$SPENDER"'"]}' +``` + +The function queries the allowance associated with the spender client ID and returns: +``` +500 +``` + +## TransferFrom tokens + +The spender intends to transfer 100 tokens to the Org2 recipient on behalf of the minter. The spender has already got the minter client Id and the recipient client ID. + +Back in the 3rd terminal, request the transfer of 100 tokens to the recipient account. + +**For a Go Contract:** + +``` +export MINTER="eDUwOTo6Q049bWludGVyLE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzEuZXhhbXBsZS5jb20sTz1vcmcxLmV4YW1wbGUuY29tLEw9RHVyaGFtLFNUPU5vcnRoIENhcm9saW5hLEM9VVM=" +export RECIPIENT="eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw==" +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc20 -c '{"function":"TransferFrom","Args":[ "'"$MINTER"'", "'"$RECIPIENT"'", "100"]}' +``` + +**For a JavaScript Contract:** + +``` +export MINTER="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=minter::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com" +export RECIPIENT="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=recipient::/C=UK/ST=Hampshire/L=Hursley/O=org2.example.com/CN=ca.org2.example.com" +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_erc20 -c '{"function":"TransferFrom","Args":[ "'"$MINTER"'", "'"$RECIPIENT"'", "100"]}' +``` + +The `TransferFrom` function has three args: sender, recipient, amount. The function validates that the account associated with the sender has sufficient funds for the transfer. The function also validates if the allowance associated with the calling client ID exceeds funds to be transferred. +It will then debit the sender's account and credit the recipient's account. It will also decrease the spender's allowance approved by the minter. Note that the sample contract will automatically create an account with zero balance for the recipient, if one does not yet exist. + +While still in the 3rd terminal for the spender, let's request the minter's account balance again: +``` +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"BalanceOf","Args":["'"$MINTER"'"]}' +``` + +The function queries the balance of the account associated with the minter client ID and returns: +``` +4800 +``` + +While still in the 3rd terminal for the spender, let's request the spender's allowance from the minter again. + +**For a Go Contract:** + +``` +export SPENDER="eDUwOTo6Q049c3BlbmRlcixPVT1jbGllbnQsTz1IeXBlcmxlZGdlcixTVD1Ob3J0aCBDYXJvbGluYSxDPVVTOjpDTj1jYS5vcmcxLmV4YW1wbGUuY29tLE89b3JnMS5leGFtcGxlLmNvbSxMPUR1cmhhbSxTVD1Ob3J0aCBDYXJvbGluYSxDPVVT" +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"Allowance","Args":["'"$MINTER"'", "'"$SPENDER"'"]}' +``` + +**For a JavaScript Contract:** + +``` +export SPENDER="x509::/C=US/ST=North Carolina/O=Hyperledger/OU=client/CN=spender::/C=US/ST=North Carolina/L=Durham/O=org1.example.com/CN=ca.org1.example.com" +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"Allowance","Args":["'"$MINTER"'", "'"$SPENDER"'"]}' +``` + +The function queries the allowance associated with the spender client ID and returns: +``` +400 +``` + +And then using the Org2 terminal, let's request the recipient's balance: +``` +peer chaincode query -C mychannel -n token_erc20 -c '{"function":"ClientAccountBalance","Args":[]}' +``` + +The function queries the balance of the account associated with the recipient client ID and returns: +``` +200 +``` + +Congratulations, you've transferred 100 tokens! The Org2 recipient can now transfer tokens to other registered users in the same manner. + +## 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 +``` + +## Contract extension ideas + +You can extend the basic ERC-20 account-based token sample to meet other requirements. For example: + +* Rather than using the default 'majority' endorsement policy, you could set the endorsement policy to a subset of organizations that represent trust anchors for the contract execution. +* You could also require that accounts get setup before use, and apply state-based endorsement for each account key that has been created. For example on an Org1 account, set state-based endorsement policy to be Org1 and the central banker (or some other trust anchor). And on an Org2 account, set state-based endorsement policy to be Org2 and the central banker (or some other trust anchor). Then to transfer tokens from an Org1 account to an Org2 account, you would require endorsements from Org1, Org2, and the central banker (or some other trust anchor). +* You could utilize anonymous addresses for accounts based on private-public key pairs, instead of accounts keyed by the client ID. In order to spend the tokens, the client would have to sign the transfer input as proof that they own the address private key, which the contract would then validate, similar to the Ethereum model in the permissionless blockchain space. However, in a permissioned blockchain such as Fabric, only registered clients are authorized to participate. Furthermore, if you don't want to leak the registered client identity associated with each account, the clients could be registered using an Identity Mixer MSP, so that the client itself is also anonymous in each of the token transactions. diff --git a/token-erc-20/chaincode-go/chaincode/token_contract.go b/token-erc-20/chaincode-go/chaincode/token_contract.go new file mode 100644 index 00000000..b7ce689a --- /dev/null +++ b/token-erc-20/chaincode-go/chaincode/token_contract.go @@ -0,0 +1,363 @@ +package chaincode + +import ( + "encoding/json" + "fmt" + "log" + "strconv" + + "github.com/hyperledger/fabric-contract-api-go/contractapi" +) + +// Define key names for options +const totalSupplyKey = "totalSupply" + +// Define objectType names for prefix +const allowancePrefix = "allowance" + +// SmartContract provides functions for transferring tokens between accounts +type SmartContract struct { + contractapi.Contract +} + +// Mint creates new tokens and adds them to minter's account balance +func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount int) error { + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens + clientMSPID, err := ctx.GetClientIdentity().GetMSPID() + if err != nil { + return fmt.Errorf("failed to get MSPID: %v", err) + } + if clientMSPID != "Org1MSP" { + return fmt.Errorf("client is not authorized to mint new tokens") + } + + // Get ID of submitting client identity + minter, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + if amount <= 0 { + return fmt.Errorf("mint amount must be a positive integer") + } + + currentBalanceBytes, err := ctx.GetStub().GetState(minter) + if err != nil { + return fmt.Errorf("failed to read minter account %s from world state: %v", minter, err) + } + + var currentBalance int + + // If minter current balance doesn't yet exist, we'll create it with a current balance of 0 + if currentBalanceBytes == nil { + currentBalance = 0 + } else { + currentBalance, _ = strconv.Atoi(string(currentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer. + } + + updatedBalance := currentBalance + amount + + err = ctx.GetStub().PutState(minter, []byte(strconv.Itoa(updatedBalance))) + if err != nil { + return err + } + + log.Printf("minter account %s balance updated from %d to %d", minter, currentBalance, updatedBalance) + + // Update the totalSupply + totalSupplyBytes, err := ctx.GetStub().GetState(totalSupplyKey) + if err != nil { + return fmt.Errorf("failed to retrieve total token supply: %v", err) + } + + var totalSupply int + + // If no tokens have been minted, initialize the totalSupply + if totalSupplyBytes == nil { + totalSupply = 0 + } else { + totalSupply, _ = strconv.Atoi(string(totalSupplyBytes)) // Error handling not needed since Itoa() was used when setting the totalSupply, guaranteeing it was an integer. + } + + // Add the mint amount to the total supply and update the state + totalSupply += amount + ctx.GetStub().PutState(totalSupplyKey, []byte(strconv.Itoa(totalSupply))) + + return nil +} + +// Transfer transfers tokens from client account to recipient account. +// recipient account must be a valid clientID as returned by the ClientID() function. +func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, recipient string, amount int) error { + + // Get ID of submitting client identity + clientID, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + err = transferHelper(ctx, clientID, recipient, amount) + if err != nil { + return fmt.Errorf("failed to transfer: %v", err) + } + + // Emit the Transfer event + transferEvent := map[string]string{"from": clientID, "to": recipient, "value": strconv.Itoa(amount)} + transferEventJSON, err := json.Marshal(transferEvent) + if err != nil { + return fmt.Errorf("failed to obtain JSON encoding: %v", err) + } + err = ctx.GetStub().SetEvent("Transfer", transferEventJSON) + if err != nil { + return fmt.Errorf("event failed to register: %v", err) + } + + return nil +} + +// BalanceOf returns the balance of the given account +func (s *SmartContract) BalanceOf(ctx contractapi.TransactionContextInterface, account string) (int, error) { + balanceBytes, err := ctx.GetStub().GetState(account) + if err != nil { + return 0, fmt.Errorf("failed to read from world state: %v", err) + } + if balanceBytes == nil { + return 0, fmt.Errorf("the account %s does not exist", account) + } + + balance, _ := strconv.Atoi(string(balanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer. + + return balance, nil +} + +// ClientAccountBalance returns the balance of the requesting client's account +func (s *SmartContract) ClientAccountBalance(ctx contractapi.TransactionContextInterface) (int, error) { + + // Get ID of submitting client identity + clientID, err := ctx.GetClientIdentity().GetID() + if err != nil { + return 0, fmt.Errorf("failed to get client id: %v", err) + } + + balanceBytes, err := ctx.GetStub().GetState(clientID) + if err != nil { + return 0, fmt.Errorf("failed to read from world state: %v", err) + } + if balanceBytes == nil { + return 0, fmt.Errorf("the account %s does not exist", clientID) + } + + balance, _ := strconv.Atoi(string(balanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer. + + return balance, nil +} + +// ClientAccountID returns the id of the requesting client's account. +// In this implementation, the client account ID is the clientId itself. +// Users can use this function to get their own account id, which they can then give to others as the payment address +func (s *SmartContract) ClientAccountID(ctx contractapi.TransactionContextInterface) (string, error) { + + // Get ID of submitting client identity + clientAccountID, err := ctx.GetClientIdentity().GetID() + if err != nil { + return "", fmt.Errorf("failed to get client id: %v", err) + } + + return clientAccountID, nil +} + +// TotalSupply returns the total token supply +func (s *SmartContract) TotalSupply(ctx contractapi.TransactionContextInterface) (int, error) { + + // Retrieve total supply of tokens from state of smart contract + totalSupplyBytes, err := ctx.GetStub().GetState(totalSupplyKey) + if err != nil { + return 0, fmt.Errorf("failed to retrieve total token supply: %v", err) + } + + var totalSupply int + + // If no tokens have been minted, return 0 + if totalSupplyBytes == nil { + totalSupply = 0 + } else { + totalSupply, _ = strconv.Atoi(string(totalSupplyBytes)) // Error handling not needed since Itoa() was used when setting the totalSupply, guaranteeing it was an integer. + } + + log.Printf("TotalSupply: %d tokens", totalSupply) + + return totalSupply, nil +} + +// Approve allows the spender to withdraw from the calling client's token account. +// The spender can withdraw multiple times if necessary, up to the value amount. +func (s *SmartContract) Approve(ctx contractapi.TransactionContextInterface, spender string, value int) error { + + // Get ID of submitting client identity + owner, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + // Create allowanceKey + allowanceKey, err := ctx.GetStub().CreateCompositeKey(allowancePrefix, []string{owner, spender}) + if err != nil { + return fmt.Errorf("failed to create the composite key for prefix %s: %v", allowancePrefix, err) + } + + // Update the state of the smart contract by adding the allowanceKey and value + err = ctx.GetStub().PutState(allowanceKey, []byte(strconv.Itoa(value))) + if err != nil { + return fmt.Errorf("failed to update state of smart contract for key %s: %v", allowanceKey, err) + } + + // Emit the Approval event + approvalEvent := map[string]string{"owner": owner, "spender": spender, "value": strconv.Itoa(value)} + approvalEventJSON, err := json.Marshal(approvalEvent) + if err != nil { + return fmt.Errorf("failed to obtain JSON encoding: %v", err) + } + err = ctx.GetStub().SetEvent("Approval", approvalEventJSON) + if err != nil { + return fmt.Errorf("event failed to register: %v", err) + } + + log.Printf("client %s approved a withdrawal allowance of %d for spender %s", owner, value, spender) + + return nil +} + +// Allowance returns the amount still available for the spender to withdraw from the owner. +func (s *SmartContract) Allowance(ctx contractapi.TransactionContextInterface, owner string, spender string) (int, error) { + + // Create allowanceKey + allowanceKey, err := ctx.GetStub().CreateCompositeKey(allowancePrefix, []string{owner, spender}) + if err != nil { + return 0, fmt.Errorf("failed to create the composite key for prefix %s: %v", allowancePrefix, err) + } + + // Read the allowance amount from the world state + allowanceBytes, err := ctx.GetStub().GetState(allowanceKey) + if err != nil { + return 0, fmt.Errorf("failed to read allowance for %s from world state: %v", allowanceKey, err) + } + + var allowance int + + // If no current allowance, set allowance to 0 + if allowanceBytes == nil { + allowance = 0 + } else { + allowance, err = strconv.Atoi(string(allowanceBytes)) // Error handling not needed since Itoa() was used when setting the totalSupply, guaranteeing it was an integer. + } + + log.Printf("The allowance left for spender %s to withdraw from owner %s: %d", spender, owner, allowance) + + return allowance, nil +} + +// TransferFrom transfers the value amount from the "from" address to the "to" address. +// This function triggers a Transfer event. +func (s *SmartContract) TransferFrom(ctx contractapi.TransactionContextInterface, from string, to string, value int) error { + + // Get ID of submitting client identity + spender, err := ctx.GetClientIdentity().GetID() + if err != nil { + return fmt.Errorf("failed to get client id: %v", err) + } + + // Create allowanceKey + allowanceKey, err := ctx.GetStub().CreateCompositeKey(allowancePrefix, []string{from, spender}) + if err != nil { + return fmt.Errorf("failed to create the composite key for prefix %s: %v", allowancePrefix, err) + } + + // Retrieve the allowance of the spender + currentAllowanceBytes, err := ctx.GetStub().GetState(allowanceKey) + if err != nil { + return fmt.Errorf("failed to retrieve the allowance for %s from world state: %v", allowanceKey, err) + } + + var currentAllowance int + currentAllowance, _ = strconv.Atoi(string(currentAllowanceBytes)) // Error handling not needed since Itoa() was used when setting the totalSupply, guaranteeing it was an integer. + + // Check if transferred value is less than allowance + if currentAllowance < value { + return fmt.Errorf("spender does not have enough allowance for transfer") + } + + // Initiate the transfer + err = transferHelper(ctx, from, to, value) + if err != nil { + return fmt.Errorf("failed to transfer: %v", err) + } + + // Decrease the allowance + updatedAllowance := currentAllowance - value + err = ctx.GetStub().PutState(allowanceKey, []byte(strconv.Itoa(updatedAllowance))) + if err != nil { + return err + } + + log.Printf("spender %s allowance updated from %d to %d", spender, currentAllowance, updatedAllowance) + + return nil +} + +// Helper Functions + +// transferHelper is a helper function that transfers tokens from the "from" address to the "to" address. +// Dependant functions include Transfer and TransferFrom. +func transferHelper(ctx contractapi.TransactionContextInterface, from string, to string, value int) error { + + if value < 0 { // transfer of 0 is allowed in ERC-20, so just validate against negative amounts + return fmt.Errorf("transfer amount cannot be negative") + } + + fromCurrentBalanceBytes, err := ctx.GetStub().GetState(from) + if err != nil { + return fmt.Errorf("failed to read client account %s from world state: %v", from, err) + } + + if fromCurrentBalanceBytes == nil { + return fmt.Errorf("client account %s has no balance", from) + } + + fromCurrentBalance, _ := strconv.Atoi(string(fromCurrentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer. + + if fromCurrentBalance < value { + return fmt.Errorf("client account %s has insufficient funds", from) + } + + toCurrentBalanceBytes, err := ctx.GetStub().GetState(to) + if err != nil { + return fmt.Errorf("failed to read recipient account %s from world state: %v", to, err) + } + + var toCurrentBalance int + // If recipient current balance doesn't yet exist, we'll create it with a current balance of 0 + if toCurrentBalanceBytes == nil { + toCurrentBalance = 0 + } else { + toCurrentBalance, _ = strconv.Atoi(string(toCurrentBalanceBytes)) // Error handling not needed since Itoa() was used when setting the account balance, guaranteeing it was an integer. + } + + fromUpdatedBalance := fromCurrentBalance - value + toUpdatedBalance := toCurrentBalance + value + + err = ctx.GetStub().PutState(from, []byte(strconv.Itoa(fromUpdatedBalance))) + if err != nil { + return err + } + + err = ctx.GetStub().PutState(to, []byte(strconv.Itoa(toUpdatedBalance))) + if err != nil { + return err + } + + log.Printf("client %s balance updated from %d to %d", from, fromCurrentBalance, fromUpdatedBalance) + log.Printf("recipient %s balance updated from %d to %d", to, toCurrentBalance, toUpdatedBalance) + + return nil +} diff --git a/token-erc-20/chaincode-go/go.mod b/token-erc-20/chaincode-go/go.mod new file mode 100644 index 00000000..6bee34ad --- /dev/null +++ b/token-erc-20/chaincode-go/go.mod @@ -0,0 +1,5 @@ +module github.com/hyperledger/fabric-samples/token-erc-20/chaincode-go + +go 1.14 + +require github.com/hyperledger/fabric-contract-api-go v1.1.0 diff --git a/token-erc-20/chaincode-go/go.sum b/token-erc-20/chaincode-go/go.sum new file mode 100644 index 00000000..5a92905b --- /dev/null +++ b/token-erc-20/chaincode-go/go.sum @@ -0,0 +1,145 @@ +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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +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 h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/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 h1:1i4lnpV8BDgKOLi1hgElfBqdHXjXieSuj8629mwBZ8o= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/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 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/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 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.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= diff --git a/token-erc-20/chaincode-go/token_erc_20.go b/token-erc-20/chaincode-go/token_erc_20.go new file mode 100644 index 00000000..2bcc51d4 --- /dev/null +++ b/token-erc-20/chaincode-go/token_erc_20.go @@ -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/token-erc-20/chaincode-go/chaincode" +) + +func main() { + tokenChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{}) + if err != nil { + log.Panicf("Error creating token-erc-20 chaincode: %v", err) + } + + if err := tokenChaincode.Start(); err != nil { + log.Panicf("Error starting token-erc-20 chaincode: %v", err) + } +} diff --git a/token-erc-20/chaincode-javascript/.editorconfig b/token-erc-20/chaincode-javascript/.editorconfig new file mode 100755 index 00000000..75a13be2 --- /dev/null +++ b/token-erc-20/chaincode-javascript/.editorconfig @@ -0,0 +1,16 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false diff --git a/token-erc-20/chaincode-javascript/.eslintignore b/token-erc-20/chaincode-javascript/.eslintignore new file mode 100644 index 00000000..15958470 --- /dev/null +++ b/token-erc-20/chaincode-javascript/.eslintignore @@ -0,0 +1,5 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +coverage diff --git a/token-erc-20/chaincode-javascript/.eslintrc.js b/token-erc-20/chaincode-javascript/.eslintrc.js new file mode 100644 index 00000000..8d99762d --- /dev/null +++ b/token-erc-20/chaincode-javascript/.eslintrc.js @@ -0,0 +1,39 @@ +/* + * SPDX-License-Identifier: Apache-2.0 + */ + +module.exports = { + env: { + node: true, + es6: true, + mocha: true + }, + parserOptions: { + ecmaVersion: 8, + sourceType: 'script' + }, + extends: "eslint:recommended", + rules: { + indent: ['error', 4], + 'linebreak-style': ['error', 'unix'], + quotes: ['error', 'single'], + semi: ['error', 'always'], + 'no-unused-vars': ['error', { args: 'none' }], + 'no-console': 'off', + curly: 'error', + eqeqeq: 'error', + 'no-throw-literal': 'error', + strict: 'error', + 'no-var': 'error', + 'dot-notation': 'error', + 'no-tabs': 'error', + 'no-trailing-spaces': 'error', + 'no-use-before-define': 'error', + 'no-useless-call': 'error', + 'no-with': 'error', + 'operator-linebreak': 'error', + yoda: 'error', + 'quote-props': ['error', 'as-needed'], + 'no-constant-condition': ["error", { "checkLoops": false }] + } +}; diff --git a/token-erc-20/chaincode-javascript/.gitignore b/token-erc-20/chaincode-javascript/.gitignore new file mode 100644 index 00000000..c84ff1db --- /dev/null +++ b/token-erc-20/chaincode-javascript/.gitignore @@ -0,0 +1,78 @@ +# +# SPDX-License-Identifier: Apache-2.0 +# + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ +package-lock.json + +# TypeScript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless diff --git a/token-erc-20/chaincode-javascript/index.js b/token-erc-20/chaincode-javascript/index.js new file mode 100644 index 00000000..1841d315 --- /dev/null +++ b/token-erc-20/chaincode-javascript/index.js @@ -0,0 +1,10 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +const tokenERC20Contract = require('./lib/tokenERC20.js'); + +module.exports.TokenERC20Contract = tokenERC20Contract; +module.exports.contracts = [tokenERC20Contract]; \ No newline at end of file diff --git a/token-erc-20/chaincode-javascript/lib/tokenERC20.js b/token-erc-20/chaincode-javascript/lib/tokenERC20.js new file mode 100644 index 00000000..7ca67749 --- /dev/null +++ b/token-erc-20/chaincode-javascript/lib/tokenERC20.js @@ -0,0 +1,409 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +const { Contract } = require('fabric-contract-api'); + +// Define objectType names for prefix +const balancePrefix = 'balance'; +const allowancePrefix = 'allowance'; + +// Define key names for options +const nameKey = 'name'; +const symbolKey = 'symbol'; +const decimalsKey = 'decimals'; +const totalSupplyKey = 'totalSupply'; + +class TokenERC20Contract extends Contract { + + /** + * Return the name of the token - e.g. "MyToken". + * The original function name is `name` in ERC20 specification. + * However, 'name' conflicts with a parameter `name` in `Contract` class. + * As a work around, we use `TokenName` as an alternative function name. + * + * @param {Context} ctx the transaction context + * @returns {String} Returns the name of the token + */ + async TokenName(ctx) { + const nameBytes = await ctx.stub.getState(nameKey); + return nameBytes.toString(); + } + + /** + * Return the symbol of the token. E.g. “HIX”. + * + * @param {Context} ctx the transaction context + * @returns {String} Returns the symbol of the token + */ + async Symbol(ctx) { + const symbolBytes = await ctx.stub.getState(symbolKey); + return symbolBytes.toString(); + } + + /** + * Return the number of decimals the token uses + * e.g. 8, means to divide the token amount by 100000000 to get its user representation. + * + * @param {Context} ctx the transaction context + * @returns {Number} Returns the number of decimals + */ + async Decimals(ctx) { + const decimalsBytes = await ctx.stub.getState(decimalsKey); + const decimals = parseInt(decimalsBytes.toString()); + return decimals; + } + + /** + * Return the total token supply. + * + * @param {Context} ctx the transaction context + * @returns {Number} Returns the total token supply + */ + async TotalSupply(ctx) { + const totalSupplyBytes = await ctx.stub.getState(totalSupplyKey); + const totalSupply = parseInt(totalSupplyBytes.toString()); + return totalSupply; + } + + /** + * BalanceOf returns the balance of the given account. + * + * @param {Context} ctx the transaction context + * @param {String} owner The owner from which the balance will be retrieved + * @returns {Number} Returns the account balance + */ + async BalanceOf(ctx, owner) { + const balanceKey = ctx.stub.createCompositeKey(balancePrefix, [owner]); + + const balanceBytes = await ctx.stub.getState(balanceKey); + if (!balanceBytes || balanceBytes.length === 0) { + throw new Error(`the account ${owner} does not exist`); + } + const balance = parseInt(balanceBytes.toString()); + + return balance; + } + + /** + * Transfer transfers tokens from client account to recipient account. + * recipient account must be a valid clientID as returned by the ClientAccountID() function. + * + * @param {Context} ctx the transaction context + * @param {String} to The recipient + * @param {Integer} value The amount of token to be transferred + * @returns {Boolean} Return whether the transfer was successful or not + */ + async Transfer(ctx, to, value) { + const from = ctx.clientIdentity.getID(); + + const transferResp = await this._transfer(ctx, from, to, value); + if (!transferResp) { + throw new Error('Failed to transfer'); + } + + // Emit the Transfer event + const transferEvent = { from, to, value: parseInt(value) }; + ctx.stub.setEvent('Transfer', Buffer.from(JSON.stringify(transferEvent))); + + return true; + } + + /** + * Transfer `value` amount of tokens from `from` to `to`. + * + * @param {Context} ctx the transaction context + * @param {String} from The sender + * @param {String} to The recipient + * @param {Integer} value The amount of token to be transferred + * @returns {Boolean} Return whether the transfer was successful or not + */ + async TransferFrom(ctx, from, to, value) { + const spender = ctx.clientIdentity.getID(); + + // Retrieve the allowance of the spender + const allowanceKey = ctx.stub.createCompositeKey(allowancePrefix, [from, spender]); + const currentAllowanceBytes = await ctx.stub.getState(allowanceKey); + + if (!currentAllowanceBytes || currentAllowanceBytes.length === 0) { + throw new Error(`spender ${spender} has no allowance from ${from}`); + } + + const currentAllowance = parseInt(currentAllowanceBytes.toString()); + + // Convert value from string to int + const valueInt = parseInt(value); + + // Check if the transferred value is less than the allowance + if (currentAllowance < valueInt) { + throw new Error('The spender does not have enough allowance to spend.'); + } + + const transferResp = await this._transfer(ctx, from, to, value); + if (!transferResp) { + throw new Error('Failed to transfer'); + } + + // Decrease the allowance + const updatedAllowance = currentAllowance - valueInt; + await ctx.stub.putState(allowanceKey, Buffer.from(updatedAllowance.toString())); + console.log(`spender ${spender} allowance updated from ${currentAllowance} to ${updatedAllowance}`); + + // Emit the Transfer event + const transferEvent = { from, to, value: valueInt }; + ctx.stub.setEvent('Transfer', Buffer.from(JSON.stringify(transferEvent))); + + console.log('transferFrom ended successfully'); + return true; + } + + async _transfer(ctx, from, to, value) { + + // Convert value from string to int + const valueInt = parseInt(value); + + if (valueInt < 0) { // transfer of 0 is allowed in ERC20, so just validate against negative amounts + throw new Error('transfer amount cannot be negative'); + } + + // Retrieve the current balance of the sender + const fromBalanceKey = ctx.stub.createCompositeKey(balancePrefix, [from]); + const fromCurrentBalanceBytes = await ctx.stub.getState(fromBalanceKey); + + if (!fromCurrentBalanceBytes || fromCurrentBalanceBytes.length === 0) { + throw new Error(`client account ${from} has no balance`); + } + + const fromCurrentBalance = parseInt(fromCurrentBalanceBytes.toString()); + + // Check if the sender has enough tokens to spend. + if (fromCurrentBalance < valueInt) { + throw new Error(`client account ${from} has insufficient funds.`); + } + + // Retrieve the current balance of the recepient + const toBalanceKey = ctx.stub.createCompositeKey(balancePrefix, [to]); + const toCurrentBalanceBytes = await ctx.stub.getState(toBalanceKey); + + let toCurrentBalance; + // If recipient current balance doesn't yet exist, we'll create it with a current balance of 0 + if (!toCurrentBalanceBytes || toCurrentBalanceBytes.length === 0) { + toCurrentBalance = 0; + } else { + toCurrentBalance = parseInt(toCurrentBalanceBytes.toString()); + } + + // Update the balance + const fromUpdatedBalance = fromCurrentBalance - valueInt; + const toUpdatedBalance = toCurrentBalance + valueInt; + + await ctx.stub.putState(fromBalanceKey, Buffer.from(fromUpdatedBalance.toString())); + await ctx.stub.putState(toBalanceKey, Buffer.from(toUpdatedBalance.toString())); + + console.log(`client ${from} balance updated from ${fromCurrentBalance} to ${fromUpdatedBalance}`); + console.log(`recipient ${to} balance updated from ${toCurrentBalance} to ${toUpdatedBalance}`); + + return true; + } + + /** + * Allows `spender` to spend `value` amount of tokens from the owner. + * + * @param {Context} ctx the transaction context + * @param {String} spender The spender + * @param {Integer} value The amount of tokens to be approved for transfer + * @returns {Boolean} Return whether the approval was successful or not + */ + async Approve(ctx, spender, value) { + const owner = ctx.clientIdentity.getID(); + + const allowanceKey = ctx.stub.createCompositeKey(allowancePrefix, [owner, spender]); + + let valueInt = parseInt(value); + await ctx.stub.putState(allowanceKey, Buffer.from(valueInt.toString())); + + // Emit the Approval event + const approvalEvent = { owner, spender, value: valueInt }; + ctx.stub.setEvent('Approval', Buffer.from(JSON.stringify(approvalEvent))); + + console.log('approve ended successfully'); + return true; + } + + /** + * Returns the amount of tokens which `spender` is allowed to withdraw from `owner`. + * + * @param {Context} ctx the transaction context + * @param {String} owner The owner of tokens + * @param {String} spender The spender who are able to transfer the tokens + * @returns {Number} Return the amount of remaining tokens allowed to spent + */ + async Allowance(ctx, owner, spender) { + const allowanceKey = ctx.stub.createCompositeKey(allowancePrefix, [owner, spender]); + + const allowanceBytes = await ctx.stub.getState(allowanceKey); + if (!allowanceBytes || allowanceBytes.length === 0) { + throw new Error(`spender ${spender} has no allowance from ${owner}`); + } + + const allowance = parseInt(allowanceBytes.toString()); + return allowance; + } + + // ================== Extended Functions ========================== + + /** + * Set optional infomation for a token. + * + * @param {Context} ctx the transaction context + * @param {String} name The name of the token + * @param {String} symbol The symbol of the token + * @param {String} decimals The decimals of the token + * @param {String} totalSupply The totalSupply of the token + */ + async SetOption(ctx, name, symbol, decimals) { + await ctx.stub.putState(nameKey, Buffer.from(name)); + await ctx.stub.putState(symbolKey, Buffer.from(symbol)); + await ctx.stub.putState(decimalsKey, Buffer.from(decimals)); + + console.log(`name: ${name}, symbol: ${symbol}, decimals: ${decimals}`); + return true; + } + + /** + * Mint creates new tokens and adds them to minter's account balance + * + * @param {Context} ctx the transaction context + * @param {Integer} amount amount of tokens to be minted + * @returns {Object} The balance + */ + async Mint(ctx, amount) { + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens + const clientMSPID = ctx.clientIdentity.getMSPID(); + if (clientMSPID !== 'Org1MSP') { + throw new Error('client is not authorized to mint new tokens'); + } + + // Get ID of submitting client identity + const minter = ctx.clientIdentity.getID(); + + const amountInt = parseInt(amount); + if (amountInt <= 0) { + throw new Error('mint amount must be a positive integer'); + } + + const balanceKey = ctx.stub.createCompositeKey(balancePrefix, [minter]); + + const currentBalanceBytes = await ctx.stub.getState(balanceKey); + // If minter current balance doesn't yet exist, we'll create it with a current balance of 0 + let currentBalance; + if (!currentBalanceBytes || currentBalanceBytes.length === 0) { + currentBalance = 0; + } else { + currentBalance = parseInt(currentBalanceBytes.toString()); + } + const updatedBalance = currentBalance + amountInt; + + await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString())); + + // Increase totalSupply + const totalSupplyBytes = await ctx.stub.getState(totalSupplyKey); + let totalSupply; + if (!totalSupplyBytes || totalSupplyBytes.length === 0) { + console.log('Initialize the tokenSupply'); + totalSupply = 0; + } else { + totalSupply = parseInt(totalSupplyBytes.toString()); + } + totalSupply = totalSupply + amountInt; + await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString())); + + // Emit the Transfer event + const transferEvent = { from: '0x0', to: minter, value: amountInt }; + ctx.stub.setEvent('Transfer', Buffer.from(JSON.stringify(transferEvent))); + + console.log(`minter account ${minter} balance updated from ${currentBalance} to ${updatedBalance}`); + return true; + } + + /** + * Burn redeem tokens from minter's account balance + * + * @param {Context} ctx the transaction context + * @param {Integer} amount amount of tokens to be burned + * @returns {Object} The balance + */ + async Burn(ctx, amount) { + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to burn tokens + const clientMSPID = ctx.clientIdentity.getMSPID(); + if (clientMSPID !== 'Org1MSP') { + throw new Error('client is not authorized to mint new tokens'); + } + + const minter = ctx.clientIdentity.getID(); + + const amountInt = parseInt(amount); + + const balanceKey = ctx.stub.createCompositeKey(balancePrefix, [minter]); + + const currentBalanceBytes = await ctx.stub.getState(balanceKey); + if (!currentBalanceBytes || currentBalanceBytes.length === 0) { + throw new Error('The balance does not exist'); + } + const currentBalance = parseInt(currentBalanceBytes.toString()); + const updatedBalance = currentBalance - amountInt; + + await ctx.stub.putState(balanceKey, Buffer.from(updatedBalance.toString())); + + // Decrease totalSupply + const totalSupplyBytes = await ctx.stub.getState(totalSupplyKey); + if (!totalSupplyBytes || totalSupplyBytes.length === 0) { + throw new Error('totalSupply does not exist.'); + } + const totalSupply = parseInt(totalSupplyBytes.toString()) - amountInt; + await ctx.stub.putState(totalSupplyKey, Buffer.from(totalSupply.toString())); + + // Emit the Transfer event + const transferEvent = { from: minter, to: '0x0', value: amountInt }; + ctx.stub.setEvent('Transfer', Buffer.from(JSON.stringify(transferEvent))); + + console.log(`minter account ${minter} balance updated from ${currentBalance} to ${updatedBalance}`); + return true; + } + + /** + * ClientAccountBalance returns the balance of the requesting client's account. + * + * @param {Context} ctx the transaction context + * @returns {Number} Returns the account balance + */ + async ClientAccountBalance(ctx) { + // Get ID of submitting client identity + const clientAccountID = ctx.clientIdentity.getID(); + + const balanceKey = ctx.stub.createCompositeKey(balancePrefix, [clientAccountID]); + const balanceBytes = await ctx.stub.getState(balanceKey); + if (!balanceBytes || balanceBytes.length === 0) { + throw new Error(`the account ${clientAccountID} does not exist`); + } + const balance = parseInt(balanceBytes.toString()); + + return balance; + } + + // ClientAccountID returns the id of the requesting client's account. + // In this implementation, the client account ID is the clientId itself. + // Users can use this function to get their own account id, which they can then give to others as the payment address + async ClientAccountID(ctx) { + // Get ID of submitting client identity + const clientAccountID = ctx.clientIdentity.getID(); + return clientAccountID; + } + +} + +module.exports = TokenERC20Contract; \ No newline at end of file diff --git a/token-erc-20/chaincode-javascript/package.json b/token-erc-20/chaincode-javascript/package.json new file mode 100644 index 00000000..e704f980 --- /dev/null +++ b/token-erc-20/chaincode-javascript/package.json @@ -0,0 +1,51 @@ +{ + "name": "token-erc20", + "version": "0.0.1", + "description": "Token-ERC20 contract implemented in JavaScript", + "main": "index.js", + "engines": { + "node": ">=12", + "npm": ">=5" + }, + "scripts": { + "lint": "eslint .", + "pretest": "npm run lint", + "test": "nyc mocha --recursive", + "mocha": "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", + "chai-as-promised": "^7.1.1", + "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": false, + "statements": 100, + "branches": 100, + "functions": 100, + "lines": 100 + } +} diff --git a/token-erc-20/chaincode-javascript/test/tokenERC20.test.js b/token-erc-20/chaincode-javascript/test/tokenERC20.test.js new file mode 100644 index 00000000..6cfafa58 --- /dev/null +++ b/token-erc-20/chaincode-javascript/test/tokenERC20.test.js @@ -0,0 +1,271 @@ +/* +SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; + +const { Context } = require('fabric-contract-api'); +const { ChaincodeStub, ClientIdentity } = require('fabric-shim'); + +const { TokenERC20Contract } = require('..'); + +const chai = require('chai'); +const chaiAsPromised = require('chai-as-promised'); +const sinon = require('sinon'); +const expect = chai.expect; + +chai.should(); +chai.use(chaiAsPromised); + +describe('Chaincode', () => { + let sandbox; + let token; + let ctx; + let mockStub; + let mockClientIdentity; + + beforeEach('Sandbox creation', () => { + sandbox = sinon.createSandbox(); + token = new TokenERC20Contract('token-erc20'); + + ctx = sinon.createStubInstance(Context); + mockStub = sinon.createStubInstance(ChaincodeStub); + ctx.stub = mockStub; + mockClientIdentity = sinon.createStubInstance(ClientIdentity); + ctx.clientIdentity = mockClientIdentity; + + mockStub.putState.resolves('some state'); + mockStub.setEvent.returns('set event'); + + }); + + afterEach('Sandbox restoration', () => { + sandbox.restore(); + }); + + describe('#TokenName', () => { + it('should work', async () => { + mockStub.getState.resolves('some state'); + + const response = await token.TokenName(ctx); + sinon.assert.calledWith(mockStub.getState, 'name'); + expect(response).to.equals('some state'); + }); + }); + + describe('#Symbol', () => { + it('should work', async () => { + mockStub.getState.resolves('some state'); + + const response = await token.Symbol(ctx); + sinon.assert.calledWith(mockStub.getState, 'symbol'); + expect(response).to.equals('some state'); + }); + }); + + describe('#Decimals', () => { + it('should work', async () => { + mockStub.getState.resolves(Buffer.from('2')); + + const response = await token.Decimals(ctx); + sinon.assert.calledWith(mockStub.getState, 'decimals'); + expect(response).to.equals(2); + }); + }); + + describe('#TotalSupply', () => { + it('should work', async () => { + mockStub.getState.resolves(Buffer.from('10000')); + + const response = await token.TotalSupply(ctx); + sinon.assert.calledWith(mockStub.getState, 'totalSupply'); + expect(response).to.equals(10000); + }); + }); + + describe('#BalanceOf', () => { + it('should work', async () => { + mockStub.createCompositeKey.returns('balance_Alice'); + mockStub.getState.resolves(Buffer.from('1000')); + + const response = await token.BalanceOf(ctx, 'Alice'); + expect(response).to.equals(1000); + }); + }); + + describe('#_transfer', () => { + it('should fail when the sender does not have enough token', async () => { + mockStub.createCompositeKey.withArgs('balance', ['Alice']).returns('balance_Alice'); + mockStub.getState.withArgs('balance_Alice').resolves(Buffer.from('500')); + + await expect(token._transfer(ctx, 'Alice', 'Bob', '1000')) + .to.be.rejectedWith(Error, 'client account Alice has insufficient funds.'); + }); + + it('should transfer to a new account when the sender has enough token', async () => { + mockStub.createCompositeKey.withArgs('balance', ['Alice']).returns('balance_Alice'); + mockStub.getState.withArgs('balance_Alice').resolves(Buffer.from('1000')); + + mockStub.createCompositeKey.withArgs('balance', ['Bob']).returns('balance_Bob'); + mockStub.getState.withArgs('balance_Bob').resolves(null); + + const response = await token._transfer(ctx, 'Alice', 'Bob', '1000'); + sinon.assert.calledWith(mockStub.putState.getCall(0), 'balance_Alice', Buffer.from('0')); + sinon.assert.calledWith(mockStub.putState.getCall(1), 'balance_Bob', Buffer.from('1000')); + expect(response).to.equals(true); + }); + + it('should transfer to the existing account when the sender has enough token', async () => { + mockStub.createCompositeKey.withArgs('balance', ['Alice']).returns('balance_Alice'); + mockStub.getState.withArgs('balance_Alice').resolves(Buffer.from('1000')); + + mockStub.createCompositeKey.withArgs('balance', ['Bob']).returns('balance_Bob'); + mockStub.getState.withArgs('balance_Bob').resolves(Buffer.from('2000')); + + const response = await token._transfer(ctx, 'Alice', 'Bob', '1000'); + sinon.assert.calledWith(mockStub.putState.getCall(0), 'balance_Alice', Buffer.from('0')); + sinon.assert.calledWith(mockStub.putState.getCall(1), 'balance_Bob', Buffer.from('3000')); + expect(response).to.equals(true); + }); + + }); + + describe('#Transfer', () => { + it('should work', async () => { + mockClientIdentity.getID.returns('Alice'); + sinon.stub(token, '_transfer').returns(true); + + const response = await token.Transfer(ctx, 'Bob', '1000'); + const event = { from: 'Alice', to: 'Bob', value: 1000 }; + sinon.assert.calledWith(mockStub.setEvent, 'Transfer', Buffer.from(JSON.stringify(event))); + expect(response).to.equals(true); + }); + }); + + describe('#TransferFrom', () => { + it('should fail when the spender is not allowed to spend the token', async () => { + mockClientIdentity.getID.returns('Charlie'); + + mockStub.createCompositeKey.withArgs('allowance', ['Alice', 'Charlie']).returns('allowance_Alice_Charlie'); + mockStub.getState.withArgs('allowance_Alice_Charlie').resolves(Buffer.from('0')); + + await expect(token.TransferFrom(ctx, 'Alice', 'Bob', '1000')) + .to.be.rejectedWith(Error, 'The spender does not have enough allowance to spend.'); + }); + + it('should transfer when the spender is allowed to spend the token', async () => { + mockClientIdentity.getID.returns('Charlie'); + + mockStub.createCompositeKey.withArgs('allowance', ['Alice', 'Charlie']).returns('allowance_Alice_Charlie'); + mockStub.getState.withArgs('allowance_Alice_Charlie').resolves(Buffer.from('3000')); + + sinon.stub(token, '_transfer').returns(true); + + const response = await token.TransferFrom(ctx, 'Alice', 'Bob', '1000'); + sinon.assert.calledWith(mockStub.putState, 'allowance_Alice_Charlie', Buffer.from('2000')); + const event = { from: 'Alice', to: 'Bob', value: 1000 }; + sinon.assert.calledWith(mockStub.setEvent, 'Transfer', Buffer.from(JSON.stringify(event))); + expect(response).to.equals(true); + }); + }); + + describe('#Approve', () => { + it('should work', async () => { + mockClientIdentity.getID.returns('Dave'); + mockStub.createCompositeKey.returns('allowance_Dave_Eve'); + + const response = await token.Approve(ctx, 'Ellen', '1000'); + sinon.assert.calledWith(mockStub.putState, 'allowance_Dave_Eve', Buffer.from('1000')); + expect(response).to.equals(true); + }); + }); + + describe('#Allowance', () => { + it('should work', async () => { + mockStub.createCompositeKey.returns('allowance_Dave_Eve'); + mockStub.getState.resolves(Buffer.from('1000')); + + const response = await token.Allowance(ctx, 'Dave', 'Eve'); + expect(response).to.equals(1000); + }); + }); + + describe('#Mint', () => { + it('should add token to a new account and a new total supply', async () => { + mockClientIdentity.getMSPID.returns('Org1MSP'); + mockClientIdentity.getID.returns('Alice'); + mockStub.createCompositeKey.returns('balance_Alice'); + mockStub.getState.withArgs('balance_Alice').resolves(null); + mockStub.getState.withArgs('totalSupply').resolves(null); + + const response = await token.Mint(ctx, '1000'); + sinon.assert.calledWith(mockStub.putState.getCall(0), 'balance_Alice', Buffer.from('1000')); + sinon.assert.calledWith(mockStub.putState.getCall(1), 'totalSupply', Buffer.from('1000')); + expect(response).to.equals(true); + }); + + it('should add token to the existing account and the existing total supply', async () => { + mockClientIdentity.getMSPID.returns('Org1MSP'); + mockClientIdentity.getID.returns('Alice'); + mockStub.createCompositeKey.returns('balance_Alice'); + mockStub.getState.withArgs('balance_Alice').resolves(Buffer.from('1000')); + mockStub.getState.withArgs('totalSupply').resolves(Buffer.from('2000')); + + const response = await token.Mint(ctx, '1000'); + sinon.assert.calledWith(mockStub.putState.getCall(0), 'balance_Alice', Buffer.from('2000')); + sinon.assert.calledWith(mockStub.putState.getCall(1), 'totalSupply', Buffer.from('3000')); + expect(response).to.equals(true); + }); + + it('should add token to a new account and the existing total supply', async () => { + mockClientIdentity.getMSPID.returns('Org1MSP'); + mockClientIdentity.getID.returns('Alice'); + mockStub.createCompositeKey.returns('balance_Alice'); + mockStub.getState.withArgs('balance_Alice').resolves(null); + mockStub.getState.withArgs('totalSupply').resolves(Buffer.from('2000')); + + const response = await token.Mint(ctx, '1000'); + sinon.assert.calledWith(mockStub.putState.getCall(0), 'balance_Alice', Buffer.from('1000')); + sinon.assert.calledWith(mockStub.putState.getCall(1), 'totalSupply', Buffer.from('3000')); + expect(response).to.equals(true); + }); + + }); + + describe('#Burn', () => { + it('should work', async () => { + mockClientIdentity.getMSPID.returns('Org1MSP'); + mockClientIdentity.getID.returns('Alice'); + mockStub.createCompositeKey.returns('balance_Alice'); + mockStub.getState.withArgs('balance_Alice').resolves(Buffer.from('1000')); + mockStub.getState.withArgs('totalSupply').resolves(Buffer.from('2000')); + + const response = await token.Burn(ctx, '1000'); + sinon.assert.calledWith(mockStub.putState.getCall(0), 'balance_Alice', Buffer.from('0')); + sinon.assert.calledWith(mockStub.putState.getCall(1), 'totalSupply', Buffer.from('1000')); + expect(response).to.equals(true); + }); + }); + + describe('#ClientAccountBalance', () => { + it('should work', async () => { + mockClientIdentity.getID.returns('Alice'); + mockStub.createCompositeKey.returns('balance_Alice'); + mockStub.getState.resolves(Buffer.from('1000')); + + const response = await token.ClientAccountBalance(ctx,); + expect(response).to.equals(1000); + }); + }); + + describe('#ClientAccountID', () => { + it('should work', async () => { + mockClientIdentity.getID.returns('x509::{subject DN}::{issuer DN}'); + + const response = await token.ClientAccountID(ctx); + sinon.assert.calledOnce(mockClientIdentity.getID); + expect(response).to.equals('x509::{subject DN}::{issuer DN}'); + }); + }); + +}); diff --git a/token-utxo/README.md b/token-utxo/README.md new file mode 100644 index 00000000..665d9807 --- /dev/null +++ b/token-utxo/README.md @@ -0,0 +1,212 @@ +# UTXO token scenario + +The UTXO token smart contract demonstrates how to create and transfer fungible tokens using a UTXO (unspent transaction output) model. In a UTXO model, unspent transaction outputs representing a number of tokens can be 'spent' to transfer tokens between participants. +An unspent transaction output can only be spent once, and the full value must be completely spent. A transaction that spends UTXOs as input will also generate new UTXOs as outputs, where the value of the inputs must equal the value of the outputs. As an example, if you own an unspent transaction output representing 5000 tokens, and you need to transfer 100 tokens to a recipient, the transaction would spend the 5000 token UTXO as input, create a new 100 token UTXO output owned by the recipient, and return a new 4900 token UTXO to you as 'change'. + +Each UTXO in this sample has a key derived from the transaction id that created it, as well as a number of tokens, and an owner that is authorized to spend the tokens. +Ownership of each UTXO could be represented at the organization level or client identity level. In this sample UTXO ownership is based on a client identity, where the client ID is simply a base64-encoded concatenation of the issuer and subject from the client identity's enrollment certificate. The client ID can therefore be used as the payment address when transferring tokens in a UTXO transaction. + +While a transfer transaction spends UTXOs and creates new UTXOs for the recipient(s), a mint transaction can create new UTXOs. In this sample it is assumed that only one organization (played by Org1) is in a central banker role and can mint new tokens owned by their client ID. Any client from any organization can transfer tokens in a UTXO transaction. + +In this tutorial, you will mint and transfer tokens as follows: + +- A member of Org1 uses the `Mint` function to create a UTXO representing a number of tokens. The `Mint` function reads the certificate information of the client identity that submitted the transaction using the `GetClientIdentity.GetID()` API and assigns the UTXO ownership to the minter client ID. +- The same minter client will then use the `Transfer` function to transfer the requested number of tokens to a recipient. The minted UTXO key gets passed as input to the `Transfer` function, a UTXO output representing the number of transferred tokens gets created for the recipient, and another UTXO output representing the 'change' gets created for the minter. It is assumed that the recipient has provided their client ID to the transfer caller out of band. The recipient can then transfer tokens to other registered users in the same fashion. + +## Bring up the test network + +You can run the UTXO token transfer scenario using the Fabric test network. Open a command terminal and navigate to the 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 start the test network: +``` +./network.sh up createChannel -ca +``` + +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 UTXO token contract to the channel that was just created. Deploy the smart contract to `mychannel` using the following command: +``` +./network.sh deployCC -ccn token_utxo -ccp ../token-utxo/chaincode-go/ +``` + +The above command deploys the go chaincode with short name `token_utxo`. The smart contract will utilize the default endorsement policy of majority of channel members. +Since the channel has two members, this implies that we'll need to get peer endorsements from 2 out of the 2 channel members. + +Now you are ready to call the deployed smart contract via peer CLI calls. But let's first create the client identities for our scenario. + +## Register identities + +The smart contract supports UTXO ownership based on individual client identities from organizations that are members of the channel. In our scenario, the minter of the tokens will be a member of Org1, while the recipient 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 (and subsequent commands) +``` +export PATH=${PWD}/../bin:${PWD}:$PATH +export FABRIC_CFG_PATH=$PWD/../config/ +``` + +The terminal we have been using will represent Org1. We will use the Org1 CA to create the minter identity. 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 minter client identity using the `fabric-ca-client` tool: +``` +fabric-ca-client register --caname ca-org1 --id.name minter --id.secret minterpw --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://minter:minterpw@localhost:7054 --caname ca-org1 -M ${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@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 minter identity MSP folder. +``` +cp ${PWD}/organizations/peerOrganizations/org1.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org1.example.com/users/minter@org1.example.com/msp/config.yaml +``` + +Open a new terminal to represent Org2 and navigate to fabric-samples/test-network. We'll use the Org2 CA to create the Org2 recipient identity. Set the Fabric CA client home to the MSP of the Org2 CA admin: +``` +cd fabric-samples/test-network +export PATH=${PWD}/../bin:${PWD}:$PATH +export FABRIC_CA_CLIENT_HOME=${PWD}/organizations/peerOrganizations/org2.example.com/ +``` + +You can register a recipient client identity using the `fabric-ca-client` tool: +``` +fabric-ca-client register --caname ca-org2 --id.name recipient --id.secret recipientpw --id.type client --tls.certfiles ${PWD}/organizations/fabric-ca/org2/tls-cert.pem +``` + +We can now enroll to generate the identity certificates and MSP folder: +``` +fabric-ca-client enroll -u https://recipient:recipientpw@localhost:8054 --caname ca-org2 -M ${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@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 recipient identity MSP folder. +``` +cp ${PWD}/organizations/peerOrganizations/org2.example.com/msp/config.yaml ${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@org2.example.com/msp/config.yaml +``` + +## Mint some tokens + +Now that we have created the identity of the minter, we can invoke the smart contract to mint some tokens. +Shift back to the Org1 terminal, we'll set the following environment variables to operate the `peer` CLI as the minter 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/minter@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 +export TARGET_TLS_OPTIONS="-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 --peerAddresses localhost:7051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt --peerAddresses localhost:9051 --tlsRootCertFiles ${PWD}/organizations/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt" +``` + +The last environment variable above will be utilized within the CLI invoke commands to set the target peers for endorsement, and the target ordering service endpoint and TLS options. + +We can then invoke the smart contract to mint 5000 tokens: +``` +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_utxo -c '{"function":"Mint","Args":["5000"]}' +``` + +The mint function validated that the client is a member of the minter organization, and then created a UTXO with 5000 tokens belonging to the minter client identity. +The function returns the UTXO that was created so that we can inspect it. Here is the returned UTXO with JSON formatting applied: +``` +{ + "utxo_key":"c3706696c537e7bd6940fedfd52f4a3a630d253297db0ecc2b3ba514b45f5e7c.0", + "owner":"eDUwOTo6Q049bWludGVyLE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzEuZXhhbXBsZS5jb20sTz1vcmcxLmV4YW1wbGUuY29tLEw9RHVyaGFtLFNUPU5vcnRoIENhcm9saW5hLEM9VVM=", + "amount":5000 +} +``` + +Notice that the utxo_key is set to the transaction id, concatenated with ".0" to indicate that this is the first (and only) UTXO output of the transaction. Your transaction id will be different and unique. The owner is set to the minter's client ID, meaning that only this client can spend the UTXO, and the amount is "5000". + +The utxo_key that was created will be needed when you spend the UTXO in the Transfer function below. Copy the utxo_key value (including the ".0") so that you'll have it available for the Transfer function. + +We can check the minter client's total set of owned UTXOs by calling the `ClientUTXOs` function. +``` +peer chaincode query -C mychannel -n token_utxo -c '{"function":"ClientUTXOs","Args":[]}' +``` + +The same minted UTXO is returned. + + +## Transfer tokens + +The minter intends to transfer 100 tokens to the Org2 recipient, but first the Org2 recipient needs to provide their own client ID as the payment address. +A client can derive their client ID from their own public certificate, but to be sure the client ID is accurate, the contract has a `ClientID` utility function that simply looks at the callers certificate and returns the calling client's ID. +Let's prepare the Org2 terminal by setting the environment variables for the Org2 recipient user. +``` +export FABRIC_CFG_PATH=$PWD/../config/ +export CORE_PEER_TLS_ENABLED=true +export CORE_PEER_LOCALMSPID="Org2MSP" +export CORE_PEER_MSPCONFIGPATH=${PWD}/organizations/peerOrganizations/org2.example.com/users/recipient@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 +``` + +Using the Org2 terminal, the Org2 recipient user can retrieve their own client ID: +``` +peer chaincode query -C mychannel -n token_utxo -c '{"function":"ClientID","Args":[]}' +``` + +The function returns of recipient's client ID: +``` +eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw== +``` + +Let's base64 decode the client ID to make sure it is the Org2 recipient user: +``` +echo eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw== | base64 --decode +``` + +The result shows that the subject and issuer is indeed the recipient user from Org2: +``` +x509::CN=recipient,OU=client,O=Hyperledger,ST=North Carolina,C=US::CN=ca.org2.example.com,O=org2.example.com,L=Hursley,ST=Hampshire,C=UK +``` + +After the Org2 recipient provides their client ID to the minter, the minter can initiate a transfer of tokens. We'll pass in the utxo_key of the UTXO with 5000 tokens to spend, and request that two new UTXOs get created, a UTXO with 100 tokens for the recipient, and a UTXO with 4900 tokens for the minter as the 'change'. Since the contract will create the UTXO output keys, we'll initially leave the output keys blank. +Back in the Org1 terminal, request the UTXO transfer. **Replace YOUR_UTXO_KEY below with the key you saved earlier**: +``` +peer chaincode invoke $TARGET_TLS_OPTIONS -C mychannel -n token_utxo -c '{"function":"Transfer","Args":["[\"YOUR_UTXO_KEY\"]"," [{\"utxo_key\":\"\",\"owner\":\"eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw==\",\"amount\":100},{\"utxo_key\":\"\",\"owner\":\"eDUwOTo6Q049bWludGVyLE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzEuZXhhbXBsZS5jb20sTz1vcmcxLmV4YW1wbGUuY29tLEw9RHVyaGFtLFNUPU5vcnRoIENhcm9saW5hLEM9VVM=\",\"amount\":4900}]"]}' +``` + +The `Transfer` function verifies that the calling client owns the input UTXO, and that the sum of the input amounts equals the sum of the output amounts. It will then delete (spend) the input UTXO, and create the two output UTXOs. If you passed the incorrect UTXO input key, or requested UTXO output values that don't total 5000, you'll get an error indicating as such. + +The new UTXO outputs are returned in the successful response: +``` +[{\"utxo_key\":\"e51c3d19e92326f772e49e8a3e58f2bbf72bc3905e55fcd649b97a91b9b2cf44.0\",\"owner\":\"eDUwOTo6Q049cmVjaXBpZW50LE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzIuZXhhbXBsZS5jb20sTz1vcmcyLmV4YW1wbGUuY29tLEw9SHVyc2xleSxTVD1IYW1wc2hpcmUsQz1VSw==\",\"amount\":100},{\"utxo_key\":\"e51c3d19e92326f772e49e8a3e58f2bbf72bc3905e55fcd649b97a91b9b2cf44.1\",\"owner\":\"eDUwOTo6Q049bWludGVyLE9VPWNsaWVudCxPPUh5cGVybGVkZ2VyLFNUPU5vcnRoIENhcm9saW5hLEM9VVM6OkNOPWNhLm9yZzEuZXhhbXBsZS5jb20sTz1vcmcxLmV4YW1wbGUuY29tLEw9RHVyaGFtLFNUPU5vcnRoIENhcm9saW5hLEM9VVM=\",\"amount\":4900}] +``` + +While still in the Org1 terminal, let's request the minter's UTXOs again: +``` +peer chaincode query -C mychannel -n token_utxo -c '{"function":"ClientUTXOs","Args":[]}' +``` + +The new UTXO worth 4900 tokens is returned. + +And then using the Org2 terminal, let's request the recipient's UTXOs: +``` +peer chaincode query -C mychannel -n token_utxo -c '{"function":"ClientUTXOs","Args":[]}' +``` + +The new UTXO worth 100 tokens is returned. + +Congratulations, you've transferred 100 tokens! The Org2 recipient can now transfer tokens to other registered users in the same manner. + +## 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 +``` + +## Contract extension ideas + +You can extend the basic UTXO token sample to meet other requirements. For example: + +* Rather than using the default 'majority' endorsement policy, you could set the endorsement policy to a subset of organizations that represent trust anchors for the contract execution. +* You could utilize anonymous payment addresses on the UTXO outputs based on private-public key pairs, instead of UTXOs assigned to a client ID. In order to spend the tokens, the client would have to sign the transfer input as proof that they own the address private key, which the contract would then validate, similar to the Bitcoin model in the permissionless blockchain space. However, in a permissioned blockchain such as Fabric, only registered clients are authorized to participate. Furthermore, if you don't want to leak the registered client identity associated with each address, the clients could be registered using an Identity Mixer MSP, so that the client itself is also anonymous in each of the token transactions. diff --git a/token-utxo/chaincode-go/chaincode/token_contract.go b/token-utxo/chaincode-go/chaincode/token_contract.go new file mode 100644 index 00000000..963e11aa --- /dev/null +++ b/token-utxo/chaincode-go/chaincode/token_contract.go @@ -0,0 +1,221 @@ +package chaincode + +import ( + "fmt" + "log" + "strconv" + + "github.com/hyperledger/fabric-contract-api-go/contractapi" +) + +// SmartContract provides functions for transferring tokens using UTXO transactions +type SmartContract struct { + contractapi.Contract +} + +// UTXO represents an unspent transaction output +type UTXO struct { + Key string `json:"utxo_key"` + Owner string `json:"owner"` + Amount int `json:"amount"` +} + +// Mint creates a new unspent transaction output (UTXO) owned by the minter +func (s *SmartContract) Mint(ctx contractapi.TransactionContextInterface, amount int) (*UTXO, error) { + + // Check minter authorization - this sample assumes Org1 is the central banker with privilege to mint new tokens + clientMSPID, err := ctx.GetClientIdentity().GetMSPID() + if err != nil { + return nil, fmt.Errorf("failed to get MSPID: %v", err) + } + if clientMSPID != "Org1MSP" { + return nil, fmt.Errorf("client is not authorized to mint new tokens") + } + + // Get ID of submitting client identity + minter, err := ctx.GetClientIdentity().GetID() + if err != nil { + return nil, fmt.Errorf("failed to get client id: %v", err) + } + + if amount <= 0 { + return nil, fmt.Errorf("mint amount must be a positive integer") + } + + utxo := UTXO{} + utxo.Key = ctx.GetStub().GetTxID() + ".0" + utxo.Owner = minter + utxo.Amount = amount + + // the utxo has a composite key of owner:utxoKey, this enables ClientUTXOs() function to query for an owner's utxos. + utxoCompositeKey, err := ctx.GetStub().CreateCompositeKey("utxo", []string{minter, utxo.Key}) + + err = ctx.GetStub().PutState(utxoCompositeKey, []byte(strconv.Itoa(amount))) + if err != nil { + return nil, err + } + + log.Printf("utxo minted: %+v", utxo) + + return &utxo, nil +} + +// Transfer transfers UTXOs containing tokens from client to recipient(s) +func (s *SmartContract) Transfer(ctx contractapi.TransactionContextInterface, utxoInputKeys []string, utxoOutputs []UTXO) ([]UTXO, error) { + + // Get ID of submitting client identity + clientID, err := ctx.GetClientIdentity().GetID() + if err != nil { + return nil, fmt.Errorf("failed to get client id: %v", err) + } + + // Validate and summarize utxo inputs + utxoInputs := make(map[string]*UTXO) + var totalInputAmount int + for _, utxoInputKey := range utxoInputKeys { + if utxoInputs[utxoInputKey] != nil { + return nil, fmt.Errorf("the same utxo input can not be spend twice") + } + + utxoInputCompositeKey, err := ctx.GetStub().CreateCompositeKey("utxo", []string{clientID, utxoInputKey}) + if err != nil { + return nil, fmt.Errorf("failed to create composite key: %v", err) + } + + // validate that client has a utxo matching the input key + valueBytes, err := ctx.GetStub().GetState(utxoInputCompositeKey) + if err != nil { + return nil, fmt.Errorf("failed to read utxoInputCompositeKey %s from world state: %v", utxoInputCompositeKey, err) + } + + if valueBytes == nil { + return nil, fmt.Errorf("utxoInput %s not found for client %s", utxoInputKey, clientID) + } + + amount, _ := strconv.Atoi(string(valueBytes)) // Error handling not needed since Itoa() was used when setting the utxo amount, guaranteeing it was an integer. + + utxoInput := &UTXO{ + Key: utxoInputKey, + Owner: clientID, + Amount: amount, + } + + totalInputAmount += amount + utxoInputs[utxoInputKey] = utxoInput + } + + // Validate and summarize utxo outputs + var totalOutputAmount int + txID := ctx.GetStub().GetTxID() + for i, utxoOutput := range utxoOutputs { + + if utxoOutput.Amount <= 0 { + return nil, fmt.Errorf("utxo output amount must be a positive integer") + } + + utxoOutputs[i].Key = fmt.Sprintf("%s.%d", txID, i) + + totalOutputAmount += utxoOutput.Amount + } + + // Validate total inputs equals total outputs + if totalInputAmount != totalOutputAmount { + return nil, fmt.Errorf("total utxoInput amount %d does not equal total utxoOutput amount %d", totalInputAmount, totalOutputAmount) + } + + // Since the transaction is valid, now delete utxo inputs from owner's state + for _, utxoInput := range utxoInputs { + + utxoInputCompositeKey, err := ctx.GetStub().CreateCompositeKey("utxo", []string{utxoInput.Owner, utxoInput.Key}) + if err != nil { + return nil, fmt.Errorf("failed to create composite key: %v", err) + } + + err = ctx.GetStub().DelState(utxoInputCompositeKey) + if err != nil { + return nil, err + } + log.Printf("utxoInput deleted: %+v", utxoInput) + } + + // Create utxo outputs using a composite key based on the owner and utxo key + for _, utxoOutput := range utxoOutputs { + utxoOutputCompositeKey, err := ctx.GetStub().CreateCompositeKey("utxo", []string{utxoOutput.Owner, utxoOutput.Key}) + if err != nil { + return nil, fmt.Errorf("failed to create composite key: %v", err) + } + + err = ctx.GetStub().PutState(utxoOutputCompositeKey, []byte(strconv.Itoa(utxoOutput.Amount))) + if err != nil { + return nil, err + } + log.Printf("utxoOutput created: %+v", utxoOutput) + } + + return utxoOutputs, nil +} + +// ClientUTXOs returns all UTXOs owned by the calling client +func (s *SmartContract) ClientUTXOs(ctx contractapi.TransactionContextInterface) ([]*UTXO, error) { + + // Get ID of submitting client identity + clientID, err := ctx.GetClientIdentity().GetID() + if err != nil { + return nil, fmt.Errorf("failed to get client id: %v", err) + } + + // since utxos have a composite key of owner:utxoKey, we can query for all utxos matching owner:* + utxoResultsIterator, err := ctx.GetStub().GetStateByPartialCompositeKey("utxo", []string{clientID}) + if err != nil { + return nil, err + } + defer utxoResultsIterator.Close() + + var utxos []*UTXO + for utxoResultsIterator.HasNext() { + utxoRecord, err := utxoResultsIterator.Next() + if err != nil { + return nil, err + } + + // composite key is expected to be owner:utxoKey + _, compositeKeyParts, err := ctx.GetStub().SplitCompositeKey(utxoRecord.Key) + if err != nil { + return nil, err + } + + if len(compositeKeyParts) != 2 { + return nil, fmt.Errorf("expected composite key with two parts (owner:utxoKey)") + } + + utxoKey := compositeKeyParts[1] // owner is at [0], utxoKey is at[1] + + if utxoRecord.Value == nil { + return nil, fmt.Errorf("utxo %s has no value", utxoKey) + } + + amount, _ := strconv.Atoi(string(utxoRecord.Value)) // Error handling not needed since Itoa() was used when setting the utxo amount, guaranteeing it was an integer. + + utxo := &UTXO{ + Key: utxoKey, + Owner: clientID, + Amount: amount, + } + + utxos = append(utxos, utxo) + } + return utxos, nil +} + +// ClientID returns the client id of the calling client +// Users can use this function to get their own client id, which they can then give to others as the payment address +func (s *SmartContract) ClientID(ctx contractapi.TransactionContextInterface) (string, error) { + + // Get ID of submitting client identity + clientID, err := ctx.GetClientIdentity().GetID() + if err != nil { + return "", fmt.Errorf("failed to get client id: %v", err) + } + + return clientID, nil +} diff --git a/token-utxo/chaincode-go/go.mod b/token-utxo/chaincode-go/go.mod new file mode 100644 index 00000000..9cdc693f --- /dev/null +++ b/token-utxo/chaincode-go/go.mod @@ -0,0 +1,5 @@ +module github.com/hyperledger/fabric-samples/token-utxo/chaincode-go + +go 1.14 + +require github.com/hyperledger/fabric-contract-api-go v1.1.0 diff --git a/token-utxo/chaincode-go/go.sum b/token-utxo/chaincode-go/go.sum new file mode 100644 index 00000000..5a92905b --- /dev/null +++ b/token-utxo/chaincode-go/go.sum @@ -0,0 +1,145 @@ +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 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +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 h1:VKtxabqXZkF25pY9ekfRL6a582T4P37/31XEstQ5p58= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/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 h1:1i4lnpV8BDgKOLi1hgElfBqdHXjXieSuj8629mwBZ8o= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20200424173110-d7076418f212/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 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e h1:hB2xlXdHp/pmPZq0y3QnmWAArdw9PqbmotexnWx/FU8= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/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 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= +github.com/xeipuuv/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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.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= diff --git a/token-utxo/chaincode-go/token_utxo.go b/token-utxo/chaincode-go/token_utxo.go new file mode 100644 index 00000000..481a6d30 --- /dev/null +++ b/token-utxo/chaincode-go/token_utxo.go @@ -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/token-utxo/chaincode-go/chaincode" +) + +func main() { + tokenChaincode, err := contractapi.NewChaincode(&chaincode.SmartContract{}) + if err != nil { + log.Panicf("Error creating token-utxo chaincode: %v", err) + } + + if err := tokenChaincode.Start(); err != nil { + log.Panicf("Error starting token-utxo chaincode: %v", err) + } +}