From 7c9c5d21791507b9d0e4e1733cfffd72ddd480b8 Mon Sep 17 00:00:00 2001 From: Rinish Sam <36656347+CaptainIRS@users.noreply.github.com> Date: Tue, 21 Feb 2023 23:46:37 +0530 Subject: [PATCH] Use go chaincode in integration tests (#1484) Signed-off-by: CaptainIRS <36656347+CaptainIRS@users.noreply.github.com> --- .../.gitignore | 1 + .../run-with-build.sh | 4 +- .../run-without-build.sh | 4 +- .../src/marbles/go/go.mod | 8 + .../src/marbles/go/go.sum | 58 ++ .../go/{marbles.go => marbles_chaincode.go} | 263 ++++-- .../src/marbles/node/marbles.js | 509 ------------ .../src/marbles/node/package.json | 17 - .../fabric_docker_local_tests/.gitignore | 1 + .../run-with-build.sh | 4 +- .../run-without-build.sh | 4 +- .../src/marbles/go/go.mod | 8 + .../src/marbles/go/go.sum | 58 ++ .../src/marbles/go/marbles_chaincode.go} | 263 ++++-- .../src/marbles/node/marbles.js | 509 ------------ .../statedb/couchdb/indexes/indexOwner.json | 1 - .../src/marbles/node/package.json | 17 - .../fabric_tests/.gitignore | 1 + .../fabric_tests/run.sh | 12 +- .../fabric_tests/src/marbles/go/go.mod | 8 + .../fabric_tests/src/marbles/go/go.sum | 58 ++ .../src/marbles/go/marbles_chaincode.go} | 263 ++++-- .../fabric_tests/src/marbles/node/marbles.js | 509 ------------ .../statedb/couchdb/indexes/indexOwner.json | 1 - .../src/marbles/node/package.json | 17 - .../generator_tests/fabric/.gitignore | 2 + .../generator_tests/fabric/run.sh | 2 +- .../fabric/src/marbles/go/go.mod | 8 + .../fabric/src/marbles/go/go.sum | 58 ++ .../src/marbles/go/marbles_chaincode.go | 770 ++++++++++++++++++ .../statedb/couchdb/indexes/indexOwner.json | 0 .../fabric/src/marbles/node/marbles.js | 509 ------------ .../statedb/couchdb/indexes/indexOwner.json | 1 - .../fabric/src/marbles/node/package.json | 17 - .../caliper-tests-integration/package.json | 4 +- 35 files changed, 1611 insertions(+), 2358 deletions(-) create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/go.mod create mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/go.sum rename packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/{marbles.go => marbles_chaincode.go} (76%) delete mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/marbles.js delete mode 100644 packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/package.json create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/go.mod create mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/go.sum rename packages/caliper-tests-integration/{fabric_tests/src/marbles/go/marbles.go => fabric_docker_local_tests/src/marbles/go/marbles_chaincode.go} (76%) delete mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/marbles.js delete mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json delete mode 100644 packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/package.json create mode 100644 packages/caliper-tests-integration/fabric_tests/src/marbles/go/go.mod create mode 100644 packages/caliper-tests-integration/fabric_tests/src/marbles/go/go.sum rename packages/caliper-tests-integration/{fabric_docker_local_tests/src/marbles/go/marbles.go => fabric_tests/src/marbles/go/marbles_chaincode.go} (76%) delete mode 100644 packages/caliper-tests-integration/fabric_tests/src/marbles/node/marbles.js delete mode 100644 packages/caliper-tests-integration/fabric_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json delete mode 100644 packages/caliper-tests-integration/fabric_tests/src/marbles/node/package.json create mode 100644 packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/go.mod create mode 100644 packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/go.sum create mode 100644 packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/marbles_chaincode.go rename packages/caliper-tests-integration/{fabric_docker_distributed_tests/src/marbles/node => generator_tests/fabric/src/marbles/go}/metadata/statedb/couchdb/indexes/indexOwner.json (100%) delete mode 100644 packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/marbles.js delete mode 100644 packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json delete mode 100644 packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/package.json diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/.gitignore b/packages/caliper-tests-integration/fabric_docker_distributed_tests/.gitignore index f44eb31f6..3ca6b498b 100644 --- a/packages/caliper-tests-integration/fabric_docker_distributed_tests/.gitignore +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/.gitignore @@ -3,3 +3,4 @@ report.html node_modules/* package-lock.json fabric-samples +vendor diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-with-build.sh b/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-with-build.sh index 065dc2070..0201acdd8 100755 --- a/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-with-build.sh +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-with-build.sh @@ -30,8 +30,8 @@ pushd ${TEST_NETWORK_DIR} ./network.sh up -s couchdb ./network.sh createChannel -c mychannel ./network.sh createChannel -c yourchannel -./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" -./network.sh deployCC -ccn yourmarbles -c yourchannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn yourmarbles -c yourchannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" popd # back to this dir diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-without-build.sh b/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-without-build.sh index e3b01abe1..3290ca875 100755 --- a/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-without-build.sh +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/run-without-build.sh @@ -30,8 +30,8 @@ pushd ${TEST_NETWORK_DIR} ./network.sh up -s couchdb ./network.sh createChannel -c mychannel ./network.sh createChannel -c yourchannel -./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" -./network.sh deployCC -ccn yourmarbles -c yourchannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn yourmarbles -c yourchannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" popd # back to this dir diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/go.mod b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/go.mod new file mode 100644 index 000000000..8daf08249 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/go.mod @@ -0,0 +1,8 @@ +module github.com/hyperledger/fabric-samples/chaincode/marbles02/go + +go 1.12 + +require ( + github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85 + github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022 +) diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/go.sum b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/go.sum new file mode 100644 index 000000000..e37d78069 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/go.sum @@ -0,0 +1,58 @@ +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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +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/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85 h1:VEm3tPRTCzq3J/1XpVERh1PbOSnshUVwx2G5s3cLiTw= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85/go.mod h1:HZK6PKLWrvdD/t0oSLiyaRaUM6fZ7qjJuOlb0zrn0mo= +github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022 h1:WzttYAPO5xkQ87ZrxzEhvDZknfarSNu1PZt3NPMTE3Y= +github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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= +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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= \ No newline at end of file diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/marbles.go b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/marbles_chaincode.go similarity index 76% rename from packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/marbles.go rename to packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/marbles_chaincode.go index 4e932984b..dfe34b6fb 100644 --- a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/marbles.go +++ b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/go/marbles_chaincode.go @@ -14,7 +14,7 @@ /* * NOTE: This implementation is a replica of the following: -* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js +* https://github.com/hyperledger/fabric-samples/blob/main/chaincode/marbles02/go/marbles_chaincode.go */ // ====CHAINCODE EXECUTION SAMPLES (CLI) ================== @@ -33,8 +33,11 @@ // peer chaincode query -C myc1 -n marbles -c '{"Args":["getHistoryForMarble","marble1"]}' // Rich Query (Only supported if CouchDB is used as state database): -// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' -// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' + +// Rich Query with Pagination (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesWithPagination","{\"selector\":{\"owner\":\"tom\"}}","3",""]}' // INDEXES TO SUPPORT COUCHDB RICH QUERIES // @@ -73,23 +76,15 @@ // http://127.0.0.1:5984/ // Index for docType, owner. -// Note that docType and owner fields must be prefixed with the "data" wrapper -// -// Index definition for use with Fauxton interface -// {"index":{"fields":["data.docType","data.owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} // -// Example curl command line to define index in the CouchDB channel_chaincode database -// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"data.docType\",\"data.owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index +// Example curl command line to define index in the CouchDB channel_chaincode database. Default user_name/password in couchdb is admin/adminpw +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"docType\",\"owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" user_name:password@hostname:port/myc1_marbles/_index // // Index for docType, owner, size (descending order). -// Note that docType, owner and size fields must be prefixed with the "data" wrapper -// -// Index definition for use with Fauxton interface -// {"index":{"fields":[{"data.size":"desc"},{"data.docType":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeSortDoc", "name":"indexSizeSortDesc","type":"json"} // -// Example curl command line to define index in the CouchDB channel_chaincode database -// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"data.size\":\"desc\"},{\"data.docType\":\"desc\"},{\"data.owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index +// Example curl command line to define index in the CouchDB channel_chaincode database. Default user_name/password in couchdb is admin/adminpw +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"size\":\"desc\"},{\"docType\":\"desc\"},{\"owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" user_name:password@hostname:port/myc1_marbles/_index // Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database): // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":\"marble\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}' @@ -107,8 +102,8 @@ import ( "strings" "time" - "github.com/hyperledger/fabric/core/chaincode/shim" - pb "github.com/hyperledger/fabric/protos/peer" + "github.com/hyperledger/fabric-chaincode-go/shim" + pb "github.com/hyperledger/fabric-protos-go/peer" ) // SimpleChaincode example simple Chaincode implementation @@ -136,19 +131,6 @@ func main() { // Init initializes chaincode // =========================== func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { - var err error - marbles := []marble{ - marble{ObjectType: "marble", Name: "marble1", Color: "Green", Size: 5, Owner: "Alice"}, - marble{ObjectType: "marble", Name: "marble2", Color: "Yellow", Size: 10, Owner: "Bob"}, - marble{ObjectType: "marble", Name: "marble3", Color: "Blue", Size: 15, Owner: "Tom"}, - } - for i, marble := range marbles { - marbleJSONasBytes, _ := json.Marshal(marble) - stub.PutState(marbles[i].Name, marbleJSONasBytes) - if err != nil { - return shim.Error("Failed to put to world state. %s") - } - } return shim.Success(nil) } @@ -177,6 +159,10 @@ func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { return t.getHistoryForMarble(stub, args) } else if function == "getMarblesByRange" { //get marbles based on range query return t.getMarblesByRange(stub, args) + } else if function == "getMarblesByRangeWithPagination" { + return t.getMarblesByRangeWithPagination(stub, args) + } else if function == "queryMarblesWithPagination" { + return t.queryMarblesWithPagination(stub, args) } fmt.Println("invoke did not find func: " + function) //error @@ -374,31 +360,10 @@ func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args } // =========================================================================================== -// getMarblesByRange performs a range query based on the start and end keys provided. - -// Read-only function results are not typically submitted to ordering. If the read-only -// results are submitted to ordering, or if the query is used in an update transaction -// and submitted to ordering, then the committing peers will re-execute to guarantee that -// result sets are stable between endorsement time and commit time. The transaction is -// invalidated by the committing peers if the result set has changed between endorsement -// time and commit time. -// Therefore, range queries are a safe option for performing update transactions based on query results. +// constructQueryResponseFromIterator constructs a JSON array containing query results from +// a given result iterator // =========================================================================================== -func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { - - if len(args) < 2 { - return shim.Error("Incorrect number of arguments. Expecting 2") - } - - startKey := args[0] - endKey := args[1] - - resultsIterator, err := stub.GetStateByRange(startKey, endKey) - if err != nil { - return shim.Error(err.Error()) - } - defer resultsIterator.Close() - +func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) (*bytes.Buffer, error) { // buffer is a JSON array containing QueryResults var buffer bytes.Buffer buffer.WriteString("[") @@ -407,7 +372,7 @@ func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, ar for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { - return shim.Error(err.Error()) + return nil, err } // Add a comma before array members, suppress it for the first array member if bArrayMemberAlreadyWritten == true { @@ -426,6 +391,58 @@ func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, ar } buffer.WriteString("]") + return &buffer, nil +} + +// =========================================================================================== +// addPaginationMetadataToQueryResults adds QueryResponseMetadata, which contains pagination +// info, to the constructed query results +// =========================================================================================== +func addPaginationMetadataToQueryResults(buffer *bytes.Buffer, responseMetadata *pb.QueryResponseMetadata) *bytes.Buffer { + + buffer.WriteString("[{\"ResponseMetadata\":{\"RecordsCount\":") + buffer.WriteString("\"") + buffer.WriteString(fmt.Sprintf("%v", responseMetadata.FetchedRecordsCount)) + buffer.WriteString("\"") + buffer.WriteString(", \"Bookmark\":") + buffer.WriteString("\"") + buffer.WriteString(responseMetadata.Bookmark) + buffer.WriteString("\"}}]") + + return buffer +} + +// =========================================================================================== +// getMarblesByRange performs a range query based on the start and end keys provided. + +// Read-only function results are not typically submitted to ordering. If the read-only +// results are submitted to ordering, or if the query is used in an update transaction +// and submitted to ordering, then the committing peers will re-execute to guarantee that +// result sets are stable between endorsement time and commit time. The transaction is +// invalidated by the committing peers if the result set has changed between endorsement +// time and commit time. +// Therefore, range queries are a safe option for performing update transactions based on query results. +// =========================================================================================== +func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + startKey := args[0] + endKey := args[1] + + resultsIterator, err := stub.GetStateByRange(startKey, endKey) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return shim.Error(err.Error()) + } + fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String()) return shim.Success(buffer.Bytes()) @@ -567,34 +584,122 @@ func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString } defer resultsIterator.Close() - // buffer is a JSON array containing QueryRecords - var buffer bytes.Buffer - buffer.WriteString("[") + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return nil, err + } - bArrayMemberAlreadyWritten := false - for resultsIterator.HasNext() { - queryResponse, err := resultsIterator.Next() - if err != nil { - return nil, err - } - // Add a comma before array members, suppress it for the first array member - if bArrayMemberAlreadyWritten == true { - buffer.WriteString(",") - } - buffer.WriteString("{\"Key\":") - buffer.WriteString("\"") - buffer.WriteString(queryResponse.Key) - buffer.WriteString("\"") + fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) - buffer.WriteString(", \"Record\":") - // Record is a JSON object, so we write as-is - buffer.WriteString(string(queryResponse.Value)) - buffer.WriteString("}") - bArrayMemberAlreadyWritten = true + return buffer.Bytes(), nil +} + +// ====== Pagination ========================================================================= +// Pagination provides a method to retrieve records with a defined pagesize and +// start point (bookmark). An empty string bookmark defines the first "page" of a query +// result. Paginated queries return a bookmark that can be used in +// the next query to retrieve the next page of results. Paginated queries extend +// rich queries and range queries to include a pagesize and bookmark. +// +// Two examples are provided in this example. The first is getMarblesByRangeWithPagination +// which executes a paginated range query. +// The second example is a paginated query for rich ad-hoc queries. +// ========================================================================================= + +// ====== Example: Pagination with Range Query =============================================== +// getMarblesByRangeWithPagination performs a range query based on the start & end key, +// page size and a bookmark. + +// The number of fetched records will be equal to or lesser than the page size. +// Paginated range queries are only valid for read only transactions. +// =========================================================================================== +func (t *SimpleChaincode) getMarblesByRangeWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 4 { + return shim.Error("Incorrect number of arguments. Expecting 4") } - buffer.WriteString("]") - fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) + startKey := args[0] + endKey := args[1] + //return type of ParseInt is int64 + pageSize, err := strconv.ParseInt(args[2], 10, 32) + if err != nil { + return shim.Error(err.Error()) + } + bookmark := args[3] + + resultsIterator, responseMetadata, err := stub.GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return shim.Error(err.Error()) + } + + bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) + + fmt.Printf("- getMarblesByRange queryResult:\n%s\n", bufferWithPaginationInfo.String()) + + return shim.Success(buffer.Bytes()) +} + +// ===== Example: Pagination with Ad hoc Rich Query ======================================================== +// queryMarblesWithPagination uses a query string, page size and a bookmark to perform a query +// for marbles. Query string matching state database syntax is passed in and executed as is. +// The number of fetched records would be equal to or lesser than the specified page size. +// Supports ad hoc queries that can be defined at runtime by the client. +// If this is not desired, follow the queryMarblesForOwner example for parameterized queries. +// Only available on state databases that support rich query (e.g. CouchDB) +// Paginated queries are only valid for read only transactions. +// ========================================================================================= +func (t *SimpleChaincode) queryMarblesWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 + // "queryString" + if len(args) < 3 { + return shim.Error("Incorrect number of arguments. Expecting 3") + } + + queryString := args[0] + //return type of ParseInt is int64 + pageSize, err := strconv.ParseInt(args[1], 10, 32) + if err != nil { + return shim.Error(err.Error()) + } + bookmark := args[2] + + queryResults, err := getQueryResultForQueryStringWithPagination(stub, queryString, int32(pageSize), bookmark) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(queryResults) +} + +// ========================================================================================= +// getQueryResultForQueryStringWithPagination executes the passed in query string with +// pagination info. Result set is built and returned as a byte array containing the JSON results. +// ========================================================================================= +func getQueryResultForQueryStringWithPagination(stub shim.ChaincodeStubInterface, queryString string, pageSize int32, bookmark string) ([]byte, error) { + + fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) + + resultsIterator, responseMetadata, err := stub.GetQueryResultWithPagination(queryString, pageSize, bookmark) + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return nil, err + } + + bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) + + fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", bufferWithPaginationInfo.String()) return buffer.Bytes(), nil } diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/marbles.js b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/marbles.js deleted file mode 100644 index f36228a19..000000000 --- a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/marbles.js +++ /dev/null @@ -1,509 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -/* -* NOTE: This implementation is a derivative work of the following: -* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js -* The modifications include: bug fixes and refactoring for eslint compliance. -*/ - -/* eslint-disable no-console */ - -'use strict'; -const shim = require('fabric-shim'); -const util = require('util'); - -/** - * Marble asset management chaincode written in node.js, implementing {@link ChaincodeInterface}. - * @type {SimpleChaincode} - * @extends {ChaincodeInterface} - */ -let Chaincode = class { - /** - * Called during chaincode instantiate and upgrade. This method can be used - * to initialize asset states. - * @async - * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim - * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub - * encapsulates the APIs between the chaincode implementation and the Fabric peer. - * @return {Promise} Returns a promise of a response indicating the result of the invocation. - */ - async Init(stub) { - const marbles = [ - { - name: 'marble1', - color: 'Green', - size: '5', - owner: 'Alice' - }, - { - name: 'marble2', - color: 'Yellow', - size: '15', - owner: 'Bob' - }, - { - name: 'marble3', - color: 'Blue', - size: '10', - owner: 'Tom' - } - ] - for (let i=0; i < marbles.length; i++) { - marbles[i].docType = 'marble'; - await stub.putState(marbles[i].name, Buffer.from(JSON.stringify(marbles[i]))); - console.info('Added <--> ', marbles[i]); - } - console.info('=========== Instantiated Marbles Chaincode ==========='); - return shim.success(); - } - - /** - * Called throughout the life time of the chaincode to carry out business - * transaction logic and effect the asset states. - * The provided functions are the following: initMarble, delete, transferMarble, readMarble, getMarblesByRange, - * transferMarblesBasedOnColor, queryMarblesByOwner, queryMarbles, getHistoryForMarble. - * @async - * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim - * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub - * encapsulates the APIs between the chaincode implementation and the Fabric peer. - * @return {Promise} Returns a promise of a response indicating the result of the invocation. - */ - async Invoke(stub) { - console.info('Transaction ID: ' + stub.getTxID()); - console.info(util.format('Args: %j', stub.getArgs())); - - let ret = stub.getFunctionAndParameters(); - console.info(ret); - - let method = this[ret.fcn]; - if (!method) { - console.log('no function of name:' + ret.fcn + ' found'); - throw new Error('Received unknown function ' + ret.fcn + ' invocation'); - } - try { - let payload = await method(stub, ret.params, this); - return shim.success(payload); - } catch (err) { - console.log(err); - return shim.error(err); - } - } - - /** - * Creates a new marble with the given attributes. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: marble color. - * Index 2: marble size. Index 3: marble owner. - */ - async initMarble(stub, args) { - if (args.length !== 4) { - throw new Error('Incorrect number of arguments. Expecting 4'); - } - // ==== Input sanitation ==== - console.info('--- start init marble ---'); - if (args[0].length <= 0) { - throw new Error('1st argument must be a non-empty string'); - } - if (args[1].length <= 0) { - throw new Error('2nd argument must be a non-empty string'); - } - if (args[2].length <= 0) { - throw new Error('3rd argument must be a non-empty string'); - } - if (args[3].length <= 0) { - throw new Error('4th argument must be a non-empty string'); - } - let marbleName = args[0]; - let color = args[1].toLowerCase(); - let owner = args[3].toLowerCase(); - let size = parseInt(args[2]); - if (isNaN(size)) { - throw new Error('3rd argument must be a numeric string'); - } - - // ==== Check if marble already exists ==== - let marbleState = await stub.getState(marbleName); - if (marbleState.toString()) { - throw new Error('This marble already exists: ' + marbleName); - } - - // ==== Create marble object and marshal to JSON ==== - let marble = {}; - marble.docType = 'marble'; - marble.name = marbleName; - marble.color = color; - marble.size = size; - marble.owner = owner; - - // === Save marble to state === - await stub.putState(marbleName, Buffer.from(JSON.stringify(marble))); - let indexName = 'color~name'; - let colorNameIndexKey = await stub.createCompositeKey(indexName, [marble.color, marble.name]); - console.info(colorNameIndexKey); - // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. - // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value - await stub.putState(colorNameIndexKey, Buffer.from('\u0000')); - // ==== Marble saved and indexed. Return success ==== - console.info('- end init marble'); - } - - /** - * Retrieves the information about a marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - * @return {Promise} The byte representation of the marble. - */ - async readMarble(stub, args) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting name of the marble to query'); - } - - let name = args[0]; - if (!name) { - throw new Error(' marble name must not be empty'); - } - let marbleAsBytes = await stub.getState(name); //get the marble from chaincode state - if (!marbleAsBytes.toString()) { - let jsonResp = {}; - jsonResp.Error = 'Marble does not exist: ' + name; - throw new Error(JSON.stringify(jsonResp)); - } - console.info('======================================='); - console.log(marbleAsBytes.toString()); - console.info('======================================='); - return marbleAsBytes; - } - - /** - * Deletes the given marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - */ - async delete(stub, args) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting name of the marble to delete'); - } - let marbleName = args[0]; - if (!marbleName) { - throw new Error('marble name must not be empty'); - } - // to maintain the color~name index, we need to read the marble first and get its color - let valAsBytes = await stub.getState(marbleName); //get the marble from chaincode state - let jsonResp = {}; - if (!valAsBytes) { - jsonResp.error = 'marble does not exist: ' + marbleName; - throw new Error(jsonResp); - } - let marbleJSON = {}; - try { - marbleJSON = JSON.parse(valAsBytes.toString()); - } catch (err) { - jsonResp = {}; - jsonResp.error = 'Failed to decode JSON of: ' + marbleName; - throw new Error(jsonResp); - } - - await stub.deleteState(marbleName); //remove the marble from chaincode state - - // delete the index - let indexName = 'color~name'; - let colorNameIndexKey = stub.createCompositeKey(indexName, [marbleJSON.color, marbleJSON.name]); - if (!colorNameIndexKey) { - throw new Error(' Failed to create the createCompositeKey'); - } - // Delete index entry to state. - await stub.deleteState(colorNameIndexKey); - } - - // =========================================================== - // transfer a marble by setting a new owner name on the marble - // =========================================================== - /** - * Transfers the given marble to a new owner. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: the new owner. - */ - async transferMarble(stub, args) { - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting marble name and owner'); - } - - let marbleName = args[0]; - let newOwner = args[1].toLowerCase(); - console.info('- start transferMarble ', marbleName, newOwner); - - let marbleAsBytes = await stub.getState(marbleName); - if (!marbleAsBytes || !marbleAsBytes.toString()) { - throw new Error('marble does not exist'); - } - let marbleToTransfer = {}; - try { - marbleToTransfer = JSON.parse(marbleAsBytes.toString()); //unmarshal - } catch (err) { - let jsonResp = {}; - jsonResp.error = 'Failed to decode JSON of: ' + marbleName; - throw new Error(jsonResp); - } - console.info(marbleToTransfer); - marbleToTransfer.owner = newOwner; //change the owner - - let marbleJSONasBytes = Buffer.from(JSON.stringify(marbleToTransfer)); - await stub.putState(marbleName, marbleJSONasBytes); //rewrite the marble - - console.info('- end transferMarble (success)'); - } - - /** - * Performs a range query based on the start and end keys provided. - * - * Read-only function results are not typically submitted to ordering. If the read-only - * results are submitted to ordering, or if the query is used in an update transaction - * and submitted to ordering, then the committing peers will re-execute to guarantee that - * result sets are stable between endorsement time and commit time. The transaction is - * invalidated by the committing peers if the result set has changed between endorsement - * time and commit time. - * Therefore, range queries are a safe option for performing update transactions based on query results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: start key. Index 1: end key. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The marbles in the given range. - */ - async getMarblesByRange(stub, args, thisObject) { - - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting 2'); - } - - let startKey = args[0]; - let endKey = args[1]; - - let resultsIterator = await stub.getStateByRange(startKey, endKey); - let results = await thisObject.getAllResults(resultsIterator, false); - - return Buffer.from(JSON.stringify(results)); - } - - /** - * Transfers marbles of a given color to a certain new owner. - * - * Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. - * Committing peers will re-execute range queries to guarantee that result sets are stable - * between endorsement time and commit time. The transaction is invalidated by the - * committing peers if the result set has changed between endorsement time and commit time. - * Therefore, range queries are a safe option for performing update transactions based on query results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble color. Index 1: new owner. - * @param {Chaincode} thisObject The chaincode object context. - */ - async transferMarblesBasedOnColor(stub, args, thisObject) { - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting color and owner'); - } - - let color = args[0]; - let newOwner = args[1].toLowerCase(); - console.info('- start transferMarblesBasedOnColor ', color, newOwner); - - // Query the color~name index by color - // This will execute a key range query on all keys starting with 'color' - let coloredMarbleResultsIterator = await stub.getStateByPartialCompositeKey('color~name', [color]); - - let hasNext = true; - // Iterate through result set and for each marble found, transfer to newOwner - while (hasNext) { - let responseRange; - try { - responseRange = await coloredMarbleResultsIterator.next(); - } catch (err) { - hasNext = false; - continue; - } - - if (!responseRange || !responseRange.value || !responseRange.value.key) { - return; - } - console.log(responseRange.value.key); - - // let value = res.value.value.toString('utf8'); - let objectType; - let attributes; - ({ - objectType, - attributes - } = await stub.splitCompositeKey(responseRange.value.key)); - - let returnedColor = attributes[0]; - let returnedMarbleName = attributes[1]; - console.info(util.format('- found a marble from index:%s color:%s name:%s\n', objectType, returnedColor, returnedMarbleName)); - - // Now call the transfer function for the found marble. - // Re-use the same function that is used to transfer individual marbles - await thisObject.transferMarble(stub, [returnedMarbleName, newOwner]); - } - - let responsePayload = util.format('Transferred %s marbles to %s', color, newOwner); - console.info('- end transferMarblesBasedOnColor: ' + responsePayload); - } - - /** - * Queries for marbles based on a passed in owner. - * This is an example of a parameterized query where the query logic is baked into the chaincode, - * and accepting a single query parameter (owner). - * Only available on state databases that support rich query (e.g. CouchDB) - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble owner. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The marbles of the specified owner. - */ - async queryMarblesByOwner(stub, args, thisObject) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting owner name.'); - } - - let owner = args[0].toLowerCase(); - let queryString = {}; - queryString.selector = {}; - queryString.selector.docType = 'marble'; - queryString.selector.owner = owner; - return await thisObject.getQueryResultForQueryString(stub, JSON.stringify(queryString), thisObject); //shim.success(queryResults); - } - - /** - * Uses a query string to perform a query for marbles. - * Query string matching state database syntax is passed in and executed as is. - * Supports ad hoc queries that can be defined at runtime by the client. - * If this is not desired, follow the queryMarblesForOwner example for parameterized queries. - * Only available on state databases that support rich query (e.g. CouchDB) - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: query string. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The results of the specified query. - */ - async queryMarbles(stub, args, thisObject) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting queryString'); - } - let queryString = args[0]; - if (!queryString) { - throw new Error('queryString must not be empty'); - } - - return await thisObject.getQueryResultForQueryString(stub, queryString, thisObject); - } - - /** - * Gets the results of a specified iterator. - * @async - * @param {Object} iterator The iterator to use. - * @param {Boolean} isHistory Specifies whether the iterator returns history entries or not. - * @return {Promise} The array of results in JSON format. - */ - async getAllResults(iterator, isHistory) { - let allResults = []; - let hasNext = true; - while (hasNext) { - let res; - try { - res = await iterator.next(); - } catch (err) { - hasNext = false; - continue; - } - - if (res.value && res.value.value.toString()) { - let jsonRes = {}; - console.log(res.value.value.toString('utf8')); - - if (isHistory && isHistory === true) { - jsonRes.TxId = res.value.tx_id; - jsonRes.Timestamp = res.value.timestamp; - jsonRes.IsDelete = res.value.is_delete.toString(); - try { - jsonRes.Value = JSON.parse(res.value.value.toString('utf8')); - } catch (err) { - console.log(err); - jsonRes.Value = res.value.value.toString('utf8'); - } - } else { - jsonRes.Key = res.value.key; - try { - jsonRes.Record = JSON.parse(res.value.value.toString('utf8')); - } catch (err) { - console.log(err); - jsonRes.Record = res.value.value.toString('utf8'); - } - } - allResults.push(jsonRes); - } - if (res.done) { - console.log('end of data'); - await iterator.close(); - console.info(allResults); - return allResults; - } - } - } - - /** - * Executes the provided query string. - * Result set is built and returned as a byte array containing the JSON results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String} queryString The query string to execute. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The results of the specified query. - */ - async getQueryResultForQueryString(stub, queryString, thisObject) { - - console.info('- getQueryResultForQueryString queryString:\n' + queryString); - let resultsIterator = await stub.getQueryResult(queryString); - - let results = await thisObject.getAllResults(resultsIterator, false); - - return Buffer.from(JSON.stringify(results)); - } - - /** - * Retrieves the history for a marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The history entries of the specified marble. - */ - async getHistoryForMarble(stub, args, thisObject) { - - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting 1'); - } - let marbleName = args[0]; - console.info('- start getHistoryForMarble: %s\n', marbleName); - - let resultsIterator = await stub.getHistoryForKey(marbleName); - let results = await thisObject.getAllResults(resultsIterator, true); - - return Buffer.from(JSON.stringify(results)); - } -}; - -shim.start(new Chaincode()); diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/package.json b/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/package.json deleted file mode 100644 index c48659644..000000000 --- a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "marbles", - "version": "1.0.0", - "description": "marbles chaincode implemented in node.js", - "engines": { - "node": ">=8.4.0", - "npm": ">=5.3.0" - }, - "scripts": { - "start": "node marbles.js" - }, - "engine-strict": true, - "license": "Apache-2.0", - "dependencies": { - "fabric-shim": "2.4.2" - } -} diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/.gitignore b/packages/caliper-tests-integration/fabric_docker_local_tests/.gitignore index f44eb31f6..3ca6b498b 100644 --- a/packages/caliper-tests-integration/fabric_docker_local_tests/.gitignore +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/.gitignore @@ -3,3 +3,4 @@ report.html node_modules/* package-lock.json fabric-samples +vendor diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/run-with-build.sh b/packages/caliper-tests-integration/fabric_docker_local_tests/run-with-build.sh index 065dc2070..0201acdd8 100755 --- a/packages/caliper-tests-integration/fabric_docker_local_tests/run-with-build.sh +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/run-with-build.sh @@ -30,8 +30,8 @@ pushd ${TEST_NETWORK_DIR} ./network.sh up -s couchdb ./network.sh createChannel -c mychannel ./network.sh createChannel -c yourchannel -./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" -./network.sh deployCC -ccn yourmarbles -c yourchannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn yourmarbles -c yourchannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" popd # back to this dir diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/run-without-build.sh b/packages/caliper-tests-integration/fabric_docker_local_tests/run-without-build.sh index e3b01abe1..3290ca875 100755 --- a/packages/caliper-tests-integration/fabric_docker_local_tests/run-without-build.sh +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/run-without-build.sh @@ -30,8 +30,8 @@ pushd ${TEST_NETWORK_DIR} ./network.sh up -s couchdb ./network.sh createChannel -c mychannel ./network.sh createChannel -c yourchannel -./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" -./network.sh deployCC -ccn yourmarbles -c yourchannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn yourmarbles -c yourchannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" popd # back to this dir diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/go.mod b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/go.mod new file mode 100644 index 000000000..8daf08249 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/go.mod @@ -0,0 +1,8 @@ +module github.com/hyperledger/fabric-samples/chaincode/marbles02/go + +go 1.12 + +require ( + github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85 + github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022 +) diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/go.sum b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/go.sum new file mode 100644 index 000000000..e37d78069 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/go.sum @@ -0,0 +1,58 @@ +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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +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/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85 h1:VEm3tPRTCzq3J/1XpVERh1PbOSnshUVwx2G5s3cLiTw= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85/go.mod h1:HZK6PKLWrvdD/t0oSLiyaRaUM6fZ7qjJuOlb0zrn0mo= +github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022 h1:WzttYAPO5xkQ87ZrxzEhvDZknfarSNu1PZt3NPMTE3Y= +github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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= +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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= \ No newline at end of file diff --git a/packages/caliper-tests-integration/fabric_tests/src/marbles/go/marbles.go b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/marbles_chaincode.go similarity index 76% rename from packages/caliper-tests-integration/fabric_tests/src/marbles/go/marbles.go rename to packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/marbles_chaincode.go index 4e932984b..dfe34b6fb 100644 --- a/packages/caliper-tests-integration/fabric_tests/src/marbles/go/marbles.go +++ b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/marbles_chaincode.go @@ -14,7 +14,7 @@ /* * NOTE: This implementation is a replica of the following: -* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js +* https://github.com/hyperledger/fabric-samples/blob/main/chaincode/marbles02/go/marbles_chaincode.go */ // ====CHAINCODE EXECUTION SAMPLES (CLI) ================== @@ -33,8 +33,11 @@ // peer chaincode query -C myc1 -n marbles -c '{"Args":["getHistoryForMarble","marble1"]}' // Rich Query (Only supported if CouchDB is used as state database): -// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' -// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' + +// Rich Query with Pagination (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesWithPagination","{\"selector\":{\"owner\":\"tom\"}}","3",""]}' // INDEXES TO SUPPORT COUCHDB RICH QUERIES // @@ -73,23 +76,15 @@ // http://127.0.0.1:5984/ // Index for docType, owner. -// Note that docType and owner fields must be prefixed with the "data" wrapper -// -// Index definition for use with Fauxton interface -// {"index":{"fields":["data.docType","data.owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} // -// Example curl command line to define index in the CouchDB channel_chaincode database -// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"data.docType\",\"data.owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index +// Example curl command line to define index in the CouchDB channel_chaincode database. Default user_name/password in couchdb is admin/adminpw +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"docType\",\"owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" user_name:password@hostname:port/myc1_marbles/_index // // Index for docType, owner, size (descending order). -// Note that docType, owner and size fields must be prefixed with the "data" wrapper -// -// Index definition for use with Fauxton interface -// {"index":{"fields":[{"data.size":"desc"},{"data.docType":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeSortDoc", "name":"indexSizeSortDesc","type":"json"} // -// Example curl command line to define index in the CouchDB channel_chaincode database -// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"data.size\":\"desc\"},{\"data.docType\":\"desc\"},{\"data.owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index +// Example curl command line to define index in the CouchDB channel_chaincode database. Default user_name/password in couchdb is admin/adminpw +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"size\":\"desc\"},{\"docType\":\"desc\"},{\"owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" user_name:password@hostname:port/myc1_marbles/_index // Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database): // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":\"marble\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}' @@ -107,8 +102,8 @@ import ( "strings" "time" - "github.com/hyperledger/fabric/core/chaincode/shim" - pb "github.com/hyperledger/fabric/protos/peer" + "github.com/hyperledger/fabric-chaincode-go/shim" + pb "github.com/hyperledger/fabric-protos-go/peer" ) // SimpleChaincode example simple Chaincode implementation @@ -136,19 +131,6 @@ func main() { // Init initializes chaincode // =========================== func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { - var err error - marbles := []marble{ - marble{ObjectType: "marble", Name: "marble1", Color: "Green", Size: 5, Owner: "Alice"}, - marble{ObjectType: "marble", Name: "marble2", Color: "Yellow", Size: 10, Owner: "Bob"}, - marble{ObjectType: "marble", Name: "marble3", Color: "Blue", Size: 15, Owner: "Tom"}, - } - for i, marble := range marbles { - marbleJSONasBytes, _ := json.Marshal(marble) - stub.PutState(marbles[i].Name, marbleJSONasBytes) - if err != nil { - return shim.Error("Failed to put to world state. %s") - } - } return shim.Success(nil) } @@ -177,6 +159,10 @@ func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { return t.getHistoryForMarble(stub, args) } else if function == "getMarblesByRange" { //get marbles based on range query return t.getMarblesByRange(stub, args) + } else if function == "getMarblesByRangeWithPagination" { + return t.getMarblesByRangeWithPagination(stub, args) + } else if function == "queryMarblesWithPagination" { + return t.queryMarblesWithPagination(stub, args) } fmt.Println("invoke did not find func: " + function) //error @@ -374,31 +360,10 @@ func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args } // =========================================================================================== -// getMarblesByRange performs a range query based on the start and end keys provided. - -// Read-only function results are not typically submitted to ordering. If the read-only -// results are submitted to ordering, or if the query is used in an update transaction -// and submitted to ordering, then the committing peers will re-execute to guarantee that -// result sets are stable between endorsement time and commit time. The transaction is -// invalidated by the committing peers if the result set has changed between endorsement -// time and commit time. -// Therefore, range queries are a safe option for performing update transactions based on query results. +// constructQueryResponseFromIterator constructs a JSON array containing query results from +// a given result iterator // =========================================================================================== -func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { - - if len(args) < 2 { - return shim.Error("Incorrect number of arguments. Expecting 2") - } - - startKey := args[0] - endKey := args[1] - - resultsIterator, err := stub.GetStateByRange(startKey, endKey) - if err != nil { - return shim.Error(err.Error()) - } - defer resultsIterator.Close() - +func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) (*bytes.Buffer, error) { // buffer is a JSON array containing QueryResults var buffer bytes.Buffer buffer.WriteString("[") @@ -407,7 +372,7 @@ func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, ar for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { - return shim.Error(err.Error()) + return nil, err } // Add a comma before array members, suppress it for the first array member if bArrayMemberAlreadyWritten == true { @@ -426,6 +391,58 @@ func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, ar } buffer.WriteString("]") + return &buffer, nil +} + +// =========================================================================================== +// addPaginationMetadataToQueryResults adds QueryResponseMetadata, which contains pagination +// info, to the constructed query results +// =========================================================================================== +func addPaginationMetadataToQueryResults(buffer *bytes.Buffer, responseMetadata *pb.QueryResponseMetadata) *bytes.Buffer { + + buffer.WriteString("[{\"ResponseMetadata\":{\"RecordsCount\":") + buffer.WriteString("\"") + buffer.WriteString(fmt.Sprintf("%v", responseMetadata.FetchedRecordsCount)) + buffer.WriteString("\"") + buffer.WriteString(", \"Bookmark\":") + buffer.WriteString("\"") + buffer.WriteString(responseMetadata.Bookmark) + buffer.WriteString("\"}}]") + + return buffer +} + +// =========================================================================================== +// getMarblesByRange performs a range query based on the start and end keys provided. + +// Read-only function results are not typically submitted to ordering. If the read-only +// results are submitted to ordering, or if the query is used in an update transaction +// and submitted to ordering, then the committing peers will re-execute to guarantee that +// result sets are stable between endorsement time and commit time. The transaction is +// invalidated by the committing peers if the result set has changed between endorsement +// time and commit time. +// Therefore, range queries are a safe option for performing update transactions based on query results. +// =========================================================================================== +func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + startKey := args[0] + endKey := args[1] + + resultsIterator, err := stub.GetStateByRange(startKey, endKey) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return shim.Error(err.Error()) + } + fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String()) return shim.Success(buffer.Bytes()) @@ -567,34 +584,122 @@ func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString } defer resultsIterator.Close() - // buffer is a JSON array containing QueryRecords - var buffer bytes.Buffer - buffer.WriteString("[") + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return nil, err + } - bArrayMemberAlreadyWritten := false - for resultsIterator.HasNext() { - queryResponse, err := resultsIterator.Next() - if err != nil { - return nil, err - } - // Add a comma before array members, suppress it for the first array member - if bArrayMemberAlreadyWritten == true { - buffer.WriteString(",") - } - buffer.WriteString("{\"Key\":") - buffer.WriteString("\"") - buffer.WriteString(queryResponse.Key) - buffer.WriteString("\"") + fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) - buffer.WriteString(", \"Record\":") - // Record is a JSON object, so we write as-is - buffer.WriteString(string(queryResponse.Value)) - buffer.WriteString("}") - bArrayMemberAlreadyWritten = true + return buffer.Bytes(), nil +} + +// ====== Pagination ========================================================================= +// Pagination provides a method to retrieve records with a defined pagesize and +// start point (bookmark). An empty string bookmark defines the first "page" of a query +// result. Paginated queries return a bookmark that can be used in +// the next query to retrieve the next page of results. Paginated queries extend +// rich queries and range queries to include a pagesize and bookmark. +// +// Two examples are provided in this example. The first is getMarblesByRangeWithPagination +// which executes a paginated range query. +// The second example is a paginated query for rich ad-hoc queries. +// ========================================================================================= + +// ====== Example: Pagination with Range Query =============================================== +// getMarblesByRangeWithPagination performs a range query based on the start & end key, +// page size and a bookmark. + +// The number of fetched records will be equal to or lesser than the page size. +// Paginated range queries are only valid for read only transactions. +// =========================================================================================== +func (t *SimpleChaincode) getMarblesByRangeWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 4 { + return shim.Error("Incorrect number of arguments. Expecting 4") } - buffer.WriteString("]") - fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) + startKey := args[0] + endKey := args[1] + //return type of ParseInt is int64 + pageSize, err := strconv.ParseInt(args[2], 10, 32) + if err != nil { + return shim.Error(err.Error()) + } + bookmark := args[3] + + resultsIterator, responseMetadata, err := stub.GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return shim.Error(err.Error()) + } + + bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) + + fmt.Printf("- getMarblesByRange queryResult:\n%s\n", bufferWithPaginationInfo.String()) + + return shim.Success(buffer.Bytes()) +} + +// ===== Example: Pagination with Ad hoc Rich Query ======================================================== +// queryMarblesWithPagination uses a query string, page size and a bookmark to perform a query +// for marbles. Query string matching state database syntax is passed in and executed as is. +// The number of fetched records would be equal to or lesser than the specified page size. +// Supports ad hoc queries that can be defined at runtime by the client. +// If this is not desired, follow the queryMarblesForOwner example for parameterized queries. +// Only available on state databases that support rich query (e.g. CouchDB) +// Paginated queries are only valid for read only transactions. +// ========================================================================================= +func (t *SimpleChaincode) queryMarblesWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 + // "queryString" + if len(args) < 3 { + return shim.Error("Incorrect number of arguments. Expecting 3") + } + + queryString := args[0] + //return type of ParseInt is int64 + pageSize, err := strconv.ParseInt(args[1], 10, 32) + if err != nil { + return shim.Error(err.Error()) + } + bookmark := args[2] + + queryResults, err := getQueryResultForQueryStringWithPagination(stub, queryString, int32(pageSize), bookmark) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(queryResults) +} + +// ========================================================================================= +// getQueryResultForQueryStringWithPagination executes the passed in query string with +// pagination info. Result set is built and returned as a byte array containing the JSON results. +// ========================================================================================= +func getQueryResultForQueryStringWithPagination(stub shim.ChaincodeStubInterface, queryString string, pageSize int32, bookmark string) ([]byte, error) { + + fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) + + resultsIterator, responseMetadata, err := stub.GetQueryResultWithPagination(queryString, pageSize, bookmark) + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return nil, err + } + + bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) + + fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", bufferWithPaginationInfo.String()) return buffer.Bytes(), nil } diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/marbles.js b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/marbles.js deleted file mode 100644 index f36228a19..000000000 --- a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/marbles.js +++ /dev/null @@ -1,509 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -/* -* NOTE: This implementation is a derivative work of the following: -* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js -* The modifications include: bug fixes and refactoring for eslint compliance. -*/ - -/* eslint-disable no-console */ - -'use strict'; -const shim = require('fabric-shim'); -const util = require('util'); - -/** - * Marble asset management chaincode written in node.js, implementing {@link ChaincodeInterface}. - * @type {SimpleChaincode} - * @extends {ChaincodeInterface} - */ -let Chaincode = class { - /** - * Called during chaincode instantiate and upgrade. This method can be used - * to initialize asset states. - * @async - * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim - * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub - * encapsulates the APIs between the chaincode implementation and the Fabric peer. - * @return {Promise} Returns a promise of a response indicating the result of the invocation. - */ - async Init(stub) { - const marbles = [ - { - name: 'marble1', - color: 'Green', - size: '5', - owner: 'Alice' - }, - { - name: 'marble2', - color: 'Yellow', - size: '15', - owner: 'Bob' - }, - { - name: 'marble3', - color: 'Blue', - size: '10', - owner: 'Tom' - } - ] - for (let i=0; i < marbles.length; i++) { - marbles[i].docType = 'marble'; - await stub.putState(marbles[i].name, Buffer.from(JSON.stringify(marbles[i]))); - console.info('Added <--> ', marbles[i]); - } - console.info('=========== Instantiated Marbles Chaincode ==========='); - return shim.success(); - } - - /** - * Called throughout the life time of the chaincode to carry out business - * transaction logic and effect the asset states. - * The provided functions are the following: initMarble, delete, transferMarble, readMarble, getMarblesByRange, - * transferMarblesBasedOnColor, queryMarblesByOwner, queryMarbles, getHistoryForMarble. - * @async - * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim - * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub - * encapsulates the APIs between the chaincode implementation and the Fabric peer. - * @return {Promise} Returns a promise of a response indicating the result of the invocation. - */ - async Invoke(stub) { - console.info('Transaction ID: ' + stub.getTxID()); - console.info(util.format('Args: %j', stub.getArgs())); - - let ret = stub.getFunctionAndParameters(); - console.info(ret); - - let method = this[ret.fcn]; - if (!method) { - console.log('no function of name:' + ret.fcn + ' found'); - throw new Error('Received unknown function ' + ret.fcn + ' invocation'); - } - try { - let payload = await method(stub, ret.params, this); - return shim.success(payload); - } catch (err) { - console.log(err); - return shim.error(err); - } - } - - /** - * Creates a new marble with the given attributes. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: marble color. - * Index 2: marble size. Index 3: marble owner. - */ - async initMarble(stub, args) { - if (args.length !== 4) { - throw new Error('Incorrect number of arguments. Expecting 4'); - } - // ==== Input sanitation ==== - console.info('--- start init marble ---'); - if (args[0].length <= 0) { - throw new Error('1st argument must be a non-empty string'); - } - if (args[1].length <= 0) { - throw new Error('2nd argument must be a non-empty string'); - } - if (args[2].length <= 0) { - throw new Error('3rd argument must be a non-empty string'); - } - if (args[3].length <= 0) { - throw new Error('4th argument must be a non-empty string'); - } - let marbleName = args[0]; - let color = args[1].toLowerCase(); - let owner = args[3].toLowerCase(); - let size = parseInt(args[2]); - if (isNaN(size)) { - throw new Error('3rd argument must be a numeric string'); - } - - // ==== Check if marble already exists ==== - let marbleState = await stub.getState(marbleName); - if (marbleState.toString()) { - throw new Error('This marble already exists: ' + marbleName); - } - - // ==== Create marble object and marshal to JSON ==== - let marble = {}; - marble.docType = 'marble'; - marble.name = marbleName; - marble.color = color; - marble.size = size; - marble.owner = owner; - - // === Save marble to state === - await stub.putState(marbleName, Buffer.from(JSON.stringify(marble))); - let indexName = 'color~name'; - let colorNameIndexKey = await stub.createCompositeKey(indexName, [marble.color, marble.name]); - console.info(colorNameIndexKey); - // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. - // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value - await stub.putState(colorNameIndexKey, Buffer.from('\u0000')); - // ==== Marble saved and indexed. Return success ==== - console.info('- end init marble'); - } - - /** - * Retrieves the information about a marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - * @return {Promise} The byte representation of the marble. - */ - async readMarble(stub, args) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting name of the marble to query'); - } - - let name = args[0]; - if (!name) { - throw new Error(' marble name must not be empty'); - } - let marbleAsBytes = await stub.getState(name); //get the marble from chaincode state - if (!marbleAsBytes.toString()) { - let jsonResp = {}; - jsonResp.Error = 'Marble does not exist: ' + name; - throw new Error(JSON.stringify(jsonResp)); - } - console.info('======================================='); - console.log(marbleAsBytes.toString()); - console.info('======================================='); - return marbleAsBytes; - } - - /** - * Deletes the given marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - */ - async delete(stub, args) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting name of the marble to delete'); - } - let marbleName = args[0]; - if (!marbleName) { - throw new Error('marble name must not be empty'); - } - // to maintain the color~name index, we need to read the marble first and get its color - let valAsBytes = await stub.getState(marbleName); //get the marble from chaincode state - let jsonResp = {}; - if (!valAsBytes) { - jsonResp.error = 'marble does not exist: ' + marbleName; - throw new Error(jsonResp); - } - let marbleJSON = {}; - try { - marbleJSON = JSON.parse(valAsBytes.toString()); - } catch (err) { - jsonResp = {}; - jsonResp.error = 'Failed to decode JSON of: ' + marbleName; - throw new Error(jsonResp); - } - - await stub.deleteState(marbleName); //remove the marble from chaincode state - - // delete the index - let indexName = 'color~name'; - let colorNameIndexKey = stub.createCompositeKey(indexName, [marbleJSON.color, marbleJSON.name]); - if (!colorNameIndexKey) { - throw new Error(' Failed to create the createCompositeKey'); - } - // Delete index entry to state. - await stub.deleteState(colorNameIndexKey); - } - - // =========================================================== - // transfer a marble by setting a new owner name on the marble - // =========================================================== - /** - * Transfers the given marble to a new owner. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: the new owner. - */ - async transferMarble(stub, args) { - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting marble name and owner'); - } - - let marbleName = args[0]; - let newOwner = args[1].toLowerCase(); - console.info('- start transferMarble ', marbleName, newOwner); - - let marbleAsBytes = await stub.getState(marbleName); - if (!marbleAsBytes || !marbleAsBytes.toString()) { - throw new Error('marble does not exist'); - } - let marbleToTransfer = {}; - try { - marbleToTransfer = JSON.parse(marbleAsBytes.toString()); //unmarshal - } catch (err) { - let jsonResp = {}; - jsonResp.error = 'Failed to decode JSON of: ' + marbleName; - throw new Error(jsonResp); - } - console.info(marbleToTransfer); - marbleToTransfer.owner = newOwner; //change the owner - - let marbleJSONasBytes = Buffer.from(JSON.stringify(marbleToTransfer)); - await stub.putState(marbleName, marbleJSONasBytes); //rewrite the marble - - console.info('- end transferMarble (success)'); - } - - /** - * Performs a range query based on the start and end keys provided. - * - * Read-only function results are not typically submitted to ordering. If the read-only - * results are submitted to ordering, or if the query is used in an update transaction - * and submitted to ordering, then the committing peers will re-execute to guarantee that - * result sets are stable between endorsement time and commit time. The transaction is - * invalidated by the committing peers if the result set has changed between endorsement - * time and commit time. - * Therefore, range queries are a safe option for performing update transactions based on query results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: start key. Index 1: end key. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The marbles in the given range. - */ - async getMarblesByRange(stub, args, thisObject) { - - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting 2'); - } - - let startKey = args[0]; - let endKey = args[1]; - - let resultsIterator = await stub.getStateByRange(startKey, endKey); - let results = await thisObject.getAllResults(resultsIterator, false); - - return Buffer.from(JSON.stringify(results)); - } - - /** - * Transfers marbles of a given color to a certain new owner. - * - * Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. - * Committing peers will re-execute range queries to guarantee that result sets are stable - * between endorsement time and commit time. The transaction is invalidated by the - * committing peers if the result set has changed between endorsement time and commit time. - * Therefore, range queries are a safe option for performing update transactions based on query results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble color. Index 1: new owner. - * @param {Chaincode} thisObject The chaincode object context. - */ - async transferMarblesBasedOnColor(stub, args, thisObject) { - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting color and owner'); - } - - let color = args[0]; - let newOwner = args[1].toLowerCase(); - console.info('- start transferMarblesBasedOnColor ', color, newOwner); - - // Query the color~name index by color - // This will execute a key range query on all keys starting with 'color' - let coloredMarbleResultsIterator = await stub.getStateByPartialCompositeKey('color~name', [color]); - - let hasNext = true; - // Iterate through result set and for each marble found, transfer to newOwner - while (hasNext) { - let responseRange; - try { - responseRange = await coloredMarbleResultsIterator.next(); - } catch (err) { - hasNext = false; - continue; - } - - if (!responseRange || !responseRange.value || !responseRange.value.key) { - return; - } - console.log(responseRange.value.key); - - // let value = res.value.value.toString('utf8'); - let objectType; - let attributes; - ({ - objectType, - attributes - } = await stub.splitCompositeKey(responseRange.value.key)); - - let returnedColor = attributes[0]; - let returnedMarbleName = attributes[1]; - console.info(util.format('- found a marble from index:%s color:%s name:%s\n', objectType, returnedColor, returnedMarbleName)); - - // Now call the transfer function for the found marble. - // Re-use the same function that is used to transfer individual marbles - await thisObject.transferMarble(stub, [returnedMarbleName, newOwner]); - } - - let responsePayload = util.format('Transferred %s marbles to %s', color, newOwner); - console.info('- end transferMarblesBasedOnColor: ' + responsePayload); - } - - /** - * Queries for marbles based on a passed in owner. - * This is an example of a parameterized query where the query logic is baked into the chaincode, - * and accepting a single query parameter (owner). - * Only available on state databases that support rich query (e.g. CouchDB) - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble owner. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The marbles of the specified owner. - */ - async queryMarblesByOwner(stub, args, thisObject) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting owner name.'); - } - - let owner = args[0].toLowerCase(); - let queryString = {}; - queryString.selector = {}; - queryString.selector.docType = 'marble'; - queryString.selector.owner = owner; - return await thisObject.getQueryResultForQueryString(stub, JSON.stringify(queryString), thisObject); //shim.success(queryResults); - } - - /** - * Uses a query string to perform a query for marbles. - * Query string matching state database syntax is passed in and executed as is. - * Supports ad hoc queries that can be defined at runtime by the client. - * If this is not desired, follow the queryMarblesForOwner example for parameterized queries. - * Only available on state databases that support rich query (e.g. CouchDB) - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: query string. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The results of the specified query. - */ - async queryMarbles(stub, args, thisObject) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting queryString'); - } - let queryString = args[0]; - if (!queryString) { - throw new Error('queryString must not be empty'); - } - - return await thisObject.getQueryResultForQueryString(stub, queryString, thisObject); - } - - /** - * Gets the results of a specified iterator. - * @async - * @param {Object} iterator The iterator to use. - * @param {Boolean} isHistory Specifies whether the iterator returns history entries or not. - * @return {Promise} The array of results in JSON format. - */ - async getAllResults(iterator, isHistory) { - let allResults = []; - let hasNext = true; - while (hasNext) { - let res; - try { - res = await iterator.next(); - } catch (err) { - hasNext = false; - continue; - } - - if (res.value && res.value.value.toString()) { - let jsonRes = {}; - console.log(res.value.value.toString('utf8')); - - if (isHistory && isHistory === true) { - jsonRes.TxId = res.value.tx_id; - jsonRes.Timestamp = res.value.timestamp; - jsonRes.IsDelete = res.value.is_delete.toString(); - try { - jsonRes.Value = JSON.parse(res.value.value.toString('utf8')); - } catch (err) { - console.log(err); - jsonRes.Value = res.value.value.toString('utf8'); - } - } else { - jsonRes.Key = res.value.key; - try { - jsonRes.Record = JSON.parse(res.value.value.toString('utf8')); - } catch (err) { - console.log(err); - jsonRes.Record = res.value.value.toString('utf8'); - } - } - allResults.push(jsonRes); - } - if (res.done) { - console.log('end of data'); - await iterator.close(); - console.info(allResults); - return allResults; - } - } - } - - /** - * Executes the provided query string. - * Result set is built and returned as a byte array containing the JSON results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String} queryString The query string to execute. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The results of the specified query. - */ - async getQueryResultForQueryString(stub, queryString, thisObject) { - - console.info('- getQueryResultForQueryString queryString:\n' + queryString); - let resultsIterator = await stub.getQueryResult(queryString); - - let results = await thisObject.getAllResults(resultsIterator, false); - - return Buffer.from(JSON.stringify(results)); - } - - /** - * Retrieves the history for a marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The history entries of the specified marble. - */ - async getHistoryForMarble(stub, args, thisObject) { - - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting 1'); - } - let marbleName = args[0]; - console.info('- start getHistoryForMarble: %s\n', marbleName); - - let resultsIterator = await stub.getHistoryForKey(marbleName); - let results = await thisObject.getAllResults(resultsIterator, true); - - return Buffer.from(JSON.stringify(results)); - } -}; - -shim.start(new Chaincode()); diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json deleted file mode 100644 index 305f09044..000000000 --- a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json +++ /dev/null @@ -1 +0,0 @@ -{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/package.json b/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/package.json deleted file mode 100644 index c48659644..000000000 --- a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/node/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "marbles", - "version": "1.0.0", - "description": "marbles chaincode implemented in node.js", - "engines": { - "node": ">=8.4.0", - "npm": ">=5.3.0" - }, - "scripts": { - "start": "node marbles.js" - }, - "engine-strict": true, - "license": "Apache-2.0", - "dependencies": { - "fabric-shim": "2.4.2" - } -} diff --git a/packages/caliper-tests-integration/fabric_tests/.gitignore b/packages/caliper-tests-integration/fabric_tests/.gitignore index 73806d919..aaadf2629 100644 --- a/packages/caliper-tests-integration/fabric_tests/.gitignore +++ b/packages/caliper-tests-integration/fabric_tests/.gitignore @@ -1,3 +1,4 @@ **/*.log report.html fabric-samples +vendor diff --git a/packages/caliper-tests-integration/fabric_tests/run.sh b/packages/caliper-tests-integration/fabric_tests/run.sh index c38179187..da2c140a7 100755 --- a/packages/caliper-tests-integration/fabric_tests/run.sh +++ b/packages/caliper-tests-integration/fabric_tests/run.sh @@ -42,20 +42,24 @@ fi # change default settings (add config paths too) export CALIPER_PROJECTCONFIG=../caliper.yaml +TEST_NETWORK_DIR=${DIR}/fabric-samples/test-network + dispose () { docker ps -a ${CALL_METHOD} launch manager --caliper-workspace phase6 --caliper-flow-only-end -} -TEST_NETWORK_DIR=${DIR}/fabric-samples/test-network + pushd ${TEST_NETWORK_DIR} + ./network.sh down + popd +} # Create Fabric network pushd ${TEST_NETWORK_DIR} ./network.sh up -s couchdb ./network.sh createChannel -c mychannel ./network.sh createChannel -c yourchannel -./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" -./network.sh deployCC -ccn yourmarbles -c yourchannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn yourmarbles -c yourchannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" popd # PHASE 1: just starting the network diff --git a/packages/caliper-tests-integration/fabric_tests/src/marbles/go/go.mod b/packages/caliper-tests-integration/fabric_tests/src/marbles/go/go.mod new file mode 100644 index 000000000..8daf08249 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_tests/src/marbles/go/go.mod @@ -0,0 +1,8 @@ +module github.com/hyperledger/fabric-samples/chaincode/marbles02/go + +go 1.12 + +require ( + github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85 + github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022 +) diff --git a/packages/caliper-tests-integration/fabric_tests/src/marbles/go/go.sum b/packages/caliper-tests-integration/fabric_tests/src/marbles/go/go.sum new file mode 100644 index 000000000..e37d78069 --- /dev/null +++ b/packages/caliper-tests-integration/fabric_tests/src/marbles/go/go.sum @@ -0,0 +1,58 @@ +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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +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/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85 h1:VEm3tPRTCzq3J/1XpVERh1PbOSnshUVwx2G5s3cLiTw= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85/go.mod h1:HZK6PKLWrvdD/t0oSLiyaRaUM6fZ7qjJuOlb0zrn0mo= +github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022 h1:WzttYAPO5xkQ87ZrxzEhvDZknfarSNu1PZt3NPMTE3Y= +github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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= +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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= \ No newline at end of file diff --git a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/marbles.go b/packages/caliper-tests-integration/fabric_tests/src/marbles/go/marbles_chaincode.go similarity index 76% rename from packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/marbles.go rename to packages/caliper-tests-integration/fabric_tests/src/marbles/go/marbles_chaincode.go index 4e932984b..dfe34b6fb 100644 --- a/packages/caliper-tests-integration/fabric_docker_local_tests/src/marbles/go/marbles.go +++ b/packages/caliper-tests-integration/fabric_tests/src/marbles/go/marbles_chaincode.go @@ -14,7 +14,7 @@ /* * NOTE: This implementation is a replica of the following: -* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js +* https://github.com/hyperledger/fabric-samples/blob/main/chaincode/marbles02/go/marbles_chaincode.go */ // ====CHAINCODE EXECUTION SAMPLES (CLI) ================== @@ -33,8 +33,11 @@ // peer chaincode query -C myc1 -n marbles -c '{"Args":["getHistoryForMarble","marble1"]}' // Rich Query (Only supported if CouchDB is used as state database): -// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' -// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' + +// Rich Query with Pagination (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesWithPagination","{\"selector\":{\"owner\":\"tom\"}}","3",""]}' // INDEXES TO SUPPORT COUCHDB RICH QUERIES // @@ -73,23 +76,15 @@ // http://127.0.0.1:5984/ // Index for docType, owner. -// Note that docType and owner fields must be prefixed with the "data" wrapper -// -// Index definition for use with Fauxton interface -// {"index":{"fields":["data.docType","data.owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} // -// Example curl command line to define index in the CouchDB channel_chaincode database -// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"data.docType\",\"data.owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index +// Example curl command line to define index in the CouchDB channel_chaincode database. Default user_name/password in couchdb is admin/adminpw +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"docType\",\"owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" user_name:password@hostname:port/myc1_marbles/_index // // Index for docType, owner, size (descending order). -// Note that docType, owner and size fields must be prefixed with the "data" wrapper -// -// Index definition for use with Fauxton interface -// {"index":{"fields":[{"data.size":"desc"},{"data.docType":"desc"},{"data.owner":"desc"}]},"ddoc":"indexSizeSortDoc", "name":"indexSizeSortDesc","type":"json"} // -// Example curl command line to define index in the CouchDB channel_chaincode database -// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"data.size\":\"desc\"},{\"data.docType\":\"desc\"},{\"data.owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" http://hostname:port/myc1_marbles/_index +// Example curl command line to define index in the CouchDB channel_chaincode database. Default user_name/password in couchdb is admin/adminpw +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"size\":\"desc\"},{\"docType\":\"desc\"},{\"owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" user_name:password@hostname:port/myc1_marbles/_index // Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database): // peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":\"marble\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}' @@ -107,8 +102,8 @@ import ( "strings" "time" - "github.com/hyperledger/fabric/core/chaincode/shim" - pb "github.com/hyperledger/fabric/protos/peer" + "github.com/hyperledger/fabric-chaincode-go/shim" + pb "github.com/hyperledger/fabric-protos-go/peer" ) // SimpleChaincode example simple Chaincode implementation @@ -136,19 +131,6 @@ func main() { // Init initializes chaincode // =========================== func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { - var err error - marbles := []marble{ - marble{ObjectType: "marble", Name: "marble1", Color: "Green", Size: 5, Owner: "Alice"}, - marble{ObjectType: "marble", Name: "marble2", Color: "Yellow", Size: 10, Owner: "Bob"}, - marble{ObjectType: "marble", Name: "marble3", Color: "Blue", Size: 15, Owner: "Tom"}, - } - for i, marble := range marbles { - marbleJSONasBytes, _ := json.Marshal(marble) - stub.PutState(marbles[i].Name, marbleJSONasBytes) - if err != nil { - return shim.Error("Failed to put to world state. %s") - } - } return shim.Success(nil) } @@ -177,6 +159,10 @@ func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { return t.getHistoryForMarble(stub, args) } else if function == "getMarblesByRange" { //get marbles based on range query return t.getMarblesByRange(stub, args) + } else if function == "getMarblesByRangeWithPagination" { + return t.getMarblesByRangeWithPagination(stub, args) + } else if function == "queryMarblesWithPagination" { + return t.queryMarblesWithPagination(stub, args) } fmt.Println("invoke did not find func: " + function) //error @@ -374,31 +360,10 @@ func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args } // =========================================================================================== -// getMarblesByRange performs a range query based on the start and end keys provided. - -// Read-only function results are not typically submitted to ordering. If the read-only -// results are submitted to ordering, or if the query is used in an update transaction -// and submitted to ordering, then the committing peers will re-execute to guarantee that -// result sets are stable between endorsement time and commit time. The transaction is -// invalidated by the committing peers if the result set has changed between endorsement -// time and commit time. -// Therefore, range queries are a safe option for performing update transactions based on query results. +// constructQueryResponseFromIterator constructs a JSON array containing query results from +// a given result iterator // =========================================================================================== -func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { - - if len(args) < 2 { - return shim.Error("Incorrect number of arguments. Expecting 2") - } - - startKey := args[0] - endKey := args[1] - - resultsIterator, err := stub.GetStateByRange(startKey, endKey) - if err != nil { - return shim.Error(err.Error()) - } - defer resultsIterator.Close() - +func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) (*bytes.Buffer, error) { // buffer is a JSON array containing QueryResults var buffer bytes.Buffer buffer.WriteString("[") @@ -407,7 +372,7 @@ func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, ar for resultsIterator.HasNext() { queryResponse, err := resultsIterator.Next() if err != nil { - return shim.Error(err.Error()) + return nil, err } // Add a comma before array members, suppress it for the first array member if bArrayMemberAlreadyWritten == true { @@ -426,6 +391,58 @@ func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, ar } buffer.WriteString("]") + return &buffer, nil +} + +// =========================================================================================== +// addPaginationMetadataToQueryResults adds QueryResponseMetadata, which contains pagination +// info, to the constructed query results +// =========================================================================================== +func addPaginationMetadataToQueryResults(buffer *bytes.Buffer, responseMetadata *pb.QueryResponseMetadata) *bytes.Buffer { + + buffer.WriteString("[{\"ResponseMetadata\":{\"RecordsCount\":") + buffer.WriteString("\"") + buffer.WriteString(fmt.Sprintf("%v", responseMetadata.FetchedRecordsCount)) + buffer.WriteString("\"") + buffer.WriteString(", \"Bookmark\":") + buffer.WriteString("\"") + buffer.WriteString(responseMetadata.Bookmark) + buffer.WriteString("\"}}]") + + return buffer +} + +// =========================================================================================== +// getMarblesByRange performs a range query based on the start and end keys provided. + +// Read-only function results are not typically submitted to ordering. If the read-only +// results are submitted to ordering, or if the query is used in an update transaction +// and submitted to ordering, then the committing peers will re-execute to guarantee that +// result sets are stable between endorsement time and commit time. The transaction is +// invalidated by the committing peers if the result set has changed between endorsement +// time and commit time. +// Therefore, range queries are a safe option for performing update transactions based on query results. +// =========================================================================================== +func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + startKey := args[0] + endKey := args[1] + + resultsIterator, err := stub.GetStateByRange(startKey, endKey) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return shim.Error(err.Error()) + } + fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String()) return shim.Success(buffer.Bytes()) @@ -567,34 +584,122 @@ func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString } defer resultsIterator.Close() - // buffer is a JSON array containing QueryRecords - var buffer bytes.Buffer - buffer.WriteString("[") + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return nil, err + } - bArrayMemberAlreadyWritten := false - for resultsIterator.HasNext() { - queryResponse, err := resultsIterator.Next() - if err != nil { - return nil, err - } - // Add a comma before array members, suppress it for the first array member - if bArrayMemberAlreadyWritten == true { - buffer.WriteString(",") - } - buffer.WriteString("{\"Key\":") - buffer.WriteString("\"") - buffer.WriteString(queryResponse.Key) - buffer.WriteString("\"") + fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) - buffer.WriteString(", \"Record\":") - // Record is a JSON object, so we write as-is - buffer.WriteString(string(queryResponse.Value)) - buffer.WriteString("}") - bArrayMemberAlreadyWritten = true + return buffer.Bytes(), nil +} + +// ====== Pagination ========================================================================= +// Pagination provides a method to retrieve records with a defined pagesize and +// start point (bookmark). An empty string bookmark defines the first "page" of a query +// result. Paginated queries return a bookmark that can be used in +// the next query to retrieve the next page of results. Paginated queries extend +// rich queries and range queries to include a pagesize and bookmark. +// +// Two examples are provided in this example. The first is getMarblesByRangeWithPagination +// which executes a paginated range query. +// The second example is a paginated query for rich ad-hoc queries. +// ========================================================================================= + +// ====== Example: Pagination with Range Query =============================================== +// getMarblesByRangeWithPagination performs a range query based on the start & end key, +// page size and a bookmark. + +// The number of fetched records will be equal to or lesser than the page size. +// Paginated range queries are only valid for read only transactions. +// =========================================================================================== +func (t *SimpleChaincode) getMarblesByRangeWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 4 { + return shim.Error("Incorrect number of arguments. Expecting 4") } - buffer.WriteString("]") - fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) + startKey := args[0] + endKey := args[1] + //return type of ParseInt is int64 + pageSize, err := strconv.ParseInt(args[2], 10, 32) + if err != nil { + return shim.Error(err.Error()) + } + bookmark := args[3] + + resultsIterator, responseMetadata, err := stub.GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return shim.Error(err.Error()) + } + + bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) + + fmt.Printf("- getMarblesByRange queryResult:\n%s\n", bufferWithPaginationInfo.String()) + + return shim.Success(buffer.Bytes()) +} + +// ===== Example: Pagination with Ad hoc Rich Query ======================================================== +// queryMarblesWithPagination uses a query string, page size and a bookmark to perform a query +// for marbles. Query string matching state database syntax is passed in and executed as is. +// The number of fetched records would be equal to or lesser than the specified page size. +// Supports ad hoc queries that can be defined at runtime by the client. +// If this is not desired, follow the queryMarblesForOwner example for parameterized queries. +// Only available on state databases that support rich query (e.g. CouchDB) +// Paginated queries are only valid for read only transactions. +// ========================================================================================= +func (t *SimpleChaincode) queryMarblesWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 + // "queryString" + if len(args) < 3 { + return shim.Error("Incorrect number of arguments. Expecting 3") + } + + queryString := args[0] + //return type of ParseInt is int64 + pageSize, err := strconv.ParseInt(args[1], 10, 32) + if err != nil { + return shim.Error(err.Error()) + } + bookmark := args[2] + + queryResults, err := getQueryResultForQueryStringWithPagination(stub, queryString, int32(pageSize), bookmark) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(queryResults) +} + +// ========================================================================================= +// getQueryResultForQueryStringWithPagination executes the passed in query string with +// pagination info. Result set is built and returned as a byte array containing the JSON results. +// ========================================================================================= +func getQueryResultForQueryStringWithPagination(stub shim.ChaincodeStubInterface, queryString string, pageSize int32, bookmark string) ([]byte, error) { + + fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) + + resultsIterator, responseMetadata, err := stub.GetQueryResultWithPagination(queryString, pageSize, bookmark) + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return nil, err + } + + bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) + + fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", bufferWithPaginationInfo.String()) return buffer.Bytes(), nil } diff --git a/packages/caliper-tests-integration/fabric_tests/src/marbles/node/marbles.js b/packages/caliper-tests-integration/fabric_tests/src/marbles/node/marbles.js deleted file mode 100644 index f36228a19..000000000 --- a/packages/caliper-tests-integration/fabric_tests/src/marbles/node/marbles.js +++ /dev/null @@ -1,509 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -/* -* NOTE: This implementation is a derivative work of the following: -* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js -* The modifications include: bug fixes and refactoring for eslint compliance. -*/ - -/* eslint-disable no-console */ - -'use strict'; -const shim = require('fabric-shim'); -const util = require('util'); - -/** - * Marble asset management chaincode written in node.js, implementing {@link ChaincodeInterface}. - * @type {SimpleChaincode} - * @extends {ChaincodeInterface} - */ -let Chaincode = class { - /** - * Called during chaincode instantiate and upgrade. This method can be used - * to initialize asset states. - * @async - * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim - * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub - * encapsulates the APIs between the chaincode implementation and the Fabric peer. - * @return {Promise} Returns a promise of a response indicating the result of the invocation. - */ - async Init(stub) { - const marbles = [ - { - name: 'marble1', - color: 'Green', - size: '5', - owner: 'Alice' - }, - { - name: 'marble2', - color: 'Yellow', - size: '15', - owner: 'Bob' - }, - { - name: 'marble3', - color: 'Blue', - size: '10', - owner: 'Tom' - } - ] - for (let i=0; i < marbles.length; i++) { - marbles[i].docType = 'marble'; - await stub.putState(marbles[i].name, Buffer.from(JSON.stringify(marbles[i]))); - console.info('Added <--> ', marbles[i]); - } - console.info('=========== Instantiated Marbles Chaincode ==========='); - return shim.success(); - } - - /** - * Called throughout the life time of the chaincode to carry out business - * transaction logic and effect the asset states. - * The provided functions are the following: initMarble, delete, transferMarble, readMarble, getMarblesByRange, - * transferMarblesBasedOnColor, queryMarblesByOwner, queryMarbles, getHistoryForMarble. - * @async - * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim - * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub - * encapsulates the APIs between the chaincode implementation and the Fabric peer. - * @return {Promise} Returns a promise of a response indicating the result of the invocation. - */ - async Invoke(stub) { - console.info('Transaction ID: ' + stub.getTxID()); - console.info(util.format('Args: %j', stub.getArgs())); - - let ret = stub.getFunctionAndParameters(); - console.info(ret); - - let method = this[ret.fcn]; - if (!method) { - console.log('no function of name:' + ret.fcn + ' found'); - throw new Error('Received unknown function ' + ret.fcn + ' invocation'); - } - try { - let payload = await method(stub, ret.params, this); - return shim.success(payload); - } catch (err) { - console.log(err); - return shim.error(err); - } - } - - /** - * Creates a new marble with the given attributes. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: marble color. - * Index 2: marble size. Index 3: marble owner. - */ - async initMarble(stub, args) { - if (args.length !== 4) { - throw new Error('Incorrect number of arguments. Expecting 4'); - } - // ==== Input sanitation ==== - console.info('--- start init marble ---'); - if (args[0].length <= 0) { - throw new Error('1st argument must be a non-empty string'); - } - if (args[1].length <= 0) { - throw new Error('2nd argument must be a non-empty string'); - } - if (args[2].length <= 0) { - throw new Error('3rd argument must be a non-empty string'); - } - if (args[3].length <= 0) { - throw new Error('4th argument must be a non-empty string'); - } - let marbleName = args[0]; - let color = args[1].toLowerCase(); - let owner = args[3].toLowerCase(); - let size = parseInt(args[2]); - if (isNaN(size)) { - throw new Error('3rd argument must be a numeric string'); - } - - // ==== Check if marble already exists ==== - let marbleState = await stub.getState(marbleName); - if (marbleState.toString()) { - throw new Error('This marble already exists: ' + marbleName); - } - - // ==== Create marble object and marshal to JSON ==== - let marble = {}; - marble.docType = 'marble'; - marble.name = marbleName; - marble.color = color; - marble.size = size; - marble.owner = owner; - - // === Save marble to state === - await stub.putState(marbleName, Buffer.from(JSON.stringify(marble))); - let indexName = 'color~name'; - let colorNameIndexKey = await stub.createCompositeKey(indexName, [marble.color, marble.name]); - console.info(colorNameIndexKey); - // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. - // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value - await stub.putState(colorNameIndexKey, Buffer.from('\u0000')); - // ==== Marble saved and indexed. Return success ==== - console.info('- end init marble'); - } - - /** - * Retrieves the information about a marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - * @return {Promise} The byte representation of the marble. - */ - async readMarble(stub, args) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting name of the marble to query'); - } - - let name = args[0]; - if (!name) { - throw new Error(' marble name must not be empty'); - } - let marbleAsBytes = await stub.getState(name); //get the marble from chaincode state - if (!marbleAsBytes.toString()) { - let jsonResp = {}; - jsonResp.Error = 'Marble does not exist: ' + name; - throw new Error(JSON.stringify(jsonResp)); - } - console.info('======================================='); - console.log(marbleAsBytes.toString()); - console.info('======================================='); - return marbleAsBytes; - } - - /** - * Deletes the given marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - */ - async delete(stub, args) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting name of the marble to delete'); - } - let marbleName = args[0]; - if (!marbleName) { - throw new Error('marble name must not be empty'); - } - // to maintain the color~name index, we need to read the marble first and get its color - let valAsBytes = await stub.getState(marbleName); //get the marble from chaincode state - let jsonResp = {}; - if (!valAsBytes) { - jsonResp.error = 'marble does not exist: ' + marbleName; - throw new Error(jsonResp); - } - let marbleJSON = {}; - try { - marbleJSON = JSON.parse(valAsBytes.toString()); - } catch (err) { - jsonResp = {}; - jsonResp.error = 'Failed to decode JSON of: ' + marbleName; - throw new Error(jsonResp); - } - - await stub.deleteState(marbleName); //remove the marble from chaincode state - - // delete the index - let indexName = 'color~name'; - let colorNameIndexKey = stub.createCompositeKey(indexName, [marbleJSON.color, marbleJSON.name]); - if (!colorNameIndexKey) { - throw new Error(' Failed to create the createCompositeKey'); - } - // Delete index entry to state. - await stub.deleteState(colorNameIndexKey); - } - - // =========================================================== - // transfer a marble by setting a new owner name on the marble - // =========================================================== - /** - * Transfers the given marble to a new owner. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: the new owner. - */ - async transferMarble(stub, args) { - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting marble name and owner'); - } - - let marbleName = args[0]; - let newOwner = args[1].toLowerCase(); - console.info('- start transferMarble ', marbleName, newOwner); - - let marbleAsBytes = await stub.getState(marbleName); - if (!marbleAsBytes || !marbleAsBytes.toString()) { - throw new Error('marble does not exist'); - } - let marbleToTransfer = {}; - try { - marbleToTransfer = JSON.parse(marbleAsBytes.toString()); //unmarshal - } catch (err) { - let jsonResp = {}; - jsonResp.error = 'Failed to decode JSON of: ' + marbleName; - throw new Error(jsonResp); - } - console.info(marbleToTransfer); - marbleToTransfer.owner = newOwner; //change the owner - - let marbleJSONasBytes = Buffer.from(JSON.stringify(marbleToTransfer)); - await stub.putState(marbleName, marbleJSONasBytes); //rewrite the marble - - console.info('- end transferMarble (success)'); - } - - /** - * Performs a range query based on the start and end keys provided. - * - * Read-only function results are not typically submitted to ordering. If the read-only - * results are submitted to ordering, or if the query is used in an update transaction - * and submitted to ordering, then the committing peers will re-execute to guarantee that - * result sets are stable between endorsement time and commit time. The transaction is - * invalidated by the committing peers if the result set has changed between endorsement - * time and commit time. - * Therefore, range queries are a safe option for performing update transactions based on query results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: start key. Index 1: end key. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The marbles in the given range. - */ - async getMarblesByRange(stub, args, thisObject) { - - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting 2'); - } - - let startKey = args[0]; - let endKey = args[1]; - - let resultsIterator = await stub.getStateByRange(startKey, endKey); - let results = await thisObject.getAllResults(resultsIterator, false); - - return Buffer.from(JSON.stringify(results)); - } - - /** - * Transfers marbles of a given color to a certain new owner. - * - * Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. - * Committing peers will re-execute range queries to guarantee that result sets are stable - * between endorsement time and commit time. The transaction is invalidated by the - * committing peers if the result set has changed between endorsement time and commit time. - * Therefore, range queries are a safe option for performing update transactions based on query results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble color. Index 1: new owner. - * @param {Chaincode} thisObject The chaincode object context. - */ - async transferMarblesBasedOnColor(stub, args, thisObject) { - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting color and owner'); - } - - let color = args[0]; - let newOwner = args[1].toLowerCase(); - console.info('- start transferMarblesBasedOnColor ', color, newOwner); - - // Query the color~name index by color - // This will execute a key range query on all keys starting with 'color' - let coloredMarbleResultsIterator = await stub.getStateByPartialCompositeKey('color~name', [color]); - - let hasNext = true; - // Iterate through result set and for each marble found, transfer to newOwner - while (hasNext) { - let responseRange; - try { - responseRange = await coloredMarbleResultsIterator.next(); - } catch (err) { - hasNext = false; - continue; - } - - if (!responseRange || !responseRange.value || !responseRange.value.key) { - return; - } - console.log(responseRange.value.key); - - // let value = res.value.value.toString('utf8'); - let objectType; - let attributes; - ({ - objectType, - attributes - } = await stub.splitCompositeKey(responseRange.value.key)); - - let returnedColor = attributes[0]; - let returnedMarbleName = attributes[1]; - console.info(util.format('- found a marble from index:%s color:%s name:%s\n', objectType, returnedColor, returnedMarbleName)); - - // Now call the transfer function for the found marble. - // Re-use the same function that is used to transfer individual marbles - await thisObject.transferMarble(stub, [returnedMarbleName, newOwner]); - } - - let responsePayload = util.format('Transferred %s marbles to %s', color, newOwner); - console.info('- end transferMarblesBasedOnColor: ' + responsePayload); - } - - /** - * Queries for marbles based on a passed in owner. - * This is an example of a parameterized query where the query logic is baked into the chaincode, - * and accepting a single query parameter (owner). - * Only available on state databases that support rich query (e.g. CouchDB) - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble owner. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The marbles of the specified owner. - */ - async queryMarblesByOwner(stub, args, thisObject) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting owner name.'); - } - - let owner = args[0].toLowerCase(); - let queryString = {}; - queryString.selector = {}; - queryString.selector.docType = 'marble'; - queryString.selector.owner = owner; - return await thisObject.getQueryResultForQueryString(stub, JSON.stringify(queryString), thisObject); //shim.success(queryResults); - } - - /** - * Uses a query string to perform a query for marbles. - * Query string matching state database syntax is passed in and executed as is. - * Supports ad hoc queries that can be defined at runtime by the client. - * If this is not desired, follow the queryMarblesForOwner example for parameterized queries. - * Only available on state databases that support rich query (e.g. CouchDB) - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: query string. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The results of the specified query. - */ - async queryMarbles(stub, args, thisObject) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting queryString'); - } - let queryString = args[0]; - if (!queryString) { - throw new Error('queryString must not be empty'); - } - - return await thisObject.getQueryResultForQueryString(stub, queryString, thisObject); - } - - /** - * Gets the results of a specified iterator. - * @async - * @param {Object} iterator The iterator to use. - * @param {Boolean} isHistory Specifies whether the iterator returns history entries or not. - * @return {Promise} The array of results in JSON format. - */ - async getAllResults(iterator, isHistory) { - let allResults = []; - let hasNext = true; - while (hasNext) { - let res; - try { - res = await iterator.next(); - } catch (err) { - hasNext = false; - continue; - } - - if (res.value && res.value.value.toString()) { - let jsonRes = {}; - console.log(res.value.value.toString('utf8')); - - if (isHistory && isHistory === true) { - jsonRes.TxId = res.value.tx_id; - jsonRes.Timestamp = res.value.timestamp; - jsonRes.IsDelete = res.value.is_delete.toString(); - try { - jsonRes.Value = JSON.parse(res.value.value.toString('utf8')); - } catch (err) { - console.log(err); - jsonRes.Value = res.value.value.toString('utf8'); - } - } else { - jsonRes.Key = res.value.key; - try { - jsonRes.Record = JSON.parse(res.value.value.toString('utf8')); - } catch (err) { - console.log(err); - jsonRes.Record = res.value.value.toString('utf8'); - } - } - allResults.push(jsonRes); - } - if (res.done) { - console.log('end of data'); - await iterator.close(); - console.info(allResults); - return allResults; - } - } - } - - /** - * Executes the provided query string. - * Result set is built and returned as a byte array containing the JSON results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String} queryString The query string to execute. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The results of the specified query. - */ - async getQueryResultForQueryString(stub, queryString, thisObject) { - - console.info('- getQueryResultForQueryString queryString:\n' + queryString); - let resultsIterator = await stub.getQueryResult(queryString); - - let results = await thisObject.getAllResults(resultsIterator, false); - - return Buffer.from(JSON.stringify(results)); - } - - /** - * Retrieves the history for a marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The history entries of the specified marble. - */ - async getHistoryForMarble(stub, args, thisObject) { - - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting 1'); - } - let marbleName = args[0]; - console.info('- start getHistoryForMarble: %s\n', marbleName); - - let resultsIterator = await stub.getHistoryForKey(marbleName); - let results = await thisObject.getAllResults(resultsIterator, true); - - return Buffer.from(JSON.stringify(results)); - } -}; - -shim.start(new Chaincode()); diff --git a/packages/caliper-tests-integration/fabric_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json b/packages/caliper-tests-integration/fabric_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json deleted file mode 100644 index 305f09044..000000000 --- a/packages/caliper-tests-integration/fabric_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json +++ /dev/null @@ -1 +0,0 @@ -{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} diff --git a/packages/caliper-tests-integration/fabric_tests/src/marbles/node/package.json b/packages/caliper-tests-integration/fabric_tests/src/marbles/node/package.json deleted file mode 100644 index c48659644..000000000 --- a/packages/caliper-tests-integration/fabric_tests/src/marbles/node/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "marbles", - "version": "1.0.0", - "description": "marbles chaincode implemented in node.js", - "engines": { - "node": ">=8.4.0", - "npm": ">=5.3.0" - }, - "scripts": { - "start": "node marbles.js" - }, - "engine-strict": true, - "license": "Apache-2.0", - "dependencies": { - "fabric-shim": "2.4.2" - } -} diff --git a/packages/caliper-tests-integration/generator_tests/fabric/.gitignore b/packages/caliper-tests-integration/generator_tests/fabric/.gitignore index 48cfc5f68..3b3a8de83 100644 --- a/packages/caliper-tests-integration/generator_tests/fabric/.gitignore +++ b/packages/caliper-tests-integration/generator_tests/fabric/.gitignore @@ -2,3 +2,5 @@ report.html **/myWorkspace/benchmarks + +vendor diff --git a/packages/caliper-tests-integration/generator_tests/fabric/run.sh b/packages/caliper-tests-integration/generator_tests/fabric/run.sh index bd0ce6cf5..dee188e5c 100755 --- a/packages/caliper-tests-integration/generator_tests/fabric/run.sh +++ b/packages/caliper-tests-integration/generator_tests/fabric/run.sh @@ -62,7 +62,7 @@ fi pushd ${TEST_NETWORK_DIR} ./network.sh up -s couchdb ./network.sh createChannel -c mychannel -./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/node -ccl javascript -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" +./network.sh deployCC -ccn mymarbles -c mychannel -ccp ${DIR}/src/marbles/go -ccl go -ccv v0 -ccep "OR('Org1MSP.member','Org2MSP.member')" popd cd ${DIR} diff --git a/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/go.mod b/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/go.mod new file mode 100644 index 000000000..8daf08249 --- /dev/null +++ b/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/go.mod @@ -0,0 +1,8 @@ +module github.com/hyperledger/fabric-samples/chaincode/marbles02/go + +go 1.12 + +require ( + github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85 + github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022 +) diff --git a/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/go.sum b/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/go.sum new file mode 100644 index 000000000..e37d78069 --- /dev/null +++ b/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/go.sum @@ -0,0 +1,58 @@ +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/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +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/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +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/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85 h1:VEm3tPRTCzq3J/1XpVERh1PbOSnshUVwx2G5s3cLiTw= +github.com/hyperledger/fabric-chaincode-go v0.0.0-20190823162523-04390e015b85/go.mod h1:HZK6PKLWrvdD/t0oSLiyaRaUM6fZ7qjJuOlb0zrn0mo= +github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022 h1:WzttYAPO5xkQ87ZrxzEhvDZknfarSNu1PZt3NPMTE3Y= +github.com/hyperledger/fabric-protos-go v0.0.0-20190821214336-621b908d5022/go.mod h1:xVYTjK4DtZRBxZ2D9aE4y6AbLaPwue2o/criQyQbVD0= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +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 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +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-20190522155817-f3200d17e092 h1:4QSRKanuywn15aTZvI/mIDEgPQpswuFndXpOj3rKEco= +golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +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-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +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 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/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= +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 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= \ No newline at end of file diff --git a/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/marbles_chaincode.go b/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/marbles_chaincode.go new file mode 100644 index 000000000..dfe34b6fb --- /dev/null +++ b/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/marbles_chaincode.go @@ -0,0 +1,770 @@ +/* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +/* +* NOTE: This implementation is a replica of the following: +* https://github.com/hyperledger/fabric-samples/blob/main/chaincode/marbles02/go/marbles_chaincode.go +*/ + +// ====CHAINCODE EXECUTION SAMPLES (CLI) ================== + +// ==== Invoke marbles ==== +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble1","blue","35","tom"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble2","red","50","tom"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["initMarble","marble3","blue","70","tom"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["transferMarble","marble2","jerry"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["transferMarblesBasedOnColor","blue","jerry"]}' +// peer chaincode invoke -C myc1 -n marbles -c '{"Args":["delete","marble1"]}' + +// ==== Query marbles ==== +// peer chaincode query -C myc1 -n marbles -c '{"Args":["readMarble","marble1"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["getMarblesByRange","marble1","marble3"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["getHistoryForMarble","marble1"]}' + +// Rich Query (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesByOwner","tom"]}' +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"owner\":\"tom\"}}"]}' + +// Rich Query with Pagination (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarblesWithPagination","{\"selector\":{\"owner\":\"tom\"}}","3",""]}' + +// INDEXES TO SUPPORT COUCHDB RICH QUERIES +// +// Indexes in CouchDB are required in order to make JSON queries efficient and are required for +// any JSON query with a sort. As of Hyperledger Fabric 1.1, indexes may be packaged alongside +// chaincode in a META-INF/statedb/couchdb/indexes directory. Each index must be defined in its own +// text file with extension *.json with the index definition formatted in JSON following the +// CouchDB index JSON syntax as documented at: +// http://docs.couchdb.org/en/2.1.1/api/database/find.html#db-index +// +// This marbles02 example chaincode demonstrates a packaged +// index which you can find in META-INF/statedb/couchdb/indexes/indexOwner.json. +// For deployment of chaincode to production environments, it is recommended +// to define any indexes alongside chaincode so that the chaincode and supporting indexes +// are deployed automatically as a unit, once the chaincode has been installed on a peer and +// instantiated on a channel. See Hyperledger Fabric documentation for more details. +// +// If you have access to the your peer's CouchDB state database in a development environment, +// you may want to iteratively test various indexes in support of your chaincode queries. You +// can use the CouchDB Fauxton interface or a command line curl utility to create and update +// indexes. Then once you finalize an index, include the index definition alongside your +// chaincode in the META-INF/statedb/couchdb/indexes directory, for packaging and deployment +// to managed environments. +// +// In the examples below you can find index definitions that support marbles02 +// chaincode queries, along with the syntax that you can use in development environments +// to create the indexes in the CouchDB Fauxton interface or a curl command line utility. +// + +//Example hostname:port configurations to access CouchDB. +// +//To access CouchDB docker container from within another docker container or from vagrant environments: +// http://couchdb:5984/ +// +//Inside couchdb docker container +// http://127.0.0.1:5984/ + +// Index for docType, owner. +// +// Example curl command line to define index in the CouchDB channel_chaincode database. Default user_name/password in couchdb is admin/adminpw +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[\"docType\",\"owner\"]},\"name\":\"indexOwner\",\"ddoc\":\"indexOwnerDoc\",\"type\":\"json\"}" user_name:password@hostname:port/myc1_marbles/_index +// + +// Index for docType, owner, size (descending order). +// +// Example curl command line to define index in the CouchDB channel_chaincode database. Default user_name/password in couchdb is admin/adminpw +// curl -i -X POST -H "Content-Type: application/json" -d "{\"index\":{\"fields\":[{\"size\":\"desc\"},{\"docType\":\"desc\"},{\"owner\":\"desc\"}]},\"ddoc\":\"indexSizeSortDoc\", \"name\":\"indexSizeSortDesc\",\"type\":\"json\"}" user_name:password@hostname:port/myc1_marbles/_index + +// Rich Query with index design doc and index name specified (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":\"marble\",\"owner\":\"tom\"}, \"use_index\":[\"_design/indexOwnerDoc\", \"indexOwner\"]}"]}' + +// Rich Query with index design doc specified only (Only supported if CouchDB is used as state database): +// peer chaincode query -C myc1 -n marbles -c '{"Args":["queryMarbles","{\"selector\":{\"docType\":{\"$eq\":\"marble\"},\"owner\":{\"$eq\":\"tom\"},\"size\":{\"$gt\":0}},\"fields\":[\"docType\",\"owner\",\"size\"],\"sort\":[{\"size\":\"desc\"}],\"use_index\":\"_design/indexSizeSortDoc\"}"]}' + +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/hyperledger/fabric-chaincode-go/shim" + pb "github.com/hyperledger/fabric-protos-go/peer" +) + +// SimpleChaincode example simple Chaincode implementation +type SimpleChaincode struct { +} + +type marble struct { + ObjectType string `json:"docType"` //docType is used to distinguish the various types of objects in state database + Name string `json:"name"` //the fieldtags are needed to keep case from bouncing around + Color string `json:"color"` + Size int `json:"size"` + Owner string `json:"owner"` +} + +// =================================================================================== +// Main +// =================================================================================== +func main() { + err := shim.Start(new(SimpleChaincode)) + if err != nil { + fmt.Printf("Error starting Simple chaincode: %s", err) + } +} + +// Init initializes chaincode +// =========================== +func (t *SimpleChaincode) Init(stub shim.ChaincodeStubInterface) pb.Response { + return shim.Success(nil) +} + +// Invoke - Our entry point for Invocations +// ======================================== +func (t *SimpleChaincode) Invoke(stub shim.ChaincodeStubInterface) pb.Response { + function, args := stub.GetFunctionAndParameters() + fmt.Println("invoke is running " + function) + + // Handle different functions + if function == "initMarble" { //create a new marble + return t.initMarble(stub, args) + } else if function == "transferMarble" { //change owner of a specific marble + return t.transferMarble(stub, args) + } else if function == "transferMarblesBasedOnColor" { //transfer all marbles of a certain color + return t.transferMarblesBasedOnColor(stub, args) + } else if function == "delete" { //delete a marble + return t.delete(stub, args) + } else if function == "readMarble" { //read a marble + return t.readMarble(stub, args) + } else if function == "queryMarblesByOwner" { //find marbles for owner X using rich query + return t.queryMarblesByOwner(stub, args) + } else if function == "queryMarbles" { //find marbles based on an ad hoc rich query + return t.queryMarbles(stub, args) + } else if function == "getHistoryForMarble" { //get history of values for a marble + return t.getHistoryForMarble(stub, args) + } else if function == "getMarblesByRange" { //get marbles based on range query + return t.getMarblesByRange(stub, args) + } else if function == "getMarblesByRangeWithPagination" { + return t.getMarblesByRangeWithPagination(stub, args) + } else if function == "queryMarblesWithPagination" { + return t.queryMarblesWithPagination(stub, args) + } + + fmt.Println("invoke did not find func: " + function) //error + return shim.Error("Received unknown function invocation") +} + +// ============================================================ +// initMarble - create a new marble, store into chaincode state +// ============================================================ +func (t *SimpleChaincode) initMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + var err error + + // 0 1 2 3 + // "asdf", "blue", "35", "bob" + if len(args) != 4 { + return shim.Error("Incorrect number of arguments. Expecting 4") + } + + // ==== Input sanitation ==== + fmt.Println("- start init marble") + if len(args[0]) <= 0 { + return shim.Error("1st argument must be a non-empty string") + } + if len(args[1]) <= 0 { + return shim.Error("2nd argument must be a non-empty string") + } + if len(args[2]) <= 0 { + return shim.Error("3rd argument must be a non-empty string") + } + if len(args[3]) <= 0 { + return shim.Error("4th argument must be a non-empty string") + } + marbleName := args[0] + color := strings.ToLower(args[1]) + owner := strings.ToLower(args[3]) + size, err := strconv.Atoi(args[2]) + if err != nil { + return shim.Error("3rd argument must be a numeric string") + } + + // ==== Check if marble already exists ==== + marbleAsBytes, err := stub.GetState(marbleName) + if err != nil { + return shim.Error("Failed to get marble: " + err.Error()) + } else if marbleAsBytes != nil { + fmt.Println("This marble already exists: " + marbleName) + return shim.Error("This marble already exists: " + marbleName) + } + + // ==== Create marble object and marshal to JSON ==== + objectType := "marble" + marble := &marble{objectType, marbleName, color, size, owner} + marbleJSONasBytes, err := json.Marshal(marble) + if err != nil { + return shim.Error(err.Error()) + } + //Alternatively, build the marble json string manually if you don't want to use struct marshalling + //marbleJSONasString := `{"docType":"Marble", "name": "` + marbleName + `", "color": "` + color + `", "size": ` + strconv.Itoa(size) + `, "owner": "` + owner + `"}` + //marbleJSONasBytes := []byte(str) + + // === Save marble to state === + err = stub.PutState(marbleName, marbleJSONasBytes) + if err != nil { + return shim.Error(err.Error()) + } + + // ==== Index the marble to enable color-based range queries, e.g. return all blue marbles ==== + // An 'index' is a normal key/value entry in state. + // The key is a composite key, with the elements that you want to range query on listed first. + // In our case, the composite key is based on indexName~color~name. + // This will enable very efficient state range queries based on composite keys matching indexName~color~* + indexName := "color~name" + colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marble.Color, marble.Name}) + if err != nil { + return shim.Error(err.Error()) + } + // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. + // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value + value := []byte{0x00} + stub.PutState(colorNameIndexKey, value) + + // ==== Marble saved and indexed. Return success ==== + fmt.Println("- end init marble") + return shim.Success(nil) +} + +// =============================================== +// readMarble - read a marble from chaincode state +// =============================================== +func (t *SimpleChaincode) readMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + var name, jsonResp string + var err error + + if len(args) != 1 { + return shim.Error("Incorrect number of arguments. Expecting name of the marble to query") + } + + name = args[0] + valAsbytes, err := stub.GetState(name) //get the marble from chaincode state + if err != nil { + jsonResp = "{\"Error\":\"Failed to get state for " + name + "\"}" + return shim.Error(jsonResp) + } else if valAsbytes == nil { + jsonResp = "{\"Error\":\"Marble does not exist: " + name + "\"}" + return shim.Error(jsonResp) + } + + return shim.Success(valAsbytes) +} + +// ================================================== +// delete - remove a marble key/value pair from state +// ================================================== +func (t *SimpleChaincode) delete(stub shim.ChaincodeStubInterface, args []string) pb.Response { + var jsonResp string + var marbleJSON marble + if len(args) != 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + marbleName := args[0] + + // to maintain the color~name index, we need to read the marble first and get its color + valAsbytes, err := stub.GetState(marbleName) //get the marble from chaincode state + if err != nil { + jsonResp = "{\"Error\":\"Failed to get state for " + marbleName + "\"}" + return shim.Error(jsonResp) + } else if valAsbytes == nil { + jsonResp = "{\"Error\":\"Marble does not exist: " + marbleName + "\"}" + return shim.Error(jsonResp) + } + + err = json.Unmarshal([]byte(valAsbytes), &marbleJSON) + if err != nil { + jsonResp = "{\"Error\":\"Failed to decode JSON of: " + marbleName + "\"}" + return shim.Error(jsonResp) + } + + err = stub.DelState(marbleName) //remove the marble from chaincode state + if err != nil { + return shim.Error("Failed to delete state:" + err.Error()) + } + + // maintain the index + indexName := "color~name" + colorNameIndexKey, err := stub.CreateCompositeKey(indexName, []string{marbleJSON.Color, marbleJSON.Name}) + if err != nil { + return shim.Error(err.Error()) + } + + // Delete index entry to state. + err = stub.DelState(colorNameIndexKey) + if err != nil { + return shim.Error("Failed to delete state:" + err.Error()) + } + return shim.Success(nil) +} + +// =========================================================== +// transfer a marble by setting a new owner name on the marble +// =========================================================== +func (t *SimpleChaincode) transferMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 1 + // "name", "bob" + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + marbleName := args[0] + newOwner := strings.ToLower(args[1]) + fmt.Println("- start transferMarble ", marbleName, newOwner) + + marbleAsBytes, err := stub.GetState(marbleName) + if err != nil { + return shim.Error("Failed to get marble:" + err.Error()) + } else if marbleAsBytes == nil { + return shim.Error("Marble does not exist") + } + + marbleToTransfer := marble{} + err = json.Unmarshal(marbleAsBytes, &marbleToTransfer) //unmarshal it aka JSON.parse() + if err != nil { + return shim.Error(err.Error()) + } + marbleToTransfer.Owner = newOwner //change the owner + + marbleJSONasBytes, _ := json.Marshal(marbleToTransfer) + err = stub.PutState(marbleName, marbleJSONasBytes) //rewrite the marble + if err != nil { + return shim.Error(err.Error()) + } + + fmt.Println("- end transferMarble (success)") + return shim.Success(nil) +} + +// =========================================================================================== +// constructQueryResponseFromIterator constructs a JSON array containing query results from +// a given result iterator +// =========================================================================================== +func constructQueryResponseFromIterator(resultsIterator shim.StateQueryIteratorInterface) (*bytes.Buffer, error) { + // buffer is a JSON array containing QueryResults + var buffer bytes.Buffer + buffer.WriteString("[") + + bArrayMemberAlreadyWritten := false + for resultsIterator.HasNext() { + queryResponse, err := resultsIterator.Next() + if err != nil { + return nil, err + } + // Add a comma before array members, suppress it for the first array member + if bArrayMemberAlreadyWritten == true { + buffer.WriteString(",") + } + buffer.WriteString("{\"Key\":") + buffer.WriteString("\"") + buffer.WriteString(queryResponse.Key) + buffer.WriteString("\"") + + buffer.WriteString(", \"Record\":") + // Record is a JSON object, so we write as-is + buffer.WriteString(string(queryResponse.Value)) + buffer.WriteString("}") + bArrayMemberAlreadyWritten = true + } + buffer.WriteString("]") + + return &buffer, nil +} + +// =========================================================================================== +// addPaginationMetadataToQueryResults adds QueryResponseMetadata, which contains pagination +// info, to the constructed query results +// =========================================================================================== +func addPaginationMetadataToQueryResults(buffer *bytes.Buffer, responseMetadata *pb.QueryResponseMetadata) *bytes.Buffer { + + buffer.WriteString("[{\"ResponseMetadata\":{\"RecordsCount\":") + buffer.WriteString("\"") + buffer.WriteString(fmt.Sprintf("%v", responseMetadata.FetchedRecordsCount)) + buffer.WriteString("\"") + buffer.WriteString(", \"Bookmark\":") + buffer.WriteString("\"") + buffer.WriteString(responseMetadata.Bookmark) + buffer.WriteString("\"}}]") + + return buffer +} + +// =========================================================================================== +// getMarblesByRange performs a range query based on the start and end keys provided. + +// Read-only function results are not typically submitted to ordering. If the read-only +// results are submitted to ordering, or if the query is used in an update transaction +// and submitted to ordering, then the committing peers will re-execute to guarantee that +// result sets are stable between endorsement time and commit time. The transaction is +// invalidated by the committing peers if the result set has changed between endorsement +// time and commit time. +// Therefore, range queries are a safe option for performing update transactions based on query results. +// =========================================================================================== +func (t *SimpleChaincode) getMarblesByRange(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + startKey := args[0] + endKey := args[1] + + resultsIterator, err := stub.GetStateByRange(startKey, endKey) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return shim.Error(err.Error()) + } + + fmt.Printf("- getMarblesByRange queryResult:\n%s\n", buffer.String()) + + return shim.Success(buffer.Bytes()) +} + +// ==== Example: GetStateByPartialCompositeKey/RangeQuery ========================================= +// transferMarblesBasedOnColor will transfer marbles of a given color to a certain new owner. +// Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. +// Committing peers will re-execute range queries to guarantee that result sets are stable +// between endorsement time and commit time. The transaction is invalidated by the +// committing peers if the result set has changed between endorsement time and commit time. +// Therefore, range queries are a safe option for performing update transactions based on query results. +// =========================================================================================== +func (t *SimpleChaincode) transferMarblesBasedOnColor(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 1 + // "color", "bob" + if len(args) < 2 { + return shim.Error("Incorrect number of arguments. Expecting 2") + } + + color := args[0] + newOwner := strings.ToLower(args[1]) + fmt.Println("- start transferMarblesBasedOnColor ", color, newOwner) + + // Query the color~name index by color + // This will execute a key range query on all keys starting with 'color' + coloredMarbleResultsIterator, err := stub.GetStateByPartialCompositeKey("color~name", []string{color}) + if err != nil { + return shim.Error(err.Error()) + } + defer coloredMarbleResultsIterator.Close() + + // Iterate through result set and for each marble found, transfer to newOwner + var i int + for i = 0; coloredMarbleResultsIterator.HasNext(); i++ { + // Note that we don't get the value (2nd return variable), we'll just get the marble name from the composite key + responseRange, err := coloredMarbleResultsIterator.Next() + if err != nil { + return shim.Error(err.Error()) + } + + // get the color and name from color~name composite key + objectType, compositeKeyParts, err := stub.SplitCompositeKey(responseRange.Key) + if err != nil { + return shim.Error(err.Error()) + } + returnedColor := compositeKeyParts[0] + returnedMarbleName := compositeKeyParts[1] + fmt.Printf("- found a marble from index:%s color:%s name:%s\n", objectType, returnedColor, returnedMarbleName) + + // Now call the transfer function for the found marble. + // Re-use the same function that is used to transfer individual marbles + response := t.transferMarble(stub, []string{returnedMarbleName, newOwner}) + // if the transfer failed break out of loop and return error + if response.Status != shim.OK { + return shim.Error("Transfer failed: " + response.Message) + } + } + + responsePayload := fmt.Sprintf("Transferred %d %s marbles to %s", i, color, newOwner) + fmt.Println("- end transferMarblesBasedOnColor: " + responsePayload) + return shim.Success([]byte(responsePayload)) +} + +// =======Rich queries ========================================================================= +// Two examples of rich queries are provided below (parameterized query and ad hoc query). +// Rich queries pass a query string to the state database. +// Rich queries are only supported by state database implementations +// that support rich query (e.g. CouchDB). +// The query string is in the syntax of the underlying state database. +// With rich queries there is no guarantee that the result set hasn't changed between +// endorsement time and commit time, aka 'phantom reads'. +// Therefore, rich queries should not be used in update transactions, unless the +// application handles the possibility of result set changes between endorsement and commit time. +// Rich queries can be used for point-in-time queries against a peer. +// ============================================================================================ + +// ===== Example: Parameterized rich query ================================================= +// queryMarblesByOwner queries for marbles based on a passed in owner. +// This is an example of a parameterized query where the query logic is baked into the chaincode, +// and accepting a single query parameter (owner). +// Only available on state databases that support rich query (e.g. CouchDB) +// ========================================================================================= +func (t *SimpleChaincode) queryMarblesByOwner(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 + // "bob" + if len(args) < 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + + owner := strings.ToLower(args[0]) + + queryString := fmt.Sprintf("{\"selector\":{\"docType\":\"marble\",\"owner\":\"%s\"}}", owner) + + queryResults, err := getQueryResultForQueryString(stub, queryString) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(queryResults) +} + +// ===== Example: Ad hoc rich query ======================================================== +// queryMarbles uses a query string to perform a query for marbles. +// Query string matching state database syntax is passed in and executed as is. +// Supports ad hoc queries that can be defined at runtime by the client. +// If this is not desired, follow the queryMarblesForOwner example for parameterized queries. +// Only available on state databases that support rich query (e.g. CouchDB) +// ========================================================================================= +func (t *SimpleChaincode) queryMarbles(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 + // "queryString" + if len(args) < 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + + queryString := args[0] + + queryResults, err := getQueryResultForQueryString(stub, queryString) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(queryResults) +} + +// ========================================================================================= +// getQueryResultForQueryString executes the passed in query string. +// Result set is built and returned as a byte array containing the JSON results. +// ========================================================================================= +func getQueryResultForQueryString(stub shim.ChaincodeStubInterface, queryString string) ([]byte, error) { + + fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) + + resultsIterator, err := stub.GetQueryResult(queryString) + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return nil, err + } + + fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", buffer.String()) + + return buffer.Bytes(), nil +} + +// ====== Pagination ========================================================================= +// Pagination provides a method to retrieve records with a defined pagesize and +// start point (bookmark). An empty string bookmark defines the first "page" of a query +// result. Paginated queries return a bookmark that can be used in +// the next query to retrieve the next page of results. Paginated queries extend +// rich queries and range queries to include a pagesize and bookmark. +// +// Two examples are provided in this example. The first is getMarblesByRangeWithPagination +// which executes a paginated range query. +// The second example is a paginated query for rich ad-hoc queries. +// ========================================================================================= + +// ====== Example: Pagination with Range Query =============================================== +// getMarblesByRangeWithPagination performs a range query based on the start & end key, +// page size and a bookmark. + +// The number of fetched records will be equal to or lesser than the page size. +// Paginated range queries are only valid for read only transactions. +// =========================================================================================== +func (t *SimpleChaincode) getMarblesByRangeWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 4 { + return shim.Error("Incorrect number of arguments. Expecting 4") + } + + startKey := args[0] + endKey := args[1] + //return type of ParseInt is int64 + pageSize, err := strconv.ParseInt(args[2], 10, 32) + if err != nil { + return shim.Error(err.Error()) + } + bookmark := args[3] + + resultsIterator, responseMetadata, err := stub.GetStateByRangeWithPagination(startKey, endKey, int32(pageSize), bookmark) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return shim.Error(err.Error()) + } + + bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) + + fmt.Printf("- getMarblesByRange queryResult:\n%s\n", bufferWithPaginationInfo.String()) + + return shim.Success(buffer.Bytes()) +} + +// ===== Example: Pagination with Ad hoc Rich Query ======================================================== +// queryMarblesWithPagination uses a query string, page size and a bookmark to perform a query +// for marbles. Query string matching state database syntax is passed in and executed as is. +// The number of fetched records would be equal to or lesser than the specified page size. +// Supports ad hoc queries that can be defined at runtime by the client. +// If this is not desired, follow the queryMarblesForOwner example for parameterized queries. +// Only available on state databases that support rich query (e.g. CouchDB) +// Paginated queries are only valid for read only transactions. +// ========================================================================================= +func (t *SimpleChaincode) queryMarblesWithPagination(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + // 0 + // "queryString" + if len(args) < 3 { + return shim.Error("Incorrect number of arguments. Expecting 3") + } + + queryString := args[0] + //return type of ParseInt is int64 + pageSize, err := strconv.ParseInt(args[1], 10, 32) + if err != nil { + return shim.Error(err.Error()) + } + bookmark := args[2] + + queryResults, err := getQueryResultForQueryStringWithPagination(stub, queryString, int32(pageSize), bookmark) + if err != nil { + return shim.Error(err.Error()) + } + return shim.Success(queryResults) +} + +// ========================================================================================= +// getQueryResultForQueryStringWithPagination executes the passed in query string with +// pagination info. Result set is built and returned as a byte array containing the JSON results. +// ========================================================================================= +func getQueryResultForQueryStringWithPagination(stub shim.ChaincodeStubInterface, queryString string, pageSize int32, bookmark string) ([]byte, error) { + + fmt.Printf("- getQueryResultForQueryString queryString:\n%s\n", queryString) + + resultsIterator, responseMetadata, err := stub.GetQueryResultWithPagination(queryString, pageSize, bookmark) + if err != nil { + return nil, err + } + defer resultsIterator.Close() + + buffer, err := constructQueryResponseFromIterator(resultsIterator) + if err != nil { + return nil, err + } + + bufferWithPaginationInfo := addPaginationMetadataToQueryResults(buffer, responseMetadata) + + fmt.Printf("- getQueryResultForQueryString queryResult:\n%s\n", bufferWithPaginationInfo.String()) + + return buffer.Bytes(), nil +} + +func (t *SimpleChaincode) getHistoryForMarble(stub shim.ChaincodeStubInterface, args []string) pb.Response { + + if len(args) < 1 { + return shim.Error("Incorrect number of arguments. Expecting 1") + } + + marbleName := args[0] + + fmt.Printf("- start getHistoryForMarble: %s\n", marbleName) + + resultsIterator, err := stub.GetHistoryForKey(marbleName) + if err != nil { + return shim.Error(err.Error()) + } + defer resultsIterator.Close() + + // buffer is a JSON array containing historic values for the marble + var buffer bytes.Buffer + buffer.WriteString("[") + + bArrayMemberAlreadyWritten := false + for resultsIterator.HasNext() { + response, err := resultsIterator.Next() + if err != nil { + return shim.Error(err.Error()) + } + // Add a comma before array members, suppress it for the first array member + if bArrayMemberAlreadyWritten == true { + buffer.WriteString(",") + } + buffer.WriteString("{\"TxId\":") + buffer.WriteString("\"") + buffer.WriteString(response.TxId) + buffer.WriteString("\"") + + buffer.WriteString(", \"Value\":") + // if it was a delete operation on given key, then we need to set the + //corresponding value null. Else, we will write the response.Value + //as-is (as the Value itself a JSON marble) + if response.IsDelete { + buffer.WriteString("null") + } else { + buffer.WriteString(string(response.Value)) + } + + buffer.WriteString(", \"Timestamp\":") + buffer.WriteString("\"") + buffer.WriteString(time.Unix(response.Timestamp.Seconds, int64(response.Timestamp.Nanos)).String()) + buffer.WriteString("\"") + + buffer.WriteString(", \"IsDelete\":") + buffer.WriteString("\"") + buffer.WriteString(strconv.FormatBool(response.IsDelete)) + buffer.WriteString("\"") + + buffer.WriteString("}") + bArrayMemberAlreadyWritten = true + } + buffer.WriteString("]") + + fmt.Printf("- getHistoryForMarble returning:\n%s\n", buffer.String()) + + return shim.Success(buffer.Bytes()) +} diff --git a/packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json b/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/metadata/statedb/couchdb/indexes/indexOwner.json similarity index 100% rename from packages/caliper-tests-integration/fabric_docker_distributed_tests/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json rename to packages/caliper-tests-integration/generator_tests/fabric/src/marbles/go/metadata/statedb/couchdb/indexes/indexOwner.json diff --git a/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/marbles.js b/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/marbles.js deleted file mode 100644 index f36228a19..000000000 --- a/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/marbles.js +++ /dev/null @@ -1,509 +0,0 @@ -/* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -/* -* NOTE: This implementation is a derivative work of the following: -* https://github.com/hyperledger/fabric-samples/blob/release-1.1/chaincode/marbles02/node/marbles_chaincode.js -* The modifications include: bug fixes and refactoring for eslint compliance. -*/ - -/* eslint-disable no-console */ - -'use strict'; -const shim = require('fabric-shim'); -const util = require('util'); - -/** - * Marble asset management chaincode written in node.js, implementing {@link ChaincodeInterface}. - * @type {SimpleChaincode} - * @extends {ChaincodeInterface} - */ -let Chaincode = class { - /** - * Called during chaincode instantiate and upgrade. This method can be used - * to initialize asset states. - * @async - * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim - * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub - * encapsulates the APIs between the chaincode implementation and the Fabric peer. - * @return {Promise} Returns a promise of a response indicating the result of the invocation. - */ - async Init(stub) { - const marbles = [ - { - name: 'marble1', - color: 'Green', - size: '5', - owner: 'Alice' - }, - { - name: 'marble2', - color: 'Yellow', - size: '15', - owner: 'Bob' - }, - { - name: 'marble3', - color: 'Blue', - size: '10', - owner: 'Tom' - } - ] - for (let i=0; i < marbles.length; i++) { - marbles[i].docType = 'marble'; - await stub.putState(marbles[i].name, Buffer.from(JSON.stringify(marbles[i]))); - console.info('Added <--> ', marbles[i]); - } - console.info('=========== Instantiated Marbles Chaincode ==========='); - return shim.success(); - } - - /** - * Called throughout the life time of the chaincode to carry out business - * transaction logic and effect the asset states. - * The provided functions are the following: initMarble, delete, transferMarble, readMarble, getMarblesByRange, - * transferMarblesBasedOnColor, queryMarblesByOwner, queryMarbles, getHistoryForMarble. - * @async - * @param {ChaincodeStub} stub The chaincode stub is implemented by the fabric-shim - * library and passed to the {@link ChaincodeInterface} calls by the Hyperledger Fabric platform. The stub - * encapsulates the APIs between the chaincode implementation and the Fabric peer. - * @return {Promise} Returns a promise of a response indicating the result of the invocation. - */ - async Invoke(stub) { - console.info('Transaction ID: ' + stub.getTxID()); - console.info(util.format('Args: %j', stub.getArgs())); - - let ret = stub.getFunctionAndParameters(); - console.info(ret); - - let method = this[ret.fcn]; - if (!method) { - console.log('no function of name:' + ret.fcn + ' found'); - throw new Error('Received unknown function ' + ret.fcn + ' invocation'); - } - try { - let payload = await method(stub, ret.params, this); - return shim.success(payload); - } catch (err) { - console.log(err); - return shim.error(err); - } - } - - /** - * Creates a new marble with the given attributes. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: marble color. - * Index 2: marble size. Index 3: marble owner. - */ - async initMarble(stub, args) { - if (args.length !== 4) { - throw new Error('Incorrect number of arguments. Expecting 4'); - } - // ==== Input sanitation ==== - console.info('--- start init marble ---'); - if (args[0].length <= 0) { - throw new Error('1st argument must be a non-empty string'); - } - if (args[1].length <= 0) { - throw new Error('2nd argument must be a non-empty string'); - } - if (args[2].length <= 0) { - throw new Error('3rd argument must be a non-empty string'); - } - if (args[3].length <= 0) { - throw new Error('4th argument must be a non-empty string'); - } - let marbleName = args[0]; - let color = args[1].toLowerCase(); - let owner = args[3].toLowerCase(); - let size = parseInt(args[2]); - if (isNaN(size)) { - throw new Error('3rd argument must be a numeric string'); - } - - // ==== Check if marble already exists ==== - let marbleState = await stub.getState(marbleName); - if (marbleState.toString()) { - throw new Error('This marble already exists: ' + marbleName); - } - - // ==== Create marble object and marshal to JSON ==== - let marble = {}; - marble.docType = 'marble'; - marble.name = marbleName; - marble.color = color; - marble.size = size; - marble.owner = owner; - - // === Save marble to state === - await stub.putState(marbleName, Buffer.from(JSON.stringify(marble))); - let indexName = 'color~name'; - let colorNameIndexKey = await stub.createCompositeKey(indexName, [marble.color, marble.name]); - console.info(colorNameIndexKey); - // Save index entry to state. Only the key name is needed, no need to store a duplicate copy of the marble. - // Note - passing a 'nil' value will effectively delete the key from state, therefore we pass null character as value - await stub.putState(colorNameIndexKey, Buffer.from('\u0000')); - // ==== Marble saved and indexed. Return success ==== - console.info('- end init marble'); - } - - /** - * Retrieves the information about a marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - * @return {Promise} The byte representation of the marble. - */ - async readMarble(stub, args) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting name of the marble to query'); - } - - let name = args[0]; - if (!name) { - throw new Error(' marble name must not be empty'); - } - let marbleAsBytes = await stub.getState(name); //get the marble from chaincode state - if (!marbleAsBytes.toString()) { - let jsonResp = {}; - jsonResp.Error = 'Marble does not exist: ' + name; - throw new Error(JSON.stringify(jsonResp)); - } - console.info('======================================='); - console.log(marbleAsBytes.toString()); - console.info('======================================='); - return marbleAsBytes; - } - - /** - * Deletes the given marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - */ - async delete(stub, args) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting name of the marble to delete'); - } - let marbleName = args[0]; - if (!marbleName) { - throw new Error('marble name must not be empty'); - } - // to maintain the color~name index, we need to read the marble first and get its color - let valAsBytes = await stub.getState(marbleName); //get the marble from chaincode state - let jsonResp = {}; - if (!valAsBytes) { - jsonResp.error = 'marble does not exist: ' + marbleName; - throw new Error(jsonResp); - } - let marbleJSON = {}; - try { - marbleJSON = JSON.parse(valAsBytes.toString()); - } catch (err) { - jsonResp = {}; - jsonResp.error = 'Failed to decode JSON of: ' + marbleName; - throw new Error(jsonResp); - } - - await stub.deleteState(marbleName); //remove the marble from chaincode state - - // delete the index - let indexName = 'color~name'; - let colorNameIndexKey = stub.createCompositeKey(indexName, [marbleJSON.color, marbleJSON.name]); - if (!colorNameIndexKey) { - throw new Error(' Failed to create the createCompositeKey'); - } - // Delete index entry to state. - await stub.deleteState(colorNameIndexKey); - } - - // =========================================================== - // transfer a marble by setting a new owner name on the marble - // =========================================================== - /** - * Transfers the given marble to a new owner. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. Index 1: the new owner. - */ - async transferMarble(stub, args) { - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting marble name and owner'); - } - - let marbleName = args[0]; - let newOwner = args[1].toLowerCase(); - console.info('- start transferMarble ', marbleName, newOwner); - - let marbleAsBytes = await stub.getState(marbleName); - if (!marbleAsBytes || !marbleAsBytes.toString()) { - throw new Error('marble does not exist'); - } - let marbleToTransfer = {}; - try { - marbleToTransfer = JSON.parse(marbleAsBytes.toString()); //unmarshal - } catch (err) { - let jsonResp = {}; - jsonResp.error = 'Failed to decode JSON of: ' + marbleName; - throw new Error(jsonResp); - } - console.info(marbleToTransfer); - marbleToTransfer.owner = newOwner; //change the owner - - let marbleJSONasBytes = Buffer.from(JSON.stringify(marbleToTransfer)); - await stub.putState(marbleName, marbleJSONasBytes); //rewrite the marble - - console.info('- end transferMarble (success)'); - } - - /** - * Performs a range query based on the start and end keys provided. - * - * Read-only function results are not typically submitted to ordering. If the read-only - * results are submitted to ordering, or if the query is used in an update transaction - * and submitted to ordering, then the committing peers will re-execute to guarantee that - * result sets are stable between endorsement time and commit time. The transaction is - * invalidated by the committing peers if the result set has changed between endorsement - * time and commit time. - * Therefore, range queries are a safe option for performing update transactions based on query results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: start key. Index 1: end key. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The marbles in the given range. - */ - async getMarblesByRange(stub, args, thisObject) { - - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting 2'); - } - - let startKey = args[0]; - let endKey = args[1]; - - let resultsIterator = await stub.getStateByRange(startKey, endKey); - let results = await thisObject.getAllResults(resultsIterator, false); - - return Buffer.from(JSON.stringify(results)); - } - - /** - * Transfers marbles of a given color to a certain new owner. - * - * Uses a GetStateByPartialCompositeKey (range query) against color~name 'index'. - * Committing peers will re-execute range queries to guarantee that result sets are stable - * between endorsement time and commit time. The transaction is invalidated by the - * committing peers if the result set has changed between endorsement time and commit time. - * Therefore, range queries are a safe option for performing update transactions based on query results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble color. Index 1: new owner. - * @param {Chaincode} thisObject The chaincode object context. - */ - async transferMarblesBasedOnColor(stub, args, thisObject) { - if (args.length !== 2) { - throw new Error('Incorrect number of arguments. Expecting color and owner'); - } - - let color = args[0]; - let newOwner = args[1].toLowerCase(); - console.info('- start transferMarblesBasedOnColor ', color, newOwner); - - // Query the color~name index by color - // This will execute a key range query on all keys starting with 'color' - let coloredMarbleResultsIterator = await stub.getStateByPartialCompositeKey('color~name', [color]); - - let hasNext = true; - // Iterate through result set and for each marble found, transfer to newOwner - while (hasNext) { - let responseRange; - try { - responseRange = await coloredMarbleResultsIterator.next(); - } catch (err) { - hasNext = false; - continue; - } - - if (!responseRange || !responseRange.value || !responseRange.value.key) { - return; - } - console.log(responseRange.value.key); - - // let value = res.value.value.toString('utf8'); - let objectType; - let attributes; - ({ - objectType, - attributes - } = await stub.splitCompositeKey(responseRange.value.key)); - - let returnedColor = attributes[0]; - let returnedMarbleName = attributes[1]; - console.info(util.format('- found a marble from index:%s color:%s name:%s\n', objectType, returnedColor, returnedMarbleName)); - - // Now call the transfer function for the found marble. - // Re-use the same function that is used to transfer individual marbles - await thisObject.transferMarble(stub, [returnedMarbleName, newOwner]); - } - - let responsePayload = util.format('Transferred %s marbles to %s', color, newOwner); - console.info('- end transferMarblesBasedOnColor: ' + responsePayload); - } - - /** - * Queries for marbles based on a passed in owner. - * This is an example of a parameterized query where the query logic is baked into the chaincode, - * and accepting a single query parameter (owner). - * Only available on state databases that support rich query (e.g. CouchDB) - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble owner. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The marbles of the specified owner. - */ - async queryMarblesByOwner(stub, args, thisObject) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting owner name.'); - } - - let owner = args[0].toLowerCase(); - let queryString = {}; - queryString.selector = {}; - queryString.selector.docType = 'marble'; - queryString.selector.owner = owner; - return await thisObject.getQueryResultForQueryString(stub, JSON.stringify(queryString), thisObject); //shim.success(queryResults); - } - - /** - * Uses a query string to perform a query for marbles. - * Query string matching state database syntax is passed in and executed as is. - * Supports ad hoc queries that can be defined at runtime by the client. - * If this is not desired, follow the queryMarblesForOwner example for parameterized queries. - * Only available on state databases that support rich query (e.g. CouchDB) - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: query string. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The results of the specified query. - */ - async queryMarbles(stub, args, thisObject) { - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting queryString'); - } - let queryString = args[0]; - if (!queryString) { - throw new Error('queryString must not be empty'); - } - - return await thisObject.getQueryResultForQueryString(stub, queryString, thisObject); - } - - /** - * Gets the results of a specified iterator. - * @async - * @param {Object} iterator The iterator to use. - * @param {Boolean} isHistory Specifies whether the iterator returns history entries or not. - * @return {Promise} The array of results in JSON format. - */ - async getAllResults(iterator, isHistory) { - let allResults = []; - let hasNext = true; - while (hasNext) { - let res; - try { - res = await iterator.next(); - } catch (err) { - hasNext = false; - continue; - } - - if (res.value && res.value.value.toString()) { - let jsonRes = {}; - console.log(res.value.value.toString('utf8')); - - if (isHistory && isHistory === true) { - jsonRes.TxId = res.value.tx_id; - jsonRes.Timestamp = res.value.timestamp; - jsonRes.IsDelete = res.value.is_delete.toString(); - try { - jsonRes.Value = JSON.parse(res.value.value.toString('utf8')); - } catch (err) { - console.log(err); - jsonRes.Value = res.value.value.toString('utf8'); - } - } else { - jsonRes.Key = res.value.key; - try { - jsonRes.Record = JSON.parse(res.value.value.toString('utf8')); - } catch (err) { - console.log(err); - jsonRes.Record = res.value.value.toString('utf8'); - } - } - allResults.push(jsonRes); - } - if (res.done) { - console.log('end of data'); - await iterator.close(); - console.info(allResults); - return allResults; - } - } - } - - /** - * Executes the provided query string. - * Result set is built and returned as a byte array containing the JSON results. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String} queryString The query string to execute. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The results of the specified query. - */ - async getQueryResultForQueryString(stub, queryString, thisObject) { - - console.info('- getQueryResultForQueryString queryString:\n' + queryString); - let resultsIterator = await stub.getQueryResult(queryString); - - let results = await thisObject.getAllResults(resultsIterator, false); - - return Buffer.from(JSON.stringify(results)); - } - - /** - * Retrieves the history for a marble. - * @async - * @param {ChaincodeStub} stub The chaincode stub. - * @param {String[]} args The arguments of the function. Index 0: marble name. - * @param {Chaincode} thisObject The chaincode object context. - * @return {Promise} The history entries of the specified marble. - */ - async getHistoryForMarble(stub, args, thisObject) { - - if (args.length !== 1) { - throw new Error('Incorrect number of arguments. Expecting 1'); - } - let marbleName = args[0]; - console.info('- start getHistoryForMarble: %s\n', marbleName); - - let resultsIterator = await stub.getHistoryForKey(marbleName); - let results = await thisObject.getAllResults(resultsIterator, true); - - return Buffer.from(JSON.stringify(results)); - } -}; - -shim.start(new Chaincode()); diff --git a/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json b/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json deleted file mode 100644 index 305f09044..000000000 --- a/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/metadata/statedb/couchdb/indexes/indexOwner.json +++ /dev/null @@ -1 +0,0 @@ -{"index":{"fields":["docType","owner"]},"ddoc":"indexOwnerDoc", "name":"indexOwner","type":"json"} diff --git a/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/package.json b/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/package.json deleted file mode 100644 index c48659644..000000000 --- a/packages/caliper-tests-integration/generator_tests/fabric/src/marbles/node/package.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "name": "marbles", - "version": "1.0.0", - "description": "marbles chaincode implemented in node.js", - "engines": { - "node": ">=8.4.0", - "npm": ">=5.3.0" - }, - "scripts": { - "start": "node marbles.js" - }, - "engine-strict": true, - "license": "Apache-2.0", - "dependencies": { - "fabric-shim": "2.4.2" - } -} diff --git a/packages/caliper-tests-integration/package.json b/packages/caliper-tests-integration/package.json index 6910bbd10..5dad45bfd 100644 --- a/packages/caliper-tests-integration/package.json +++ b/packages/caliper-tests-integration/package.json @@ -120,7 +120,9 @@ ".address", ".proto", ".cfg", - ".conf" + ".conf", + ".mod", + ".sum" ], "insert_license": false, "license_formats": {