链码Node.js工程介绍
在Chaincode代码中主要包括package.json、index.js、lib目录以及test目录
链码Node.js工程package.json
- 1. 主要依赖:fabric-contract-api、fabric-shim
- 2. 测试主要依赖:mochai、chai、sinon
- 3. 测试方法:npm run test
- 4. 运行方法:npm run start
chaincode开发指南
1. 核心功能fabric-contract-api、fabric-shim
2. 代码测试不需要连接区块链 测试过程使用sinon.js模拟
Chaincode对应文件创建
在lib目录创建assetTransfer.js
初始化配置:
定义Contract类
定义AssetTransfer类
使用module.exports将AssetTransfer设置成对外访问模块
定义InitLedger功能
功能:初始化链码中数据,使用async将方法定义为异步执行。首先定义assets变量,再通过for循环加 载至区块链中
1.定义assets变量在其中加入模拟资产数据
2.使用for循环加载进区块链
- (1)使用ctx.stub.putState(使用await获取异步执行结果) 添加至区块链,主键为ID
- (2)加载进区块链时数据需要转化为Buffer类型
- (3)使用'json-stringify-deterministic' 和'sort-keys
定义CreateAsset功能
功能:创建新数据到区块链,使用async将方法定义为异步执行。首先判断数据ID是否在链中存在,接下 来添加数据,最后通过json返回数据
- 1. 使用ctx.stub.putState(使用await获取异步执行结果)添加至区块链,主键为ID
- 2. 加载进区块链时数据需要转化为Buffer类型
- 3. 使用'json-stringify-deterministic' 和'sort-keys-recursive'两个功能保证数据顺序
定义ReadAsset功能
功能:通过Asset的ID查询资产数据,以JSON格式返回
1. 通过ctx.stub.getState获取数据,查询参数为Asset的ID
2. 数据通过JSON返回
定义AssetExists功能
功能:通过Asset的ID判断资产是否存在
1. 通过ctx.stub.getState获取数据,查询参数为Asset的ID 2. 数据通过JSON返回
定义DeleteAsset功能
功能:通过Asset的ID删除数据。删除前需要判断资产是否存在
1. 通过ctx.stub.getState获取数据,查询参数为Asset的ID 2. 数据通过JSON返回
创建index.js并添加代码
/*
* Copyright IBM Corp. All Rights Reserved.
*
* SPDX-License-Identifier: Apache-2.0
*/
'use strict';
const assetTransfer = require('./lib/assetTransfer')
module.exports.AssetTransfer = assetTransfer;
module.exports.contracts = [assetTransfer];
安装fabric依赖
npm设置国内源:npm config set registry https://registry.npmmirror.com
运行以下代码
npm install fabric-contract-api@2.4.2 fabric-shim@2.4.2
验证fabric依赖安装情况
查看package.json,将有如下变化:
安装测试框架依赖
npm install -D eslint mocha sinon chai sinon-chai
添加测试文件assetTransfer.test.js
'use strict';
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const expect = chai.expect;
const { Context } = require('fabric-contract-api');
const { ChaincodeStub } = require('fabric-shim');
const AssetTransfer = require('../lib/assetTransfer.js');
let assert = sinon.assert;
chai.use(sinonChai);
describe('Asset Transfer Basic Tests', () => {
let transactionContext, chaincodeStub, asset;
beforeEach(() => {
transactionContext = new Context();
chaincodeStub = sinon.createStubInstance(ChaincodeStub);
transactionContext.setChaincodeStub(chaincodeStub);
chaincodeStub.putState.callsFake((key, value) => {
if (!chaincodeStub.states) {
chaincodeStub.states = {};
}
chaincodeStub.states[key] = value;
});
chaincodeStub.getState.callsFake(async (key) => {
let ret;
if (chaincodeStub.states) {
ret = chaincodeStub.states[key];
}
return Promise.resolve(ret);
});
chaincodeStub.deleteState.callsFake(async (key) => {
if (chaincodeStub.states) {
delete chaincodeStub.states[key];
}
return Promise.resolve(key);
});
chaincodeStub.getStateByRange.callsFake(async () => {
function* internalGetStateByRange() {
if (chaincodeStub.states) {
const copied = Object.assign({}, chaincodeStub.states);
for (let key in copied) {
yield { value: copied[key] };
}
}
}
return Promise.resolve(internalGetStateByRange());
});
asset = {
ID: "asset1",
Color: "blue",
Size: 5,
Owner: "Tomoko",
ApprasisedValue: 300,
};
});
describe('Test InitLedger', () => {
it('should return success on InitLedger', async () => {
let assetTransfer = new AssetTransfer();
await assetTransfer.InitLedger(transactionContext);
let ret = JSON.parse((await chaincodeStub.getState('asset1')).toString());
expect(ret).to.eql(Object.assign({ docType: 'asset' }, asset));
});
});
});
运行npm命令获取测试结果
征信积分链码开发(一) (Nodejs版)
背景介绍
在市场行为中存在企业吸引融资,银行贷款等金融行为。政府部门包括法院、税务局、市场监督管 理局等机构为了保证市场良好运行,需要对企业、银行等机构进行监督。故而在可以构建法院、税 务局、市场监督管理局为基础的信用积分规则,用于准确评定企业机构信用情况。为了能够对信用 情况进行标准化,采用积分的形式,对企业的信用进行打分。分数越高表示信用情况越好。反之, 信用分数越低则代表信用越差。他们制定的信用积分规则如下。
构建区块链目标
构建联盟链分别包括法院、税务局、市场监督管理局等机构,通过积分上链形式用于评估对应机构 的信用情况
开发链码征信数据 管理功能
在windows环境中创建credit_chaincode目录,使用VSCode打开credit_chaincode并初始化
npm init -y
准备package.json
添加.eslintrc.js
'use strict';
module.exports = {
env: {
node: true,
mocha: true,
es6: true
},
"extends": "eslint:recommended",
"overrides": [
{
"env": {
"node": true
},
"files": [
".eslintrc.{js,cjs}"
],
"parserOptions": {
"sourceType": "script"
}
}
],
"parserOptions": {
"ecmaVersion": "latest"
},
"rules": {
}
}
编写项目入口index.js
'use strict';
const creditContract = require('./lib/creditChaincode');
module.exports.CreditContract = creditContract;
module.exports.contracts = [creditContract];
征信数据上链和查询功能
在VSCode对应项目中创建lib目录,并在其中加入creditChaincode.js文件,在文件中加入以下代码用于 实现数据的上传与查询
'use strict';
const { Contract } = require('fabric-contract-api');
class CreditContract extends Contract {
//新建征信主体
async createCreditSubject(ctx, key, name, type) {
console.info('=== START : 创建征信主体 ===');
const subject = {
key: key, name: name,
type: type, score: 0
};
await ctx.stub.putState(key, Buffer.from(JSON.stringify(subject)));
console.info('=== END : 创建征信主体 ===');
return subject;
}
// 查询征信主体
async queryCreditSubject(ctx, subjectKey) {
console.info('=== START : 查询征信主体 ===');
const bytes = await ctx.stub.getState(subjectKey);
if (!bytes || bytes.length === 0) {
const msg = `${subjectKey} 征信主体不存在`;
console.warn(msg);
throw new Error(msg);
}
const subject = JSON.parse(bytes.toString());
console.info('=== END : 查询征信主体 ===');
return subject;
}
// 创建组合键
async _createCompositeKey(ctx, indexName, key) {
if (!key || key === "") {
throw new Error(`Key 不能为空`);
}
if (indexName === "") {
return key;
}
return ctx.stub.createCompositeKey(indexName, [key]);
}
// 创建征信主体
async createCreditSubjectEx(ctx, key, name, type) {
console.info('============= START : 创建征信主体=========== ');
const subject = {
key: key,
name: name,
type: type,
score: 0
};
let indexName = 'name~type';
let compositeKey = this._createCompositeKey(ctx, indexName, [name, type]);
await ctx.stub.putState(compositeKey, Buffer.from(JSON.stringify(subject)));
console.info('============= END : 创建征信主体=========== ');
return subject;
}
// 按照name+type查询征信主体
async queryCreditByNameType(ctx, name, type) {
console.info('============= START : 查询征信主体===========');
const CompositeKey = this._createCompositeKey(ctx, 'name~type', [
name,
type,
]);
const bytes = await ctx.stub.getState(CompositeKey);
if (!bytes || bytes.length === 0) {
const msg = `${name} ${type} 征信主体不存在`;
console.warn(msg);
throw new Error(msg);
}
const subject = JSON.parse(bytes.toString());
console.info('============= END : 查询征信主体===========');
return subject;
}
//将状态的iterator转换成JSON
async _Iterator2Json(iterator) {
let allResults = [];
let res = await iterator.next();
while (!res.done) {
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);
}
res = await iterator.next();
}
iterator.close();
return allResults;
}
// 查询征信主体
async queryCreditByRange(ctx, startKey, endKey) {
let resultsIterator = await ctx.stub.getStateByRange(startKey, endKey);
let results = await this._Iterator2Json(resultsIterator, false);
return JSON.stringify(results);
}
}
module.exports = CreditContract;
安装单元测试包
使用如下具体命令安装:
npm install -D eslint mocha sinon chai sinon-chai
安装fabric依赖的npm包
使用如下具体命令安装:
npm install fabric-contract-api@2.4.2 fabric-shim@2.4.2
编写测试用例
'use strict';
const sinon = require('sinon');
const chai = require('chai');
const sinonChai = require('sinon-chai');
const expect = chai.expect;
chai.use(sinonChai);
let assert = sinon.assert;
const { Context } = require('fabric-contract-api');
const { ChaincodeStub, ClientIdentity } = require('fabric-shim');
const CreditContract = require('../lib/creditChaincode.js');
describe('Credit Chaincode Test', () => {
let stub, ctx, ClientId;
beforeEach(() => {
ctx = new Context();
stub = sinon.createStubInstance(ChaincodeStub);
stub.getMspID.returns('Org1');
ctx.setChaincodeStub(stub);
ClientId = sinon.createStubInstance(ClientIdentity);
stub.putState.callsFake((key, value) => {
if (!stub.states) {
stub.states = {};
}
stub.states[key] = value;
});
stub.getState.callsFake(async (key) => {
let ret;
if (stub.states) {
ret = stub.states[key];
}
return Promise.resolve(ret);
});
stub.deleteState.callsFake(async (key) => {
if (stub.states) {
delete stub.states[key];
}
});
});
describe('Test CreditSubject function', () => {
it('should return success on createCreditSubject', async () => {
let creditContract = new CreditContract();
let creditSubject = await creditContract.createCreditSubject(ctx, "A001", "My Company", "Company");
let scroe = creditSubject.score;
expect(scroe).to.equals(0);
});
it('should return success on queryCreditSubject', async () => {
let creditContract = new CreditContract();
await creditContract.createCreditSubject(ctx, "A001", "My Company", "Company");
let creditSubject = await creditContract.queryCreditSubject(ctx, "A001");
let name = creditSubject.name;
expect(name).to.equals("My Company");
});
it('should return success on createCreditSubjectEx', async () => {
let creditContract = new CreditContract();
let creditSubject = await creditContract.createCreditSubjectEx(ctx, "A001", "My Company", "Company");
let scroe = creditSubject.score;
expect(scroe).to.equals(0);
});
it('should return success on queryCreditByNameType', async () => {
let creditContract = new CreditContract();
await creditContract.createCreditSubjectEx(ctx, "A001", "My Company", "Company");
let creditSubject = await creditContract.queryCreditByNameType(ctx, "My Company", "Company");
let name = creditSubject.name;
expect(name).to.equals("My Company");
})
});
})
执行测试
执行以下命令,进行测试:
npm run test
部署管理征信链码
链码操作准备
拷贝credit_chaincode到chaincode目录下,前提需要把项目中的配置文件删掉
打包链码
启动容器
docker-compose -f fabric-compose.yaml start
查看
进入上级test目录运行以下命令打包:
export FABRIC_CFG_PATH=${PWD}/config
peer lifecycle chaincode package ./chaincode/credit_chaincode.tar.gz --path ./chaincode/credit_chaincode --lang node --label credit_chaincode_1.1
检查打包结果
安装链码
运行以下进入fabric-cli容器:
docker exec -it fabric-cli bash
1. 在org1中安装 运行以下链码安装:
. scripts/set-env.sh 1 0 7051
peer lifecycle chaincode install chaincode/credit_chaincode.tar.gz
2. 在org2中安装 运行以下链码安装:
. scripts/set-env.sh 2 0 9051
peer lifecycle chaincode install chaincode/credit_chaincode.tar.gz
3. 查看安装情况
peer lifecycle chaincode queryinstalled
批准链码
org1批准链码
1. 设置链码环境变量(ID为上面安装的链码)
export CC_PACKAGE_ID=credit_chaincode_1.1:2c3741f2faf6862eecd3f3c0f7296bbba1d93cf4302cca64e79a7689fba47a23
2. 设置Org1环境变量
. scripts/set-env.sh 1 0 7051
3.批准链码
peer lifecycle chaincode approveformyorg -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode --version 1.1 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA
org2批准链码
1. 设置Org2环境变量
. scripts/set-env.sh 2 0 9051
2.批准链码
peer lifecycle chaincode approveformyorg -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode --version 1.1 --package-id $CC_PACKAGE_ID --sequence 1 --tls --cafile $ORDERER_CA
检查提交准备
peer lifecycle chaincode checkcommitreadiness --channelID $CHANNEL_NAME --name credit_chaincode --version 1.1 --sequence 1 --tls --cafile $ORDERER_CA --output json
提交链码
提交链码是特殊交易,需要背书节点背书,因此需要指定2个背书节点。具体操作如下:
peer lifecycle chaincode commit -o orderer.example.com:7050 --ordererTLSHostnameOverride orderer.example.com --channelID $CHANNEL_NAME --name credit_chaincode --version 1.1 --sequence 1 --tls --cafile $ORDERER_CA --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles $PEER0_ORG1_CA --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles $PEER0_ORG2_CA
查询提交的链码
peer lifecycle chaincode querycommitted --channelID $CHANNEL_NAME --name credit_chaincode --tls --cafile $ORDERER_CA
使用docker查看链码安装情况
docker ps
查看运行镜像形成容器情况
docker logs -f b1ddd21303a5
测试验证
调用createCreditSubject功能
peer chaincode invoke -o orderer.example.com:7050 --tls --cafile $ORDERER_CA --channelID $CHANNEL_NAME --name credit_chaincode --peerAddresses peer0.org1.example.com:7051 --tlsRootCertFiles $PEER0_ORG1_CA --peerAddresses peer0.org2.example.com:9051 --tlsRootCertFiles $PEER0_ORG2_CA -c '{"function":"createCreditSubject", "Args":["A001","My Company","Company"]}'
调用queryCreditSubject功能
peer chaincode query -C $CHANNEL_NAME --name credit_chaincode -c '{"function":"queryCreditSubject","Args":["A001"]}'
观察正在运行的链码容器运行情况