0 导言
智能合约是区块链中一个非常重要的概念和组成部分。在Fabric中内成为Chaincode,中文翻译为链码。涉及到链码地方都是 Chaincode.
本示例是一个资产交易的示例
主要实现如下的功能:
- 初始化 A、B 两个账户,并为两个账户赋初始资产值;
- 在 A、B 两个账户之间进行资产交易;
- 分别查询 A、B 两个账户上的余额,确认交易成功;
- 删除账户。
- 新增账户
主要函数
- Init:初始化 A、B 两个账户;
- Invoke:调用其它函数的入口;
- transfer:实现 A、B 账户间的转账;
- query:查询 A、B 账户上的余额;
- delete:删除账户。
- create: 新增账户
注意:Fabric官方提供了两种开发node.js链码的途径:fabric-shim和fabric-contract-api。下面演示fabric-contract-api的方式
1 创建项目目录
注:如果已经有该目录则不需 创建了
$ cd $GOPATH/src/github.com
$ mkdir -p fabric-study/chaincode-study1
注:如果上述mkdir 不能执行,可能是没有权限,加上sudo就可以了
$ sudo mkdir chaincode-study1
2 搭建运行网络
我们不另行去搭建节点网络了,直接拷贝官网提供的chaincode-docker-devmode过来用,执行cp命令进行拷贝
$ cd fabric-study/chaincode-study1/
$ cp -r ../../hyperledger/fabric-samples/chaincode-docker-devmode ./
3 创建golang链码放置目录
$ mkdir -p chaincode/javascript
4 用开发工具vs code打开chaincode-study1目录
5 创建相应的目录和文件
创建index.js和package.json文件,再创建lib目录,在lib目录下再创建example.js
package.json文件内容如下
{
"name": "example",
"version": "1.0.0",
"description": "Example contract implemented in JavaScript",
"main": "index.js",
"engines": {
"node": ">=8",
"npm": ">=5"
},
"scripts": {
"lint": "eslint .",
"pretest": "npm run lint",
"test": "nyc mocha --recursive",
"start": "fabric-chaincode-node start --peer.address peer:7052 --chaincode-id-name mycc:0"
},
"engineStrict": true,
"author": "Hyperledger",
"license": "Apache-2.0",
"dependencies": {
"fabric-contract-api": "~1.4.0",
"fabric-shim": "~1.4.0"
},
"devDependencies": {
"chai": "^4.1.2",
"eslint": "^4.19.1",
"mocha": "^5.2.0",
"nyc": "^12.0.2",
"sinon": "^6.0.0",
"sinon-chai": "^3.2.0"
},
"nyc": {
"exclude": [
"coverage/**",
"test/**"
],
"reporter": [
"text-summary",
"html"
],
"all": true,
"check-coverage": true,
"statements": 100,
"branches": 100,
"functions": 100,
"lines": 100
}
}
可以看到要依赖"fabric-contract-api": "~1.4.0"和"fabric-shim": "1.4.0"这个包
6 写一个Example类并提供构造函数
打开example.js开始编写代码
const { Contract } = require('fabric-contract-api');
class Example extends Contract {
constructor(){
super('ExampleContract');
}/这里写各种函数
}
7 创建instantiate函数
//对应peer node instantiate
async instantiate(ctx,A,Aval,B,Bval){
if(!A || !Aval || !B || !Bval){
throw new Error("必须传入4个参数");
}
if (typeof parseInt(Aval) !== 'number' || typeof parseInt(Bval) !== 'number') {
throw new Error('资产值必须是一个整型数');
}
await ctx.stub.putState(A, Buffer.from(Aval));
await ctx.stub.putState(B, Buffer.from(Bval));
}
8 创建transfer函数
async transfer(ctx,A,B,amount){
if(!A || !B || !amount){
throw new Error("必须传入3个参数");
}
let Avalbytes = await ctx.stub.getState(A);
if (!Avalbytes || Avalbytes.length === 0) {
throw new Error('从资产持有者'+A+'获取状态失败');
}
let Aval = parseInt(Avalbytes.toString());
let Bvalbytes = await ctx.stub.getState(B);
if (!Bvalbytes || Bvalbytes.length === 0) {
throw new Error('从资产持有者'+B+'获取状态失败');
}
let Bval = parseInt(Bvalbytes.toString());
let X = parseInt(amount);
if (typeof X !== 'number') {
throw new Error('转账数值必须是一个整型数');
}
if (Aval < X) {
throw new Error('余额不足');
}
Aval = Aval - X;
Bval = Bval + X;
console.info(util.format('Aval = %d, Bval = %d\n', Aval, Bval));
await ctx.stub.putState(A, Buffer.from(Aval.toString()));
await ctx.stub.putState(B, Buffer.from(Bval.toString()));
}
9 创建delete函数
// 删除账户实体
async delete(ctx, A) {
if (!A) {
throw new Error('不正确的参数个数,期望1个参数');
}
// Delete the key from the state in ledger
await ctx.stub.deleteState(A);
}
10 创建query函数
// 查询账户的资产
async query(ctx, A) {
if (!A) {
throw new Error('不正确的参数个数,期望1个参数');
}
let jsonResp = {};
// Get the state from the ledger
let Avalbytes = await ctx.stub.getState(A);
if (!Avalbytes || Avalbytes.length === 0) {
jsonResp.error = '从资产持有者'+A+'获取状态失败';
throw new Error(JSON.stringify(jsonResp));
}
jsonResp.name = A;
jsonResp.amount = Avalbytes.toString();
console.info('Query Response:');
console.info(jsonResp);
return parseInt(Avalbytes.toString());
}
11 创建create函数
// 创建账户实体
async create(ctx, A,Aval) {
if (!A || !Aval) {
throw new Error('不正确的参数个数,期望2个参数')
}
let jsonResp = {};
if (typeof parseInt(Aval) !== 'number') {
return shim.error('期望一个整型值');
}
try {
await ctx.stub.putState(A, Buffer.from(Aval));
} catch (err) {
return shim.error(err);
}
}
12 文件的最后别忘了导出
在example.js中导出
module.exports = Example;
在index.js中同样要导出
'use strict';
const Example = require('./lib/example');
module.exports.Example = Example;
module.exports.contracts = [ Example ];
13 启动节点网络
打开第1个终端
先停掉和删除已有的容器
$ docker stop $(docker ps -aq)
$ docker rm $(docker ps -aq)
执行下面的命令启动节点网络
$ cd chaincode-docker-devmode
$ docker-compose -f docker-compose-simple.yaml up
14 安装依赖包并执行
打开第2个终端
进入chaincode这个容器
$ docker exec -it chaincode bash
cd到chaincode/go目录下
$ cd javascript
安装依赖包
$ npm install
如果卡住了用淘宝镜像
$ npm install --registry=http://registry.npm.taobao.org
运行
$ npm run start
15 安装和实例化链码
打开第3个终端
进入cli这个容器
$ docker exec -it cli bash
安装链码
$ peer chaincode install -p chaincode/javascript -n mycc -v 0 -l node
实例化链码
$ peer chaincode instantiate -n mycc -v 0 -c '{"Args":["ExampleContract:instantiate","tom","100","bob","200"]}' -C myc
16 执行新建,查询,转账,删除等函数
还是在 第3个终端
新建账户实体
$ peer chaincode invoke -n mycc -c '{"Args":["ExampleContract:create","lily","150"]}' -C myc
查询
$ peer chaincode query -n mycc -c '{"Args":["ExampleContract:query","lily"]}' -C myc
$ peer chaincode query -n mycc -c '{"Args":["ExampleContract:query","tom"]}' -C myc
$ peer chaincode query -n mycc -c '{"Args":["ExampleContract:query","bob"]}' -C myc
转账
bob给lily转账30
$ peer chaincode invoke -n mycc -c '{"Args":["ExampleContract:transfer","bob","lily","30"]}' -C myc
转账后再查询
$ peer chaincode query -n mycc -c '{"Args":["ExampleContract:query","lily"]}' -C myc
$ peer chaincode query -n mycc -c '{"Args":["ExampleContract:query","bob"]}' -C myc
删除tom账户实体
$ peer chaincode invoke -n mycc -c '{"Args":["ExampleContract:delete","tom"]}' -C myc
17 Chaincode 说明
Fabric中的Chaincode包含了一个Chaincode代码和Chaincode管理命令这两部分。
Chaincode 代码是业务的承载体,负责具体的业务逻辑
Chaincode 管理命令负责 Chaincode的部署,安装,维护等工作
17.1 Chaincode代码
Fabric的Chaincode是一段运行在容器中的程序。Chaincode是客户端程序和Fabric之间的桥梁。通过Chaincode客户端程序可以发起交易,查询交易。Chaincode是运行在Dokcer容器中,因此相对来说安全。
目前支持 java,node,go。
17.2 Chaincode的管理命令
Chaincode管理命令主要用来对Chaincode进行安装,实例化,调用,打包,签名操作。Chaincode命令包含在Peer模块中,是peer模块中一个子命令. 可通过执行peer chaincode --help查看如何使用。
peer chaincode --help
Operate a chaincode: install|instantiate|invoke|package|query|signpackage|upgrade|list.
Usage:
peer chaincode [command]
Available Commands:
install Package the specified chaincode into a deployment spec and save it on the peer's path.
instantiate Deploy the specified chaincode to the network.
invoke Invoke the specified chaincode.
list Get the instantiated chaincodes on a channel or installed chaincodes on a peer.
package Package the specified chaincode into a deployment spec.
query Query using the specified chaincode.
signpackage Sign the specified chaincode package
upgrade Upgrade chaincode.
17.3 chaincode的生命周期
fabric 提供了4种命令去管理chaincode生命周期:package、install、instantiate、upgrade。将来发布的版本的会增加stop以及start。
transaction用于停止与开启chaincode,而不用去卸载chaincode。chaincode在成功install以及instantiate之后,chaincode则是运行状态,能够通过invoke transaction来处理交易。后续也能够对chaincode进行升级。
17.4 fabric的nodejs链码结构
fabric-shim是一种相对底层的fabric grpc协议封装,它直接把链码接口暴露给开发者,虽然简单直白,但如果要实现相对复杂一点的链码,开发者需要自己在Invoke实现中进行方法路由。
fabric-contract-api则是更高层级的封装,开发者直接继承开发包提供的Contract类,就不用费心合约方法路由的问题了
nodejs链码需要引入fabric-shim和util模块
const { Contract } = require('fabric-contract-api');
const util = require('util');