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');
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。