0 導(dǎo)言
智能合約是區(qū)塊鏈中一個(gè)非常重要的概念和組成部分遭商。在Fabric中內(nèi)成為Chaincode,中文翻譯為鏈碼厅各。涉及到鏈碼地方都是 Chaincode.
本示例是一個(gè)資產(chǎn)交易的示例
主要實(shí)現(xiàn)如下的功能:
- 初始化 A抵碟、B 兩個(gè)賬戶,并為兩個(gè)賬戶賦初始資產(chǎn)值祥山;
- 在 A、B 兩個(gè)賬戶之間進(jìn)行資產(chǎn)交易掉伏;
- 分別查詢 A缝呕、B 兩個(gè)賬戶上的余額,確認(rèn)交易成功斧散;
- 刪除賬戶供常。
- 新增賬戶
主要函數(shù)
- Init:初始化 A、B 兩個(gè)賬戶鸡捐;
- Invoke:調(diào)用其它函數(shù)的入口栈暇;
- transfer:實(shí)現(xiàn) A、B 賬戶間的轉(zhuǎn)賬箍镜;
- query:查詢 A源祈、B 賬戶上的余額;
- delete:刪除賬戶色迂。
- create: 新增賬戶
注意:Fabric官方提供了兩種開(kāi)發(fā)node.js鏈碼的途徑:fabric-shim和fabric-contract-api香缺。下面演示fabric-contract-api的方式
1 創(chuàng)建項(xiàng)目目錄
注:如果已經(jīng)有該目錄則不需 創(chuàng)建了
$ cd $GOPATH/src/github.com
$ mkdir -p fabric-study/chaincode-study1
注:如果上述mkdir 不能執(zhí)行,可能是沒(méi)有權(quán)限歇僧,加上sudo就可以了
$ sudo mkdir chaincode-study1
2 搭建運(yùn)行網(wǎng)絡(luò)
我們不另行去搭建節(jié)點(diǎn)網(wǎng)絡(luò)了图张,直接拷貝官網(wǎng)提供的chaincode-docker-devmode過(guò)來(lái)用,執(zhí)行cp命令進(jìn)行拷貝
$ cd fabric-study/chaincode-study1/
$ cp -r ../../hyperledger/fabric-samples/chaincode-docker-devmode ./
3 創(chuàng)建golang鏈碼放置目錄
$ mkdir -p chaincode/javascript
4 用開(kāi)發(fā)工具vs code打開(kāi)chaincode-study1目錄
5 創(chuàng)建相應(yīng)的目錄和文件
創(chuàng)建index.js和package.json文件,再創(chuàng)建lib目錄祸轮,在lib目錄下再創(chuàng)建example.js
package.json文件內(nèi)容如下
{
"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"這個(gè)包
6 寫(xiě)一個(gè)Example類并提供構(gòu)造函數(shù)
打開(kāi)example.js開(kāi)始編寫(xiě)代碼
const { Contract } = require('fabric-contract-api');
class Example extends Contract {
constructor(){
super('ExampleContract');
}/這里寫(xiě)各種函數(shù)
}
7 創(chuàng)建instantiate函數(shù)
//對(duì)應(yīng)peer node instantiate
async instantiate(ctx,A,Aval,B,Bval){
if(!A || !Aval || !B || !Bval){
throw new Error("必須傳入4個(gè)參數(shù)");
}
if (typeof parseInt(Aval) !== 'number' || typeof parseInt(Bval) !== 'number') {
throw new Error('資產(chǎn)值必須是一個(gè)整型數(shù)');
}
await ctx.stub.putState(A, Buffer.from(Aval));
await ctx.stub.putState(B, Buffer.from(Bval));
}
8 創(chuàng)建transfer函數(shù)
async transfer(ctx,A,B,amount){
if(!A || !B || !amount){
throw new Error("必須傳入3個(gè)參數(shù)");
}
let Avalbytes = await ctx.stub.getState(A);
if (!Avalbytes || Avalbytes.length === 0) {
throw new Error('從資產(chǎn)持有者'+A+'獲取狀態(tài)失敗');
}
let Aval = parseInt(Avalbytes.toString());
let Bvalbytes = await ctx.stub.getState(B);
if (!Bvalbytes || Bvalbytes.length === 0) {
throw new Error('從資產(chǎn)持有者'+B+'獲取狀態(tài)失敗');
}
let Bval = parseInt(Bvalbytes.toString());
let X = parseInt(amount);
if (typeof X !== 'number') {
throw new Error('轉(zhuǎn)賬數(shù)值必須是一個(gè)整型數(shù)');
}
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 創(chuàng)建delete函數(shù)
// 刪除賬戶實(shí)體
async delete(ctx, A) {
if (!A) {
throw new Error('不正確的參數(shù)個(gè)數(shù)姑隅,期望1個(gè)參數(shù)');
}
// Delete the key from the state in ledger
await ctx.stub.deleteState(A);
}
10 創(chuàng)建query函數(shù)
// 查詢賬戶的資產(chǎn)
async query(ctx, A) {
if (!A) {
throw new Error('不正確的參數(shù)個(gè)數(shù),期望1個(gè)參數(shù)');
}
let jsonResp = {};
// Get the state from the ledger
let Avalbytes = await ctx.stub.getState(A);
if (!Avalbytes || Avalbytes.length === 0) {
jsonResp.error = '從資產(chǎn)持有者'+A+'獲取狀態(tài)失敗';
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 創(chuàng)建create函數(shù)
// 創(chuàng)建賬戶實(shí)體
async create(ctx, A,Aval) {
if (!A || !Aval) {
throw new Error('不正確的參數(shù)個(gè)數(shù)倔撞,期望2個(gè)參數(shù)')
}
let jsonResp = {};
if (typeof parseInt(Aval) !== 'number') {
return shim.error('期望一個(gè)整型值');
}
try {
await ctx.stub.putState(A, Buffer.from(Aval));
} catch (err) {
return shim.error(err);
}
}
12 文件的最后別忘了導(dǎo)出
在example.js中導(dǎo)出
module.exports = Example;
在index.js中同樣要導(dǎo)出
'use strict';
const Example = require('./lib/example');
module.exports.Example = Example;
module.exports.contracts = [ Example ];
13 啟動(dòng)節(jié)點(diǎn)網(wǎng)絡(luò)
打開(kāi)第1個(gè)終端
先停掉和刪除已有的容器
$ docker stop $(docker ps -aq)
$ docker rm $(docker ps -aq)
執(zhí)行下面的命令啟動(dòng)節(jié)點(diǎn)網(wǎng)絡(luò)
$ cd chaincode-docker-devmode
$ docker-compose -f docker-compose-simple.yaml up
14 安裝依賴包并執(zhí)行
打開(kāi)第2個(gè)終端
進(jìn)入chaincode這個(gè)容器
$ docker exec -it chaincode bash
cd到chaincode/go目錄下
$ cd javascript
安裝依賴包
$ npm install
如果卡住了用淘寶鏡像
$ npm install --registry=http://registry.npm.taobao.org
運(yùn)行
$ npm run start
15 安裝和實(shí)例化鏈碼
打開(kāi)第3個(gè)終端
進(jìn)入cli這個(gè)容器
$ docker exec -it cli bash
安裝鏈碼
$ peer chaincode install -p chaincode/javascript -n mycc -v 0 -l node
實(shí)例化鏈碼
$ peer chaincode instantiate -n mycc -v 0 -c '{"Args":["ExampleContract:instantiate","tom","100","bob","200"]}' -C myc
16 執(zhí)行新建,查詢慕趴,轉(zhuǎn)賬痪蝇,刪除等函數(shù)
還是在 第3個(gè)終端
新建賬戶實(shí)體
$ 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
轉(zhuǎn)賬
bob給lily轉(zhuǎn)賬30
$ peer chaincode invoke -n mycc -c '{"Args":["ExampleContract:transfer","bob","lily","30"]}' -C myc
轉(zhuǎn)賬后再查詢
$ 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賬戶實(shí)體
$ peer chaincode invoke -n mycc -c '{"Args":["ExampleContract:delete","tom"]}' -C myc
17 Chaincode 說(shuō)明
Fabric中的Chaincode包含了一個(gè)Chaincode代碼和Chaincode管理命令這兩部分。
Chaincode 代碼是業(yè)務(wù)的承載體冕房,負(fù)責(zé)具體的業(yè)務(wù)邏輯
Chaincode 管理命令負(fù)責(zé) Chaincode的部署躏啰,安裝,維護(hù)等工作
17.1 Chaincode代碼
Fabric的Chaincode是一段運(yùn)行在容器中的程序耙册。Chaincode是客戶端程序和Fabric之間的橋梁给僵。通過(guò)Chaincode客戶端程序可以發(fā)起交易,查詢交易详拙。Chaincode是運(yùn)行在Dokcer容器中帝际,因此相對(duì)來(lái)說(shuō)安全。
目前支持 java,node,go饶辙。
17.2 Chaincode的管理命令
Chaincode管理命令主要用來(lái)對(duì)Chaincode進(jìn)行安裝蹲诀,實(shí)例化,調(diào)用弃揽,打包脯爪,簽名操作。Chaincode命令包含在Peer模塊中矿微,是peer模塊中一個(gè)子命令. 可通過(guò)執(zhí)行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。將來(lái)發(fā)布的版本的會(huì)增加stop以及start娜庇。
transaction用于停止與開(kāi)啟chaincode拇泛,而不用去卸載chaincode。chaincode在成功install以及instantiate之后思灌,chaincode則是運(yùn)行狀態(tài)俺叭,能夠通過(guò)invoke transaction來(lái)處理交易。后續(xù)也能夠?qū)haincode進(jìn)行升級(jí)泰偿。
17.4 fabric的nodejs鏈碼結(jié)構(gòu)
fabric-shim是一種相對(duì)底層的fabric grpc協(xié)議封裝熄守,它直接把鏈碼接口暴露給開(kāi)發(fā)者,雖然簡(jiǎn)單直白,但如果要實(shí)現(xiàn)相對(duì)復(fù)雜一點(diǎn)的鏈碼裕照,開(kāi)發(fā)者需要自己在Invoke實(shí)現(xiàn)中進(jìn)行方法路由攒发。
fabric-contract-api則是更高層級(jí)的封裝,開(kāi)發(fā)者直接繼承開(kāi)發(fā)包提供的Contract類晋南,就不用費(fèi)心合約方法路由的問(wèn)題了
nodejs鏈碼需要引入fabric-shim和util模塊
const { Contract } = require('fabric-contract-api');
const util = require('util');