mirror of
https://github.com/hyperledger/fabric-samples.git
synced 2026-06-17 15:35:09 +00:00
Merge "[FAB-5898] porting samples to node.js chaincode"
This commit is contained in:
commit
cd1b691385
25 changed files with 1081 additions and 155 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -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 <language> - 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
138
chaincode/chaincode_example02/node/chaincode_example02.js
Normal file
138
chaincode/chaincode_example02/node/chaincode_example02.js
Normal file
|
|
@ -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());
|
||||
15
chaincode/chaincode_example02/node/package.json
Normal file
15
chaincode/chaincode_example02/node/package.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
193
chaincode/fabcar/node/fabcar.js
Normal file
193
chaincode/fabcar/node/fabcar.js
Normal file
|
|
@ -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());
|
||||
15
chaincode/fabcar/node/package.json
Normal file
15
chaincode/fabcar/node/package.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
409
chaincode/marbles02/node/marbles_chaincode.js
Normal file
409
chaincode/marbles02/node/marbles_chaincode.js
Normal file
|
|
@ -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());
|
||||
15
chaincode/marbles02/node/package.json
Normal file
15
chaincode/marbles02/node/package.json
Normal file
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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":[""]}'
|
||||
|
||||
|
|
|
|||
|
|
@ -47,12 +47,14 @@ function printHelp () {
|
|||
echo " -d <delay> - delay duration in seconds (defaults to 3)"
|
||||
echo " -f <docker-compose-file> - specify which docker-compose file use (defaults to docker-compose-cli.yaml)"
|
||||
echo " -s <dbtype> - the database backend to use: goleveldb (default) or couchdb"
|
||||
echo " -l <language> - 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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in a new issue