diff --git a/.gitignore b/.gitignore index 1fe3f81538..a44d2f4055 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,9 @@ chaincode-docker-devmode/chaincode/chaincode_example02/chaincode_example02 # fabric sdk node modules fabcar/node_modules/ +fabcar/package-lock.json # balance transfer sample balance-transfer/.DS_Store balance-transfer/node_modules/* +balance-transfer/package-lock.json diff --git a/balance-transfer/README.md b/balance-transfer/README.md index 5f37222e2c..3e4514ab3f 100644 --- a/balance-transfer/README.md +++ b/balance-transfer/README.md @@ -27,6 +27,7 @@ Once you have completed the above setup, you will have provisioned a local netwo ## Running the sample program There are two options available for running the balance-transfer sample +For each of these options, you may choose to run with chaincode written in golang or in node.js. ### Option 1: @@ -82,10 +83,16 @@ With the application started in terminal 1, next, test the APIs by executing the ``` cd fabric-samples/balance-transfer -./testAPIs.sh +## To use golang chaincode execute the following command +./testAPIs.sh -l golang + +## OR use node.js chaincode + +./testAPIs.sh -l node ``` + ## Sample REST APIs Requests ### Login Request @@ -144,9 +151,25 @@ curl -s -X POST \ "peers": ["peer1","peer2"], "chaincodeName":"mycc", "chaincodePath":"github.com/example_cc", + "chaincodeType": "golang", "chaincodeVersion":"v0" }' ``` +**NOTE:** *chaincodeType* must be set to **node** when node.js chaincode is used and *chaincodePath* must be set to the location of the node.js chaincode. +``` +ex: +curl -s -X POST \ + http://localhost:4000/chaincodes \ + -H "authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJleHAiOjE0OTQ4NjU1OTEsInVzZXJuYW1lIjoiSmltIiwib3JnTmFtZSI6Im9yZzEiLCJpYXQiOjE0OTQ4NjE5OTF9.yWaJhFDuTvMQRaZIqg20Is5t-JJ_1BP58yrNLOKxtNI" \ + -H "content-type: application/json" \ + -d "{ + \"peers\": [\"peer1\",\"peer2\"], + \"chaincodeName\":\"mycc\", + \"chaincodePath\":\"$PWD/artifacts/src/github.com/example_cc/node\", + \"chaincodeType\": \"node\", + \"chaincodeVersion\":\"v0\" +}" +``` ### Instantiate chaincode @@ -158,9 +181,11 @@ curl -s -X POST \ -d '{ "chaincodeName":"mycc", "chaincodeVersion":"v0", + "chaincodeType": "golang", "args":["a","100","b","200"] }' ``` +**NOTE:** *chaincodeType* must be set to **node** when node.js chaincode is used ### Invoke request diff --git a/balance-transfer/app.js b/balance-transfer/app.js index db936b8a1c..e4bb1613e8 100644 --- a/balance-transfer/app.js +++ b/balance-transfer/app.js @@ -187,10 +187,12 @@ app.post('/chaincodes', function(req, res) { var chaincodeName = req.body.chaincodeName; var chaincodePath = req.body.chaincodePath; var chaincodeVersion = req.body.chaincodeVersion; + var chaincodeType = req.body.chaincodeType; logger.debug('peers : ' + peers); // target peers list logger.debug('chaincodeName : ' + chaincodeName); logger.debug('chaincodePath : ' + chaincodePath); logger.debug('chaincodeVersion : ' + chaincodeVersion); + logger.debug('chaincodeType : ' + chaincodeType); if (!peers || peers.length == 0) { res.json(getErrorMessage('\'peers\'')); return; @@ -207,8 +209,11 @@ app.post('/chaincodes', function(req, res) { res.json(getErrorMessage('\'chaincodeVersion\'')); return; } - - install.installChaincode(peers, chaincodeName, chaincodePath, chaincodeVersion, req.username, req.orgname) + if (!chaincodeType) { + res.json(getErrorMessage('\'chaincodeType\'')); + return; + } + install.installChaincode(peers, chaincodeName, chaincodePath, chaincodeVersion, chaincodeType, req.username, req.orgname) .then(function(message) { res.send(message); }); @@ -219,11 +224,13 @@ app.post('/channels/:channelName/chaincodes', function(req, res) { var chaincodeName = req.body.chaincodeName; var chaincodeVersion = req.body.chaincodeVersion; var channelName = req.params.channelName; + var chaincodeType = req.body.chaincodeType; var fcn = req.body.fcn; var args = req.body.args; logger.debug('channelName : ' + channelName); logger.debug('chaincodeName : ' + chaincodeName); logger.debug('chaincodeVersion : ' + chaincodeVersion); + logger.debug('chaincodeType : ' + chaincodeType); logger.debug('fcn : ' + fcn); logger.debug('args : ' + args); if (!chaincodeName) { @@ -238,11 +245,15 @@ app.post('/channels/:channelName/chaincodes', function(req, res) { res.json(getErrorMessage('\'channelName\'')); return; } + if (!chaincodeType) { + res.json(getErrorMessage('\'chaincodeType\'')); + return; + } if (!args) { res.json(getErrorMessage('\'args\'')); return; } - instantiate.instantiateChaincode(channelName, chaincodeName, chaincodeVersion, fcn, args, req.username, req.orgname) + instantiate.instantiateChaincode(channelName, chaincodeName, chaincodeVersion, chaincodeType, fcn, args, req.username, req.orgname) .then(function(message) { res.send(message); }); diff --git a/balance-transfer/app/install-chaincode.js b/balance-transfer/app/install-chaincode.js index 6f72b6d798..879e05c307 100644 --- a/balance-transfer/app/install-chaincode.js +++ b/balance-transfer/app/install-chaincode.js @@ -21,9 +21,9 @@ var config = require('../config.json'); var helper = require('./helper.js'); var logger = helper.getLogger('install-chaincode'); var tx_id = null; -//function installChaincode(org) { + var installChaincode = function(peers, chaincodeName, chaincodePath, - chaincodeVersion, username, org) { + chaincodeVersion, chaincodeType, username, org) { logger.debug( '\n============ Install chaincode on organizations ============\n'); helper.setupChaincodeDeploy(); @@ -35,7 +35,8 @@ var installChaincode = function(peers, chaincodeName, chaincodePath, targets: helper.newPeers(peers, org), chaincodePath: chaincodePath, chaincodeId: chaincodeName, - chaincodeVersion: chaincodeVersion + chaincodeVersion: chaincodeVersion, + chaincodeType: chaincodeType }; return client.installChaincode(request); }, (err) => { diff --git a/balance-transfer/app/instantiate-chaincode.js b/balance-transfer/app/instantiate-chaincode.js index 437b904f8f..d03c2cf0f7 100644 --- a/balance-transfer/app/instantiate-chaincode.js +++ b/balance-transfer/app/instantiate-chaincode.js @@ -26,7 +26,8 @@ var ORGS = hfc.getConfigSetting('network-config'); var tx_id = null; var eh = null; -var instantiateChaincode = function(channelName, chaincodeName, chaincodeVersion, functionName, args, username, org) { +var instantiateChaincode = function(channelName, chaincodeName, chaincodeVersion, functionName, chaincodeType, + args, username, org) { logger.debug('\n============ Instantiate chaincode on organization ' + org + ' ============\n'); @@ -46,6 +47,7 @@ var instantiateChaincode = function(channelName, chaincodeName, chaincodeVersion // send proposal to endorser var request = { chaincodeId: chaincodeName, + chaincodeType: chaincodeType, chaincodeVersion: chaincodeVersion, args: args, txId: tx_id diff --git a/balance-transfer/artifacts/src/github.com/example_cc/example_cc.go b/balance-transfer/artifacts/src/github.com/example_cc/go/example_cc.go similarity index 100% rename from balance-transfer/artifacts/src/github.com/example_cc/example_cc.go rename to balance-transfer/artifacts/src/github.com/example_cc/go/example_cc.go diff --git a/balance-transfer/artifacts/src/github.com/example_cc/node/example_cc.js b/balance-transfer/artifacts/src/github.com/example_cc/node/example_cc.js new file mode 100644 index 0000000000..9621178f0f --- /dev/null +++ b/balance-transfer/artifacts/src/github.com/example_cc/node/example_cc.js @@ -0,0 +1,140 @@ +/* +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +*/ + +const shim = require('fabric-shim'); +const util = require('util'); + +var Chaincode = class { + + // Initialize the chaincode + async Init(stub) { + console.info('========= example_cc Init ========='); + let ret = stub.getFunctionAndParameters(); + console.info(ret); + let args = ret.params; + // initialise only if 4 parameters passed. + if (args.length != 4) { + return shim.error('Incorrect number of arguments. Expecting 4'); + } + + let A = args[0]; + let B = args[2]; + let Aval = args[1]; + let Bval = args[3]; + + if (typeof parseInt(Aval) !== 'number' || typeof parseInt(Bval) !== 'number') { + return shim.error('Expecting integer value for asset holding'); + } + + try { + await stub.putState(A, Buffer.from(Aval)); + try { + await stub.putState(B, Buffer.from(Bval)); + return shim.success(); + } catch (err) { + return shim.error(err); + } + } catch (err) { + return shim.error(err); + } + } + + async Invoke(stub) { + let ret = stub.getFunctionAndParameters(); + console.info(ret); + let method = this[ret.fcn]; + if (!method) { + console.error('no method of name:' + ret.fcn + ' found'); + return shim.error('no method of name:' + ret.fcn + ' found'); + } + + console.info('\nCalling method : ' + ret.fcn); + try { + let payload = await method(stub, ret.params); + return shim.success(payload); + } catch (err) { + console.log(err); + return shim.error(err); + } + } + + async move(stub, args) { + if (args.length != 3) { + throw new Error('Incorrect number of arguments. Expecting 3'); + } + + let A = args[0]; + let B = args[1]; + if (!A || !B) { + throw new Error('asset holding must not be empty'); + } + + // Get the state from the ledger + let Avalbytes = await stub.getState(A); + if (!Avalbytes) { + throw new Error('Failed to get state of asset holder A'); + } + let Aval = parseInt(Avalbytes.toString()); + + let Bvalbytes = await stub.getState(B); + if (!Bvalbytes) { + throw new Error('Failed to get state of asset holder B'); + } + + let Bval = parseInt(Bvalbytes.toString()); + // Perform the execution + let amount = parseInt(args[2]); + if (typeof amount !== 'number') { + throw new Error('Expecting integer value for amount to be transaferred'); + } + + Aval = Aval - amount; + Bval = Bval + amount; + console.info(util.format('Aval = %d, Bval = %d\n', Aval, Bval)); + + // Write the states back to the ledger + await stub.putState(A, Buffer.from(Aval.toString())); + await stub.putState(B, Buffer.from(Bval.toString())); + + } + + // Deletes an entity from state + async delete(stub, args) { + if (args.length != 1) { + throw new Error('Incorrect number of arguments. Expecting 1'); + } + + let A = args[0]; + + // Delete the key from the state in ledger + await stub.deleteState(A); + } + + // query callback representing the query of a chaincode + async query(stub, args) { + if (args.length != 1) { + throw new Error('Incorrect number of arguments. Expecting name of the person to query') + } + + let jsonResp = {}; + let A = args[0]; + + // Get the state from the ledger + let Avalbytes = await stub.getState(A); + if (!Avalbytes) { + jsonResp.error = 'Failed to get state for ' + A; + throw new Error(JSON.stringify(jsonResp)); + } + + jsonResp.name = A; + jsonResp.amount = Avalbytes.toString(); + console.info('Query Response:'); + console.info(jsonResp); + return Avalbytes; + } +}; + +shim.start(new Chaincode()); diff --git a/balance-transfer/artifacts/src/github.com/example_cc/node/package.json b/balance-transfer/artifacts/src/github.com/example_cc/node/package.json new file mode 100644 index 0000000000..c7724458f5 --- /dev/null +++ b/balance-transfer/artifacts/src/github.com/example_cc/node/package.json @@ -0,0 +1,15 @@ +{ + "name": "example_cc", + "version": "1.0.0", + "description": "node-js version of example_02.go chaincode", + "engines": { + "node": ">=8.4.0", + "npm": ">=5.3.0" + }, + "scripts": { "start" : "node example_cc.js" }, + "engine-strict": true, + "license": "Apache-2.0", + "dependencies": { + "fabric-shim": "unstable" + } +} diff --git a/balance-transfer/package.json b/balance-transfer/package.json index f95a80bef4..fd5554bcb0 100644 --- a/balance-transfer/package.json +++ b/balance-transfer/package.json @@ -24,8 +24,8 @@ "express-bearer-token": "^2.1.0", "express-jwt": "^5.1.0", "express-session": "^1.15.2", - "fabric-ca-client": "^1.0.0", - "fabric-client": "^1.0.0", + "fabric-ca-client": "unstable", + "fabric-client": "unstable", "fs-extra": "^2.0.0", "jsonwebtoken": "^7.3.0", "log4js": "^0.6.38" diff --git a/balance-transfer/testAPIs.sh b/balance-transfer/testAPIs.sh index ab0d7eb8b6..a195822ac0 100755 --- a/balance-transfer/testAPIs.sh +++ b/balance-transfer/testAPIs.sh @@ -11,8 +11,47 @@ if [ $? -ne 0 ]; then echo exit 1 fi + starttime=$(date +%s) +# Print the usage message +function printHelp () { + echo "Usage: " + echo " ./testAPIs.sh -l golang|node" + echo " -l - chaincode language (defaults to \"golang\")" +} +# Language defaults to "golang" +LANGUAGE="golang" + +# Parse commandline args +while getopts "h?l:" opt; do + case "$opt" in + h|\?) + printHelp + exit 0 + ;; + l) LANGUAGE=$OPTARG + ;; + esac +done + +##set chaincode path +function setChaincodePath(){ + LANGUAGE=`echo "$LANGUAGE" | tr '[:upper:]' '[:lower:]'` + case "$LANGUAGE" in + "golang") + CC_SRC_PATH="github.com/example_cc/go" + ;; + "node") + CC_SRC_PATH="$PWD/artifacts/src/github.com/example_cc/node" + ;; + *) printf "\n ------ Language $LANGUAGE is not supported yet ------\n"$ + exit 1 + esac +} + +setChaincodePath + echo "POST request Enroll on Org1 ..." echo ORG1_TOKEN=$(curl -s -X POST \ @@ -79,28 +118,29 @@ curl -s -X POST \ http://localhost:4000/chaincodes \ -H "authorization: Bearer $ORG1_TOKEN" \ -H "content-type: application/json" \ - -d '{ - "peers": ["peer1", "peer2"], - "chaincodeName":"mycc", - "chaincodePath":"github.com/example_cc", - "chaincodeVersion":"v0" -}' + -d "{ + \"peers\": [\"peer1\", \"peer2\"], + \"chaincodeName\":\"mycc\", + \"chaincodePath\":\"$CC_SRC_PATH\", + \"chaincodeType\": \"$LANGUAGE\", + \"chaincodeVersion\":\"v0\" +}" echo echo - echo "POST Install chaincode on Org2" echo curl -s -X POST \ http://localhost:4000/chaincodes \ -H "authorization: Bearer $ORG2_TOKEN" \ -H "content-type: application/json" \ - -d '{ - "peers": ["peer1","peer2"], - "chaincodeName":"mycc", - "chaincodePath":"github.com/example_cc", - "chaincodeVersion":"v0" -}' + -d "{ + \"peers\": [\"peer1\",\"peer2\"], + \"chaincodeName\":\"mycc\", + \"chaincodePath\":\"$CC_SRC_PATH\", + \"chaincodeType\": \"$LANGUAGE\", + \"chaincodeVersion\":\"v0\" +}" echo echo @@ -110,11 +150,12 @@ curl -s -X POST \ http://localhost:4000/channels/mychannel/chaincodes \ -H "authorization: Bearer $ORG1_TOKEN" \ -H "content-type: application/json" \ - -d '{ - "chaincodeName":"mycc", - "chaincodeVersion":"v0", - "args":["a","100","b","200"] -}' + -d "{ + \"chaincodeName\":\"mycc\", + \"chaincodeVersion\":\"v0\", + \"chaincodeType\": \"$LANGUAGE\", + \"args\":[\"a\",\"100\",\"b\",\"200\"] +}" echo echo diff --git a/chaincode/chaincode_example02/chaincode_example02_test.go b/chaincode/chaincode_example02/chaincode_example02_test.go deleted file mode 100644 index c6c8ed7f34..0000000000 --- a/chaincode/chaincode_example02/chaincode_example02_test.go +++ /dev/null @@ -1,112 +0,0 @@ -/* -Copyright IBM Corp. 2016 All Rights Reserved. - -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. -*/ -package main - -import ( - "fmt" - "testing" - - "github.com/hyperledger/fabric/core/chaincode/shim" -) - -func checkInit(t *testing.T, stub *shim.MockStub, args [][]byte) { - res := stub.MockInit("1", args) - if res.Status != shim.OK { - fmt.Println("Init failed", string(res.Message)) - t.FailNow() - } -} - -func checkState(t *testing.T, stub *shim.MockStub, name string, value string) { - bytes := stub.State[name] - if bytes == nil { - fmt.Println("State", name, "failed to get value") - t.FailNow() - } - if string(bytes) != value { - fmt.Println("State value", name, "was not", value, "as expected") - t.FailNow() - } -} - -func checkQuery(t *testing.T, stub *shim.MockStub, name string, value string) { - res := stub.MockInvoke("1", [][]byte{[]byte("query"), []byte(name)}) - if res.Status != shim.OK { - fmt.Println("Query", name, "failed", string(res.Message)) - t.FailNow() - } - if res.Payload == nil { - fmt.Println("Query", name, "failed to get value") - t.FailNow() - } - if string(res.Payload) != value { - fmt.Println("Query value", name, "was not", value, "as expected") - t.FailNow() - } -} - -func checkInvoke(t *testing.T, stub *shim.MockStub, args [][]byte) { - res := stub.MockInvoke("1", args) - if res.Status != shim.OK { - fmt.Println("Invoke", args, "failed", string(res.Message)) - t.FailNow() - } -} - -func TestExample02_Init(t *testing.T) { - scc := new(SimpleChaincode) - stub := shim.NewMockStub("ex02", scc) - - // Init A=123 B=234 - checkInit(t, stub, [][]byte{[]byte("init"), []byte("A"), []byte("123"), []byte("B"), []byte("234")}) - - checkState(t, stub, "A", "123") - checkState(t, stub, "B", "234") -} - -func TestExample02_Query(t *testing.T) { - scc := new(SimpleChaincode) - stub := shim.NewMockStub("ex02", scc) - - // Init A=345 B=456 - checkInit(t, stub, [][]byte{[]byte("init"), []byte("A"), []byte("345"), []byte("B"), []byte("456")}) - - // Query A - checkQuery(t, stub, "A", "345") - - // Query B - checkQuery(t, stub, "B", "456") -} - -func TestExample02_Invoke(t *testing.T) { - scc := new(SimpleChaincode) - stub := shim.NewMockStub("ex02", scc) - - // Init A=567 B=678 - checkInit(t, stub, [][]byte{[]byte("init"), []byte("A"), []byte("567"), []byte("B"), []byte("678")}) - - // Invoke A->B for 123 - checkInvoke(t, stub, [][]byte{[]byte("invoke"), []byte("A"), []byte("B"), []byte("123")}) - checkQuery(t, stub, "A", "444") - checkQuery(t, stub, "B", "801") - - // Invoke B->A for 234 - checkInvoke(t, stub, [][]byte{[]byte("invoke"), []byte("B"), []byte("A"), []byte("234")}) - checkQuery(t, stub, "A", "678") - checkQuery(t, stub, "B", "567") - checkQuery(t, stub, "A", "678") - checkQuery(t, stub, "B", "567") -} diff --git a/chaincode/chaincode_example02/chaincode_example02.go b/chaincode/chaincode_example02/go/chaincode_example02.go similarity index 100% rename from chaincode/chaincode_example02/chaincode_example02.go rename to chaincode/chaincode_example02/go/chaincode_example02.go diff --git a/chaincode/chaincode_example02/node/chaincode_example02.js b/chaincode/chaincode_example02/node/chaincode_example02.js new file mode 100644 index 0000000000..545092aff0 --- /dev/null +++ b/chaincode/chaincode_example02/node/chaincode_example02.js @@ -0,0 +1,138 @@ +/* +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +*/ + +const shim = require('fabric-shim'); +const util = require('util'); + +var Chaincode = class { + + // Initialize the chaincode + async Init(stub) { + console.info('========= example02 Init ========='); + let ret = stub.getFunctionAndParameters(); + console.info(ret); + let args = ret.params; + // initialise only if 4 parameters passed. + if (args.length != 4) { + return shim.error('Incorrect number of arguments. Expecting 4'); + } + + let A = args[0]; + let B = args[2]; + let Aval = args[1]; + let Bval = args[3]; + + if (typeof parseInt(Aval) !== 'number' || typeof parseInt(Bval) !== 'number') { + return shim.error('Expecting integer value for asset holding'); + } + + try { + await stub.putState(A, Buffer.from(Aval)); + try { + await stub.putState(B, Buffer.from(Bval)); + return shim.success(); + } catch (err) { + return shim.error(err); + } + } catch (err) { + return shim.error(err); + } + } + + async Invoke(stub) { + let ret = stub.getFunctionAndParameters(); + console.info(ret); + let method = this[ret.fcn]; + if (!method) { + console.log('no method of name:' + ret.fcn + ' found'); + return shim.success(); + } + try { + let payload = await method(stub, ret.params); + return shim.success(payload); + } catch (err) { + console.log(err); + return shim.error(err); + } + } + + async invoke(stub, args) { + if (args.length != 3) { + throw new Error('Incorrect number of arguments. Expecting 3'); + } + + let A = args[0]; + let B = args[1]; + if (!A || !B) { + throw new Error('asset holding must not be empty'); + } + + // Get the state from the ledger + let Avalbytes = await stub.getState(A); + if (!Avalbytes) { + throw new Error('Failed to get state of asset holder A'); + } + let Aval = parseInt(Avalbytes.toString()); + + let Bvalbytes = await stub.getState(B); + if (!Bvalbytes) { + throw new Error('Failed to get state of asset holder B'); + } + + let Bval = parseInt(Bvalbytes.toString()); + // Perform the execution + let amount = parseInt(args[2]); + if (typeof amount !== 'number') { + throw new Error('Expecting integer value for amount to be transaferred'); + } + + Aval = Aval - amount; + Bval = Bval + amount; + console.info(util.format('Aval = %d, Bval = %d\n', Aval, Bval)); + + // Write the states back to the ledger + await stub.putState(A, Buffer.from(Aval.toString())); + await stub.putState(B, Buffer.from(Bval.toString())); + + } + + // Deletes an entity from state + async delete(stub, args) { + if (args.length != 1) { + throw new Error('Incorrect number of arguments. Expecting 1'); + } + + let A = args[0]; + + // Delete the key from the state in ledger + await stub.deleteState(A); + } + + // query callback representing the query of a chaincode + async query(stub, args) { + if (args.length != 1) { + throw new Error('Incorrect number of arguments. Expecting name of the person to query') + } + + let jsonResp = {}; + let A = args[0]; + + // Get the state from the ledger + let Avalbytes = await stub.getState(A); + if (!Avalbytes) { + jsonResp.error = 'Failed to get state for ' + A; + throw new Error(JSON.stringify(jsonResp)); + } + + jsonResp.name = A; + jsonResp.amount = Avalbytes.toString(); + console.info('Query Response:'); + console.info(jsonResp); + return Avalbytes; + } +}; + +shim.start(new Chaincode()); diff --git a/chaincode/chaincode_example02/node/package.json b/chaincode/chaincode_example02/node/package.json new file mode 100644 index 0000000000..2dbefae2ab --- /dev/null +++ b/chaincode/chaincode_example02/node/package.json @@ -0,0 +1,15 @@ +{ + "name": "chaincode_example02", + "version": "1.0.0", + "description": "chaincode_example02 chaincode implemented in node.js", + "engines": { + "node": ">=8.4.0", + "npm": ">=5.3.0" + }, + "scripts": { "start" : "node chaincode_example02.js" }, + "engine-strict": true, + "license": "Apache-2.0", + "dependencies": { + "fabric-shim": "unstable" + } +} diff --git a/chaincode/fabcar/fabcar.go b/chaincode/fabcar/go/fabcar.go similarity index 100% rename from chaincode/fabcar/fabcar.go rename to chaincode/fabcar/go/fabcar.go diff --git a/chaincode/fabcar/node/fabcar.js b/chaincode/fabcar/node/fabcar.js new file mode 100644 index 0000000000..f689afb435 --- /dev/null +++ b/chaincode/fabcar/node/fabcar.js @@ -0,0 +1,193 @@ +/* +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +*/ + +'use strict'; +const shim = require('fabric-shim'); +const util = require('util'); + +let Chaincode = class { + + // The Init method is called when the Smart Contract 'fabcar' is instantiated by the blockchain network + // Best practice is to have any Ledger initialization in separate function -- see initLedger() + async Init(stub) { + console.info('=========== Instantiated fabcar chaincode ==========='); + return shim.success(); + } + + // The Invoke method is called as a result of an application request to run the Smart Contract + // 'fabcar'. The calling application program has also specified the particular smart contract + // function to be called, with arguments + async Invoke(stub) { + let ret = stub.getFunctionAndParameters(); + console.info(ret); + + let method = this[ret.fcn]; + if (!method) { + console.error('no function of name:' + ret.fcn + ' found'); + throw new Error('Received unknown function ' + ret.fcn + ' invocation'); + } + try { + let payload = await method(stub, ret.params); + return shim.success(payload); + } catch (err) { + console.log(err); + return shim.error(err); + } + } + + async queryCar(stub, args) { + if (args.length != 1) { + throw new Error('Incorrect number of arguments. Expecting CarNumber ex: CAR01'); + } + let carNumber = args[0]; + + let carAsBytes = await stub.getState(carNumber); //get the car from chaincode state + if (!carAsBytes || carAsBytes.toString().length <= 0) { + throw new Error(carNumber + ' does not exist: '); + } + console.log(carAsBytes.toString()); + return carAsBytes; + } + + async initLedger(stub, args) { + console.info('============= START : Initialize Ledger ==========='); + let cars = []; + cars.push({ + make: 'Toyota', + model: 'Prius', + color: 'blue', + owner: 'Tomoko' + }); + cars.push({ + make: 'Ford', + model: 'Mustang', + color: 'red', + owner: 'Brad' + }); + cars.push({ + make: 'Hyundai', + model: 'Tucson', + color: 'green', + owner: 'Jin Soo' + }); + cars.push({ + make: 'Volkswagen', + model: 'Passat', + color: 'yellow', + owner: 'Max' + }); + cars.push({ + make: 'Tesla', + model: 'S', + color: 'black', + owner: 'Adriana' + }); + cars.push({ + make: 'Peugeot', + model: '205', + color: 'purple', + owner: 'Michel' + }); + cars.push({ + make: 'Chery', + model: 'S22L', + color: 'white', + owner: 'Aarav' + }); + cars.push({ + make: 'Fiat', + model: 'Punto', + color: 'violet', + owner: 'Pari' + }); + cars.push({ + make: 'Tata', + model: 'Nano', + color: 'indigo', + owner: 'Valeria' + }); + cars.push({ + make: 'Holden', + model: 'Barina', + color: 'brown', + owner: 'Shotaro' + }); + + for (let i = 0; i < cars.length; i++) { + cars[i].docType = 'car'; + await stub.putState('CAR' + i, Buffer.from(JSON.stringify(cars[i]))); + console.info('Added <--> ', cars[i]); + } + console.info('============= END : Initialize Ledger ==========='); + } + + async createCar(stub, args) { + console.info('============= START : Create Car ==========='); + if (args.length != 5) { + throw new Error('Incorrect number of arguments. Expecting 5'); + } + + var car = { + docType: 'car', + make: args[1], + model: args[2], + color: args[3], + owner: args[4] + }; + + await stub.putState(args[0], Buffer.from(JSON.stringify(car))); + console.info('============= END : Create Car ==========='); + } + + async queryAllCars(stub, args) { + + let startKey = 'CAR0'; + let endKey = 'CAR999'; + + let iterator = await stub.getStateByRange(startKey, endKey); + + let allResults = []; + while (true) { + let res = await iterator.next(); + + if (res.value && res.value.value.toString()) { + let jsonRes = {}; + console.log(res.value.value.toString('utf8')); + + 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 Buffer.from(JSON.stringify(allResults)); + } + } + } + + async changeCarowner(stub, args) { + console.info('============= START : changeCarowner ==========='); + if (args.length != 2) { + throw new Error('Incorrect number of arguments. Expecting 2'); + } + + let carAsBytes = await stub.getState(args[0]); + let car = JSON.parse(carAsBytes); + car.owner = args[1]; + + await stub.putState(args[0], Buffer.from(JSON.stringify(car))); + console.info('============= END : changeCarowner ==========='); + } +}; + +shim.start(new Chaincode()); diff --git a/chaincode/fabcar/node/package.json b/chaincode/fabcar/node/package.json new file mode 100644 index 0000000000..34019658cc --- /dev/null +++ b/chaincode/fabcar/node/package.json @@ -0,0 +1,15 @@ +{ + "name": "fabcar", + "version": "1.0.0", + "description": "fabcar chaincode implemented in node.js", + "engines": { + "node": ">=8.4.0", + "npm": ">=5.3.0" + }, + "scripts": { "start" : "node fabcar.js" }, + "engine-strict": true, + "license": "Apache-2.0", + "dependencies": { + "fabric-shim": "unstable" + } +} diff --git a/chaincode/marbles02/marbles_chaincode.go b/chaincode/marbles02/go/marbles_chaincode.go similarity index 100% rename from chaincode/marbles02/marbles_chaincode.go rename to chaincode/marbles02/go/marbles_chaincode.go diff --git a/chaincode/marbles02/node/marbles_chaincode.js b/chaincode/marbles02/node/marbles_chaincode.js new file mode 100644 index 0000000000..60484df051 --- /dev/null +++ b/chaincode/marbles02/node/marbles_chaincode.js @@ -0,0 +1,409 @@ +/* +# Copyright IBM Corp. All Rights Reserved. +# +# SPDX-License-Identifier: Apache-2.0 +*/ + +// ====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\"}}"]}' + +'use strict'; +const shim = require('fabric-shim'); +const util = require('util'); + +let Chaincode = class { + async Init(stub) { + let ret = stub.getFunctionAndParameters(); + console.info(ret); + console.info('=========== Instantiated Marbles Chaincode ==========='); + return shim.success(); + } + + 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); + } + } + + // =============================================== + // initMarble - create a new marble + // =============================================== + async initMarble(stub, args, thisClass) { + if (args.length != 4) { + throw new Error('Incorrect number of arguments. Expecting 4'); + } + // ==== Input sanitation ==== + console.info('--- start init marble ---') + if (args[0].lenth <= 0) { + throw new Error('1st argument must be a non-empty string'); + } + if (args[1].lenth <= 0) { + throw new Error('2nd argument must be a non-empty string'); + } + if (args[2].lenth <= 0) { + throw new Error('3rd argument must be a non-empty string'); + } + if (args[3].lenth <= 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 (typeof size !== 'number') { + 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'); + } + + // =============================================== + // readMarble - read a marble from chaincode state + // =============================================== + async readMarble(stub, args, thisClass) { + 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; + } + + // ================================================== + // delete - remove a marble key/value pair from state + // ================================================== + async delete(stub, args, thisClass) { + 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: ' + name; + 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 + // =========================================================== + async transferMarble(stub, args, thisClass) { + // 0 1 + // 'name', 'bob' + if (args.length < 2) { + throw new Error('Incorrect number of arguments. Expecting marblename 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)'); + } + + // =========================================================================================== + // 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. + // =========================================================================================== + async getMarblesByRange(stub, args, thisClass) { + + 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 method = thisClass['getAllResults']; + let results = await method(resultsIterator, false); + + return Buffer.from(JSON.stringify(results)); + } + + // ==== 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. + // =========================================================================================== + async transferMarblesBasedOnColor(stub, args, thisClass) { + + // 0 1 + // 'color', 'bob' + 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 method = thisClass['transferMarble']; + // Iterate through result set and for each marble found, transfer to newOwner + while (true) { + let responseRange = await coloredMarbleResultsIterator.next(); + 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 + let response = await method(stub, [returnedMarbleName, newOwner]); + } + + let responsePayload = util.format('Transferred %s marbles to %s', color, newOwner); + console.info('- end transferMarblesBasedOnColor: ' + responsePayload); + } + + + // ===== 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) + // ========================================================================================= + async queryMarblesByOwner(stub, args, thisClass) { + // 0 + // 'bob' + 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; + let method = thisClass['getQueryResultForQueryString']; + let queryResults = await method(stub, JSON.stringify(queryString), thisClass); + return queryResults; //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) + // ========================================================================================= + async queryMarbles(stub, args, thisClass) { + // 0 + // 'queryString' + 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'); + } + let method = thisClass['getQueryResultForQueryString']; + let queryResults = await method(stub, queryString, thisClass); + return queryResults; + } + + async getAllResults(iterator, isHistory) { + let allResults = []; + while (true) { + let res = await iterator.next(); + + 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; + } + } + } + + // ========================================================================================= + // getQueryResultForQueryString executes the passed in query string. + // Result set is built and returned as a byte array containing the JSON results. + // ========================================================================================= + async getQueryResultForQueryString(stub, queryString, thisClass) { + + console.info('- getQueryResultForQueryString queryString:\n' + queryString) + let resultsIterator = await stub.getQueryResult(queryString); + let method = thisClass['getAllResults']; + + let results = await method(resultsIterator, false); + + return Buffer.from(JSON.stringify(results)); + } + + async getHistoryForMarble(stub, args, thisClass) { + + 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 method = thisClass['getAllResults']; + let results = await method(resultsIterator, true); + + return Buffer.from(JSON.stringify(results)); + } +}; + +shim.start(new Chaincode()); diff --git a/chaincode/marbles02/node/package.json b/chaincode/marbles02/node/package.json new file mode 100644 index 0000000000..a622f00ecd --- /dev/null +++ b/chaincode/marbles02/node/package.json @@ -0,0 +1,15 @@ +{ + "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_chaincode.js" }, + "engine-strict": true, + "license": "Apache-2.0", + "dependencies": { + "fabric-shim": "unstable" + } +} diff --git a/fabcar/package.json b/fabcar/package.json index bc2647500a..481773ab0f 100644 --- a/fabcar/package.json +++ b/fabcar/package.json @@ -7,8 +7,8 @@ "test": "echo \"Error: no test specified\" && exit 1" }, "dependencies": { - "fabric-ca-client": "^1.0.1", - "fabric-client": "^1.0.1" + "fabric-ca-client": "unstable", + "fabric-client": "unstable" }, "author": "Anthony O'Dowd", "license": "Apache-2.0", diff --git a/fabcar/startFabric.sh b/fabcar/startFabric.sh index fd76336733..a9e68b142a 100755 --- a/fabcar/startFabric.sh +++ b/fabcar/startFabric.sh @@ -9,8 +9,12 @@ set -e # don't rewrite paths for Windows Git Bash users export MSYS_NO_PATHCONV=1 - starttime=$(date +%s) +LANGUAGE=${1:-"golang"} +CC_SRC_PATH=github.com/fabcar/go +if [ "$LANGUAGE" = "node" -o "$LANGUAGE" = "NODE" ]; then + CC_SRC_PATH=/opt/gopath/src/github.com/fabcar/node +fi if [ ! -d ~/.hfc-key-store/ ]; then mkdir ~/.hfc-key-store/ @@ -24,8 +28,8 @@ cd ../basic-network # and prime the ledger with our 10 cars docker-compose -f ./docker-compose.yml up -d cli -docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode install -n fabcar -v 1.0 -p github.com/fabcar -docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fabcar -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')" +docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode install -n fabcar -v 1.0 -p "$CC_SRC_PATH" -l "$LANGUAGE" +docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n fabcar -l "$LANGUAGE" -v 1.0 -c '{"Args":[""]}' -P "OR ('Org1MSP.member','Org2MSP.member')" sleep 10 docker exec -e "CORE_PEER_LOCALMSPID=Org1MSP" -e "CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp" cli peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n fabcar -c '{"function":"initLedger","Args":[""]}' diff --git a/first-network/byfn.sh b/first-network/byfn.sh index 79b3c951b6..6588e93f54 100755 --- a/first-network/byfn.sh +++ b/first-network/byfn.sh @@ -47,12 +47,14 @@ function printHelp () { echo " -d - delay duration in seconds (defaults to 3)" echo " -f - specify which docker-compose file use (defaults to docker-compose-cli.yaml)" echo " -s - the database backend to use: goleveldb (default) or couchdb" + echo " -l - the chaincode language: golang (default) or node" echo echo "Typically, one would first generate the required certificates and " echo "genesis block, then bring up the network. e.g.:" echo echo " byfn.sh -m generate -c mychannel" echo " byfn.sh -m up -c mychannel -s couchdb" + echo " byfn.sh -m up -l node" echo " byfn.sh -m down -c mychannel" echo echo "Taking all defaults:" @@ -111,9 +113,9 @@ function networkUp () { generateChannelArtifacts fi if [ "${IF_COUCHDB}" == "couchdb" ]; then - CHANNEL_NAME=$CHANNEL_NAME TIMEOUT=$CLI_TIMEOUT DELAY=$CLI_DELAY docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_COUCH up -d 2>&1 + CHANNEL_NAME=$CHANNEL_NAME TIMEOUT=$CLI_TIMEOUT DELAY=$CLI_DELAY LANG=$LANGUAGE docker-compose -f $COMPOSE_FILE -f $COMPOSE_FILE_COUCH up -d 2>&1 else - CHANNEL_NAME=$CHANNEL_NAME TIMEOUT=$CLI_TIMEOUT DELAY=$CLI_DELAY docker-compose -f $COMPOSE_FILE up -d 2>&1 + CHANNEL_NAME=$CHANNEL_NAME TIMEOUT=$CLI_TIMEOUT DELAY=$CLI_DELAY LANG=$LANGUAGE docker-compose -f $COMPOSE_FILE up -d 2>&1 fi if [ $? -ne 0 ]; then echo "ERROR !!!! Unable to start network" @@ -312,9 +314,10 @@ CHANNEL_NAME="mychannel" COMPOSE_FILE=docker-compose-cli.yaml # COMPOSE_FILE_COUCH=docker-compose-couch.yaml - +# use golang as the default language for chaincode +LANGUAGE=golang # Parse commandline args -while getopts "h?m:c:t:d:f:s:" opt; do +while getopts "h?m:c:t:d:f:s:l:" opt; do case "$opt" in h|\?) printHelp @@ -332,6 +335,8 @@ while getopts "h?m:c:t:d:f:s:" opt; do ;; s) IF_COUCHDB=$OPTARG ;; + l) LANGUAGE=$OPTARG + ;; esac done diff --git a/first-network/docker-compose-cli.yaml b/first-network/docker-compose-cli.yaml index 25e64800f1..6b7d1ce9fa 100644 --- a/first-network/docker-compose-cli.yaml +++ b/first-network/docker-compose-cli.yaml @@ -67,10 +67,10 @@ services: - CORE_PEER_TLS_ROOTCERT_FILE=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt - CORE_PEER_MSPCONFIGPATH=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp working_dir: /opt/gopath/src/github.com/hyperledger/fabric/peer - command: /bin/bash -c './scripts/script.sh ${CHANNEL_NAME} ${DELAY}; sleep $TIMEOUT' + command: /bin/bash -c './scripts/script.sh ${CHANNEL_NAME} ${DELAY} ${LANG}; sleep $TIMEOUT' volumes: - /var/run/:/host/var/run/ - - ./../chaincode/:/opt/gopath/src/github.com/hyperledger/fabric/examples/chaincode/go + - ./../chaincode/:/opt/gopath/src/github.com/ - ./crypto-config:/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ - ./scripts:/opt/gopath/src/github.com/hyperledger/fabric/peer/scripts/ - ./channel-artifacts:/opt/gopath/src/github.com/hyperledger/fabric/peer/channel-artifacts @@ -82,4 +82,3 @@ services: - peer1.org2.example.com networks: - byfn - diff --git a/first-network/scripts/script.sh b/first-network/scripts/script.sh index 2d74e34a31..8b01f67a1c 100755 --- a/first-network/scripts/script.sh +++ b/first-network/scripts/script.sh @@ -11,12 +11,20 @@ echo "Build your first network (BYFN) end-to-end test" echo CHANNEL_NAME="$1" DELAY="$2" +LANGUAGE="$3" : ${CHANNEL_NAME:="mychannel"} : ${TIMEOUT:="60"} +: ${LANGUAGE:="golang"} +LANGUAGE=`echo "$LANGUAGE" | tr [:upper:] [:lower:]` COUNTER=1 MAX_RETRY=5 ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem +CC_SRC_PATH="github.com/chaincode_example02/go/" +if [ "$LANGUAGE" = "node" ]; then + CC_SRC_PATH="/opt/gopath/src/github.com/chaincode_example02/node/" +fi + echo "Channel name : "$CHANNEL_NAME # verify the result of the end-to-end test @@ -115,7 +123,7 @@ joinChannel () { installChaincode () { PEER=$1 setGlobals $PEER - peer chaincode install -n mycc -v 1.0 -p github.com/hyperledger/fabric/examples/chaincode/go/chaincode_example02 >&log.txt + peer chaincode install -n mycc -v 1.0 -l ${LANGUAGE} -p ${CC_SRC_PATH} >&log.txt res=$? cat log.txt verifyResult $res "Chaincode installation on remote peer PEER$PEER has Failed" @@ -129,9 +137,9 @@ instantiateChaincode () { # while 'peer chaincode' command can get the orderer endpoint from the peer (if join was successful), # lets supply it directly as we know it using the "-o" option if [ -z "$CORE_PEER_TLS_ENABLED" -o "$CORE_PEER_TLS_ENABLED" = "false" ]; then - peer chaincode instantiate -o orderer.example.com:7050 -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')" >&log.txt + peer chaincode instantiate -o orderer.example.com:7050 -C $CHANNEL_NAME -n mycc -l ${LANGUAGE} -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')" >&log.txt else - peer chaincode instantiate -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')" >&log.txt + peer chaincode instantiate -o orderer.example.com:7050 --tls $CORE_PEER_TLS_ENABLED --cafile $ORDERER_CA -C $CHANNEL_NAME -n mycc -l ${LANGUAGE} -v 1.0 -c '{"Args":["init","a","100","b","200"]}' -P "OR ('Org1MSP.member','Org2MSP.member')" >&log.txt fi res=$? cat log.txt