美文网首页
fabric nodejs链码示例1

fabric nodejs链码示例1

作者: 链播学院 | 来源:发表于2019-03-22 11:08 被阅读0次

    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-shim的方式,这是一种比较底层的写法

    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-low-level
    

    4 用开发工具vs code打开chaincode-study1目录

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

    5 新建package.json文件和example.js文件

    Snipaste_2019-03-22_10-01-05.png

    package.json文件内容如下

    {
        "name": "example",
        "version": "1.0.0",
        "description": "exampe chaincode implemented in node.js",
        "engines": {
            "node": ">=8.4.0",
            "npm": ">=5.3.0"
        },
        "scripts": {
            "start": "node first.js --peer.address grpc://localhost:7052 "
        },
        "engine-strict": true,
        "license": "Apache-2.0",
        "dependencies": {
            "fabric-shim": "1.4.0"
        }
    }
    

    可以看到,底层写法要依赖"fabric-shim": "1.4.0"这个包

    6 创建一个链码对象

    const shim = require('fabric-shim');
    const util = require('util');
    
    var Chaincode = class {
        //这里写各种函数
    }
    

    7 创建Init函数

    // 实例化链码 peer chaincode instantiate 
        async Init(stub) {
            console.info('========= 实例化链码 =========');
            let ret = stub.getFunctionAndParameters();
            console.info(ret);
            let args = ret.params;
            // 实例化只能传4个参数进来
            if (args.length != 4) {
                return shim.error('不正确的参数个数,期望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('资产值必须是一个整型数');
            }
    
            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);
            }
        }
    
    

    8 创建Invoke函数

    //调用链码,对应peer node invoke
        async Invoke(stub) {
            let ret = stub.getFunctionAndParameters();
            console.info(ret);
            let method = this[ret.fcn];//得到要调用的参数
            if (!method) {
                console.log('函数:' + ret.fcn + ' 未找到');
                return shim.success();
            }
            try {
                //与golang不同,这里不需要去if-else判断
                let payload = await method(stub, ret.params);
                return shim.success(payload);
            } catch (err) {
                console.log(err);
                return shim.error(err);
            }
        }
    

    9 创建transfer函数

    //转账
        async transfer(stub, args) {
            if (args.length != 3) {
                throw new Error('不正确的参数个数,期望3个参数');
            }
    
            let A = args[0];
            let B = args[1];
            if (!A || !B) {
                throw new Error('资产数不能为空的');
            }
    
            // 从账本中获取状态
            let Avalbytes = await stub.getState(A);
            if (!Avalbytes) {
                throw new Error('从资产持有者'+A+'获取状态失败');
            }
            let Aval = parseInt(Avalbytes.toString());
    
            let Bvalbytes = await stub.getState(B);
            if (!Bvalbytes) {
                throw new Error('从资产持有者'+B+'获取状态失败');
            }
    
            let Bval = parseInt(Bvalbytes.toString());
            // Perform the execution
            let amount = parseInt(args[2]);
            if (typeof amount !== 'number') {
                throw new Error('amount必须是一个整型值');
            }
            if (Aval < amount) {
                throw new Error('余额不足');
            }
    
            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()));
    
        }
    

    10 创建delete函数

     // 删除账户实体
        async delete(stub, args) {
            if (args.length != 1) {
                throw new Error('不正确的参数个数,期望1个参数');
            }
    
            let A = args[0];
    
            // Delete the key from the state in ledger
            await stub.deleteState(A);
        }
    

    11 创建query函数

       // 查询账户的资产
        async query(stub, args) {
            if (args.length != 1) {
                throw new Error('不正确的参数个数,期望1个参数,该参数是账户实体名')
            }
    
            let jsonResp = {};
            let A = args[0];
    
            // Get the state from the ledger
            let Avalbytes = await stub.getState(A);
            if (!Avalbytes) {
                jsonResp.error = '从资产持有者'+A+'获取状态失败';
                throw new Error(JSON.stringify(jsonResp));
            }
    
            jsonResp.name = A;
            jsonResp.amount = Avalbytes.toString();
            console.info('Query Response:');
            console.info(jsonResp);
            return Avalbytes;
        }
    

    12 创建create函数

    
        // 创建账户实体
        async create(stub, args) {
            if (args.length != 2) {
                throw new Error('不正确的参数个数,期望2个参数')
            }
    
            let jsonResp = {};
            let A = args[0];
            let Aval = args[1];
    
    
            if (typeof parseInt(Aval) !== 'number') {
                return shim.error('期望一个整型值');
            }
    
            try {
                await stub.putState(A, Buffer.from(Aval));
            } catch (err) {
                return shim.error(err);
            }
        }
    

    13 别忘了start

    shim.start(new Chaincode());
    

    14 启动节点网络

    打开第1个终端

    先停掉和删除已有的容器

    $ docker stop $(docker ps -aq)
    $ docker rm $(docker ps -aq)
    

    执行下面的命令启动节点网络

    $ cd chaincode-docker-devmode
    $ docker-compose -f docker-compose-simple.yaml up
    

    15 安装依赖包并执行

    打开第2个终端

    进入chaincode这个容器

    $ docker exec -it chaincode bash
    

    cd到chaincode/go目录下

    $ cd javascript-low-level
    

    安装依赖包

    $ npm install
    

    如果卡住了用淘宝镜像

    $ npm install --registry=http://registry.npm.taobao.org
    

    运行

    $ CORE_CHAINCODE_ID_NAME=mycc:0 node example.js --peer.address peer:7052
    

    16 安装和实例化链码

    打开第3个终端

    进入cli这个容器

    $ docker exec -it cli bash
    

    安装链码

    $ peer chaincode install -p chaincode/javascript-low-level -n mycc -v 0 -l node
    

    实例化链码

    $ peer chaincode instantiate -n mycc -v 0 -c '{"Args":["init","tom","100","bob","200"]}' -C myc
    

    17 执行新建,查询,转账,删除等函数

    还是在 第3个终端

    新建账户实体

    $ peer chaincode invoke -n mycc -c '{"Args":["create","lily","150"]}' -C myc
    

    查询

    $ peer chaincode query -n mycc -c '{"Args":["query","lily"]}' -C myc
    $ peer chaincode query -n mycc -c '{"Args":["query","tom"]}' -C myc
    $ peer chaincode query -n mycc -c '{"Args":["query","bob"]}' -C myc
    

    转账

    bob给lily转账30

    $ peer chaincode invoke -n mycc -c '{"Args":["transfer","bob","lily","30"]}' -C myc
    

    转账后再查询

    $ peer chaincode query -n mycc -c '{"Args":["query","lily"]}' -C myc
    $ peer chaincode query -n mycc -c '{"Args":["query","bob"]}' -C myc
    

    删除tom账户实体

    $ peer chaincode invoke -n mycc -c '{"Args":["delete","tom"]}' -C myc
    

    18 Chaincode 说明

    Fabric中的Chaincode包含了一个Chaincode代码和Chaincode管理命令这两部分。
    Chaincode 代码是业务的承载体,负责具体的业务逻辑
    Chaincode 管理命令负责 Chaincode的部署,安装,维护等工作

    18.1 Chaincode代码

    Fabric的Chaincode是一段运行在容器中的程序。Chaincode是客户端程序和Fabric之间的桥梁。通过Chaincode客户端程序可以发起交易,查询交易。Chaincode是运行在Dokcer容器中,因此相对来说安全。

    目前支持 java,node,go。

    18.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.
    
    

    18.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

    18.4 fabric的nodejs链码结构

    nodejs链码需要引入fabric-shim和util模块

    const shim = require('fabric-shim');
    const util = require('util');
    

    fabric-shim要求链码开发者定义一个实现两个预定义方法的函数。

    • Init(stub):初始化链码时节点将调用该方法
    • Invoke(stub):节点将应用对链码的调用转化为对该方法的调用

    参数stub由节点传入,它提供了访问链上账本的方法,以便读取或更新账本状态。

    整体结构如下

    const shim = require('fabric-shim');
    const util = require('util');
    
    var Chaincode = class {
    
        // 实例化链码 peer chaincode instantiate 
        async Init(stub) {
        }
        //调用链码,对应peer node invoke
        async Invoke(stub) {
        }
    
    };
    
    shim.start(new Chaincode());
    

    相关文章

      网友评论

          本文标题:fabric nodejs链码示例1

          本文链接:https://www.haomeiwen.com/subject/drllvqtx.html