fabric nodejs链码示例2

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目录

Snipaste_2019-03-22_10-00-36.png

5 创建相应的目录和文件

创建index.js和package.json文件,再创建lib目录,在lib目录下再创建example.js


Snipaste_2019-03-22_11-45-17.png

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进行升级。


Snipaste_2019-03-22_06-53-26.png

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');
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342