使用Fabric Node SDK进行Invoke和Query

前面的文章都是在讲解Fabric网络的搭建和ChainCode的开发,那么在ChainCode开发完毕后,我们就需要使用Fabric SDK做应用程序的开发了。官方虽然提供了Node.JS,Java,Go,Python等多种语言的SDK,但是由于整个Fabric太新了,很多SDK还不成熟和完善,所以我采用Node JS的SDK,毕竟这个是功能毕竟齐全,而且也是官方示例的时候使用的SDK。由于我从来没有接触过Node.JS的开发,对这个语言理解不深,所以讲的比较肤浅,希望大家见谅。

1.环境准备

Node.js是一个跨平台的语言,可以在Linux,Window和Mac上安装,我们在开发的时候可以在Windows下开发,最后生产环境一般都是Linux,所以我们这里就以Ubuntu为例。Fabric Node SDK支持的Node版本是v6,不支持最新的v8版本。NodeJS官方给我们提供了很方便的安装方法,具体文档在:https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions

我们只需要执行以下命令即可安装NodeJS的最新v6版本:

curl -sL https://deb.nodesource.com/setup_6.x | sudo -E bash -
sudo apt-get install -y nodejs
安装完成后我们可以使用以下两个命令来查看安装的Node版本和npm版本。
node –v
npm -v
关于NPM,这个是一个包管理器,我觉得很像VS里面的NuGet,关于NPM的基础知识,我们可以参考这篇博客:http://www.ruanyifeng.com/blog/2016/01/npm-install.html
只要安装好node和npm,接下来我们就可以进行Fabric Node SDK Application的开发了。
由于我们想基于官方Example的e2e_cli里面的Fabric网络来写程序,关于Fabric网络的搭建我就不多说,大家可以参考我之前的博客。总之结果就是我们现在已经成功运行了e2e_cli这个网络,也就是说Example02这个ChainCode已经安装部署,并且测试通过了,我们接下来只是换用Node SDK的方式进行查询和调用。

2.编写package.json并下载依赖模块

我们首先在当前用户的根目录建立一个nodeTest的文件夹,用于存放我们关于node的相关项目文件,然后在其中新建一个包配置文件,package.json
mkdir ~/nodeTest
cd ~/nodeTest
vi package.json
在这个文件中,我们可以定义很多项目相关的属性,这篇博客详细的介绍了每个属性有什么用,大家可以参考:http://www.cnblogs.com/tzyy/p/5193811.html
总之,最后我们在package.json中放入了以下内容:
{
     "name": "nodeTest",
     "version": "1.0.0",
     "description": "Hyperledger Fabric Node SDK Test Application",
     "scripts": {
         "test": "echo \"Error: no test specified\" && exit 1"
     },
     "dependencies": {
         "fabric-ca-client": "^1.0.0",
         "fabric-client": "^1.0.0"
     },
     "author": "Devin Zeng",
     "license": "Apache-2.0",
     "keywords": [
         "Hyperledger",
         "Fabric",
         "Test",
         "Application"
     ]
}

最主要的就是dependencies,这里我们放了Fabric CA Client和Fabric Node SDK的Client,虽然本示例中没用到CA Client,但是以后会用到,所以先放在这里了。

编辑保存好该文件后,我们就可以运行npm install命令来下载所有相关的依赖模块,但是由于npm服务器在国外,所以下载可能会很慢,感谢淘宝为我们提供了国内的npm镜像,使得安装npm模块快很多。运行的命令是:
npm install --registry=https://registry.npm.taobao.org
运行完毕后我们查看一下nodeTest目录,可以看到多了一个node_modules文件夹。这里就是使用刚才的命令下载下来的所有依赖包。

2.编写对Fabric的Query方法

下面我们新建一个query.js文件,开始我们的Fabric Node SDK编码工作。由于代码比较长,所以我就不分步讲了,直接在代码中增加注释,将完整代码贴出来:

‘use strict‘;

var hfc = require(‘fabric-client‘);
var path = require(‘path‘);
var sdkUtils = require(‘fabric-client/lib/utils‘)
var fs = require(‘fs‘);
var options = {
    user_id: ‘[email protected]‘,
    msp_id:‘Org1MSP‘,
    channel_id: ‘mychannel‘,
    chaincode_id: ‘mycc‘,
    network_url: ‘grpcs://localhost:7051‘,//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
    privateKeyFolder:‘/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/keystore‘,
    signedCert:‘/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/signcerts/[email protected]‘,
    tls_cacerts:‘/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt‘,
    server_hostname: "peer0.org1.example.com"
};

var channel = {};
var client = null;
const getKeyFilesInDir = (dir) => {
//该函数用于找到keystore目录下的私钥文件的路径
    var files = fs.readdirSync(dir)
    var keyFiles = []
    files.forEach((file_name) => {
        let filePath = path.join(dir, file_name)
        if (file_name.endsWith(‘_sk‘)) {
            keyFiles.push(filePath)
        }
    })
    return keyFiles
}
Promise.resolve().then(() => {
    console.log("Load privateKey and signedCert");
    client = new hfc();
    var    createUserOpt = {
                username: options.user_id,
                 mspid: options.msp_id,
                cryptoContent: { privateKey: getKeyFilesInDir(options.privateKeyFolder)[0],
  signedCert: options.signedCert }
        }
//以上代码指定了当前用户的私钥,证书等基本信息
return sdkUtils.newKeyValueStore({
                        path: "/tmp/fabric-client-stateStore/"
                }).then((store) => {
                        client.setStateStore(store)
                         return client.createUser(createUserOpt)
                 })
}).then((user) => {
    channel = client.newChannel(options.channel_id); 

    let data = fs.readFileSync(options.tls_cacerts);
    let peer = client.newPeer(options.network_url,
         {
            pem: Buffer.from(data).toString(),
             ‘ssl-target-name-override‘: options.server_hostname
        }
    );
    peer.setName("peer0");
    //因为启用了TLS,所以上面的代码就是指定TLS的CA证书
    channel.addPeer(peer);
    return;
}).then(() => {
    console.log("Make query");
    var transaction_id = client.newTransactionID();
    console.log("Assigning transaction_id: ", transaction_id._transaction_id);
//构造查询request参数
    const request = {
        chaincodeId: options.chaincode_id,
        txId: transaction_id,
        fcn: ‘query‘,
        args: [‘a‘]
    };
     return channel.queryByChaincode(request);
}).then((query_responses) => {
    console.log("returned from query");
    if (!query_responses.length) {
        console.log("No payloads were returned from query");
    } else {
        console.log("Query result count = ", query_responses.length)
    }
    if (query_responses[0] instanceof Error) {
        console.error("error from query = ", query_responses[0]);
    }
    console.log("Response is ", query_responses[0].toString());//打印返回的结果
}).catch((err) => {
    console.error("Caught Error", err);
});

编写完代码,我们想要测试一下我们的代码是否靠谱,直接运行

node query.js

即可,我们可以看到,a账户的余额是90元。

[email protected]:~/nodeTest$ node query.js
Load privateKey and signedCert
Make query
Assigning transaction_id:  ee3ac35d40d8510813546a2216ad9c0d91213b8e1bba9b7fe19cfeff3014e38a
returned from query
Query result count =  1
Response is  90

为什么a账户是90?因为我们跑e2e_cli的Fabric网络时,系统会自动安装Example02的ChainCode,然后自动跑查询,转账等操作。

3.编写对Fabric的Invoke方法

相比较于Query方法,Invoke方法要复杂的多,主要是因为Invoke需要和Orderer通信,而且发起了Transaction之后,还要设置EventHub来接收消息。下面贴出invoke.js的全部内容,对于比较重要的部分我进行了注释:

‘use strict‘;

var hfc = require(‘fabric-client‘);
var path = require(‘path‘);
var util = require(‘util‘);
var sdkUtils = require(‘fabric-client/lib/utils‘)
const fs = require(‘fs‘);
var options = {
    user_id: ‘[email protected]‘,
     msp_id:‘Org1MSP‘,
    channel_id: ‘mychannel‘,
    chaincode_id: ‘mycc‘,
    peer_url: ‘grpcs://localhost:7051‘,//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
    event_url: ‘grpcs://localhost:7053‘,//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
    orderer_url: ‘grpcs://localhost:7050‘,//因为启用了TLS,所以是grpcs,如果没有启用TLS,那么就是grpc
    privateKeyFolder:‘/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/keystore‘,
    signedCert:‘/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/signcerts/[email protected]‘,
    peer_tls_cacerts:‘/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt‘,
    orderer_tls_cacerts:‘/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt‘,
    server_hostname: "peer0.org1.example.com"
};

var channel = {};
var client = null;
var targets = [];
var tx_id = null;
const getKeyFilesInDir = (dir) => {
//该函数用于找到keystore目录下的私钥文件的路径
        const files = fs.readdirSync(dir)
        const keyFiles = []
        files.forEach((file_name) => {
                let filePath = path.join(dir, file_name)
                if (file_name.endsWith(‘_sk‘)) {
                        keyFiles.push(filePath)
                }
        })
        return keyFiles
}
Promise.resolve().then(() => {
    console.log("Load privateKey and signedCert");
    client = new hfc();
    var    createUserOpt = {
                username: options.user_id,
                mspid: options.msp_id,
                cryptoContent: { privateKey: getKeyFilesInDir(options.privateKeyFolder)[0],
  signedCert: options.signedCert }
         }
//以上代码指定了当前用户的私钥,证书等基本信息
return sdkUtils.newKeyValueStore({
                        path: "/tmp/fabric-client-stateStore/"
                }).then((store) => {
                        client.setStateStore(store)
                        return client.createUser(createUserOpt)
                })
}).then((user) => {
    channel = client.newChannel(options.channel_id);
    let data = fs.readFileSync(options.peer_tls_cacerts);
    let peer = client.newPeer(options.peer_url,
        {
            pem: Buffer.from(data).toString(),
            ‘ssl-target-name-override‘: options.server_hostname
        }
    );
    //因为启用了TLS,所以上面的代码就是指定Peer的TLS的CA证书
    channel.addPeer(peer);
    //接下来连接Orderer的时候也启用了TLS,也是同样的处理方法
    let odata = fs.readFileSync(options.orderer_tls_cacerts);
    let caroots = Buffer.from(odata).toString();
    var orderer = client.newOrderer(options.orderer_url, {
        ‘pem‘: caroots,
        ‘ssl-target-name-override‘: "orderer.example.com"
    }); 

    channel.addOrderer(orderer);
    targets.push(peer);
    return;
}).then(() => {
    tx_id = client.newTransactionID();
    console.log("Assigning transaction_id: ", tx_id._transaction_id);
//发起转账行为,将a->b 10元
    var request = {
        targets: targets,
        chaincodeId: options.chaincode_id,
        fcn: ‘invoke‘,
        args: [‘a‘, ‘b‘, ‘10‘],
        chainId: options.channel_id,
        txId: tx_id
    };
    return channel.sendTransactionProposal(request);
}).then((results) => {
    var proposalResponses = results[0];
    var proposal = results[1];
    var header = results[2];
    let isProposalGood = false;
    if (proposalResponses && proposalResponses[0].response &&
        proposalResponses[0].response.status === 200) {
        isProposalGood = true;
        console.log(‘transaction proposal was good‘);
    } else {
        console.error(‘transaction proposal was bad‘);
    }
    if (isProposalGood) {
        console.log(util.format(
            ‘Successfully sent Proposal and received ProposalResponse: Status - %s, message - "%s", metadata - "%s", endorsement signature: %s‘,
            proposalResponses[0].response.status, proposalResponses[0].response.message,
            proposalResponses[0].response.payload, proposalResponses[0].endorsement.signature));
        var request = {
            proposalResponses: proposalResponses,
             proposal: proposal,
            header: header
        };
         // set the transaction listener and set a timeout of 30sec
        // if the transaction did not get committed within the timeout period,
        // fail the test
        var transactionID = tx_id.getTransactionID();
        var eventPromises = [];
        let eh = client.newEventHub();
        //接下来设置EventHub,用于监听Transaction是否成功写入,这里也是启用了TLS
        let data = fs.readFileSync(options.peer_tls_cacerts);
        let grpcOpts = {
             pem: Buffer.from(data).toString(),
            ‘ssl-target-name-override‘: options.server_hostname
        }
        eh.setPeerAddr(options.event_url,grpcOpts);
        eh.connect();

        let txPromise = new Promise((resolve, reject) => {
            let handle = setTimeout(() => {
                eh.disconnect();
                reject();
            }, 30000);
//向EventHub注册事件的处理办法
            eh.registerTxEvent(transactionID, (tx, code) => {
                clearTimeout(handle);
                eh.unregisterTxEvent(transactionID);
                eh.disconnect();

                if (code !== ‘VALID‘) {
                    console.error(
                        ‘The transaction was invalid, code = ‘ + code);
                    reject();
                 } else {
                    console.log(
                         ‘The transaction has been committed on peer ‘ +
                         eh._ep._endpoint.addr);
                    resolve();
                }
            });
        });
        eventPromises.push(txPromise);
        var sendPromise = channel.sendTransaction(request);
        return Promise.all([sendPromise].concat(eventPromises)).then((results) => {
            console.log(‘ event promise all complete and testing complete‘);
             return results[0]; // the first returned value is from the ‘sendPromise‘ which is from the ‘sendTransaction()‘ call
        }).catch((err) => {
            console.error(
                ‘Failed to send transaction and get notifications within the timeout period.‘
            );
            return ‘Failed to send transaction and get notifications within the timeout period.‘;
         });
    } else {
        console.error(
            ‘Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...‘
        );
        return ‘Failed to send Proposal or receive valid response. Response null or status is not 200. exiting...‘;
    }
}, (err) => {
    console.error(‘Failed to send proposal due to error: ‘ + err.stack ? err.stack :
        err);
    return ‘Failed to send proposal due to error: ‘ + err.stack ? err.stack :
        err;
}).then((response) => {
    if (response.status === ‘SUCCESS‘) {
        console.log(‘Successfully sent transaction to the orderer.‘);
        return tx_id.getTransactionID();
    } else {
        console.error(‘Failed to order the transaction. Error code: ‘ + response.status);
        return ‘Failed to order the transaction. Error code: ‘ + response.status;
    }
}, (err) => {
    console.error(‘Failed to send transaction due to error: ‘ + err.stack ? err
         .stack : err);
    return ‘Failed to send transaction due to error: ‘ + err.stack ? err.stack :
        err;
});

保存文件并退出,接下来测试一下我们的代码,运行:

node invoke.js

我们可以看到系统返回如下结果:

Load privateKey and signedCert
Assigning transaction_id:  1adbf20ace0d1601b00cc2b9dfdd4a431cfff9a13f6a6f5e5e4a80c897e0f7a8
transaction proposal was good
Successfully sent Proposal and received ProposalResponse: Status - 200, message - "OK", metadata - "", endorsement signature: 0D x??N??n?#???/?G???QD?w?????As? \]??FfW??+??????=m9I???? 6?i
info: [EventHub.js]: _connect - options {"grpc.ssl_target_name_override":"peer0.org1.example.com","grpc.default_authority":"peer0.org1.example.com"}
The transaction has been committed on peer localhost:7053
  event promise all complete and testing complete
Successfully sent transaction to the orderer.

从打印出的结果看,我们的转账已经成功了,我们可以重新调用之前写的query.js重新查询,可以看到a账户的余额已经变少了10元。

4.总结

我们以上的query和Invoke都是参照了官方的fabcar示例,该示例在https://github.com/hyperledger/fabric-samples/tree/release/fabcar

这只是简单的测试Node SDK是否可用,如果我们要做项目,那么就会复杂很多,可以参考官方的两个项目:

https://github.com/hyperledger/fabric-samples/tree/release/balance-transfer

https://github.com/IBM-Blockchain/marbles

我之前一直卡在怎么基于某个用户的私钥和证书来设置当前的Context,后来感谢neswater的帮助,终于才解决了这个问题。还有就是TLS的问题,官方给出的fabcar是没有TLS的,我搞了半天才搞定,原来除了制定TLS证书之外,我们访问Peer的URL也是不一样的。

时间: 2024-08-26 18:27:49

使用Fabric Node SDK进行Invoke和Query的相关文章

搭建RESTful API来使用Fabric Node SDK 开篇

在Balance-Transfer中,有关于Node SDK比较完备的例子. SDK的官方文档在这里:https://fabric-sdk-node.github.io/ Balance-Transfer中的各项配置项都已经准备了,如果需要重新生成artifacts,务必在生成之后相应的修改docker-compose.yaml,network-config.yaml 启动网络 运行./runApp.sh后,得到类似的结果: Stopping peer1.org1.example.com ...

Hyperledger Fabric Java SDK最新教程

Fabric Java SDK是Fabric区块链官方提供的用于Java应用开发的SDK,全称为Fabric-sdk-java,网上可用资料不多,本文列出了精心整理的针对Fabric Java SDK的最新精选教程. 如果希望快速掌握Fabric Java SDK的使用方法,建议访问汇智网的在线互动教程: Fabric区块链Java开发详解 1.官方文档 使用Fabric Java SDK开发必备的手册,从Java SDK源代码注释生成的每个接口.类和方法的简要说明,聊胜于无,但是要指望它达到真

Node.js SDK与fabric链码交互开发

1.本篇背景 前面已经对链码开发作了比较详细的介绍,并且对官方提供的 fabcar 链码进行了解读,本篇将介绍如何使用 Node.js SDK 与区块链网络中的链码进行交互. 本篇内容基本来自官方 Hyperledger Fabric 文档中的 Writing Your First Application 章节,对文档进行翻译,原文网址如下: http://hyperledger-fabric.readthedocs.io/en/latest/write_first_app.html 主要根据谷

HyperLeger Fabric SDK开发(一)——Fabric SDK开发简介

HyperLeger Fabric SDK开发(一)--Fabric SDK开发简介 一.Fabric SDK简介 1.Fabric SDK简介 Farbric的Peer节点和Orderer节点都提供了基于gRPC协议的接口,用于和Peer节点与Orderer节点进行命令/数据交互.为了简化开发,为开发人员开发应用程序提供操作Fabric区块链网络的API,Fabric官方提供了多种语言版本的SDK.Fabric提供了三种语言版本的SDK,分别如下:A.Fabric Nodejs SDKB.Fa

Hyperledger Fabric V1.0 for Centos7.2 开发环境搭建

区块链技术大有可为, 安装组件如下: 1.Docker-compose:Docker 容器管理: 2.Go lang SDK:Go 语言开发.编译环境: 3.Git:git 镜像克隆与提交: 4.Rest Client: rest API 测试: 容器主要运行有:MemberSrv 和Peer; 一.HyperLedger Fabric环境准备 a)检查版本 $ uname –r 3.10.0-229.el7.x86_64 b)更新Centos yum update c)安装需要软件 yum i

CentOS7 搭建Fabric 1.0

1.环境搭建 1.1 go的按装及配置 1.1.1下载go压缩包 ? wget https://dl.google.com/go/go1.9.2.linux-amd64.tar.gz 1.1.2 解压 ? tar -C /usr/local -zxvf go1.9.2.linux-amd64.tar.gz 1.1.3配置环境变量 ? vi ~/.bash_profile 添加如下内容: PATH=$PATH:$HOME/bin export PATH export PATH=$PATH:/usr

FABRIC单机开发者模式启动

在开始之前需要导出一个自定义变量,方便后续操作: export FABRIC=/opt/gopath/src/github.com/hyperledger/fabric/devenv 1.在真机上执行如下操作,进入虚拟机 cd $FABRIC vagrant up//启动虚拟机 vagrant ssh//进入虚拟机 2.在虚拟机中执行如下操作,启动memberserv和peer,并保持该终端的启动,命名为T1 cd /opt/gopath/src/github.com/hyperledger/f

HyperLeger Fabric开发(六)——HyperLeger Fabric智能合约(链码)

HyperLeger Fabric开发(六)--HyperLeger Fabric智能合约(ChainCode) 一.链码(Chaincode)简介 1.链码简介 在Fabric中,智能合约也称为链码(chaincode),分为用户链码和系统链码.系统链码用来实现系统层面的功能,包括系统的配置,用户链码的部署.升级,用户交易的签名和验证策略等:用户链码用于实现用户的应用功能,开发者编写链码应用程序并将其部署到区块链网络上,终端用户通过与网络节点交互的客户端应用程序调用链码.链码被编译成一个独立的

HyperLeger Fabric开发(七)——HyperLeger Fabric链码开发

HyperLeger Fabric开发(七)--HyperLeger Fabric链码开发 一.链码开发模式 1.链码开发模式简介 Fabric的链码开发调试比较繁琐.在不使用链码开发模式的情况下,链码不能在本地测试,必须部署到docker,install和instantiate后,Peer节点会在新的容器中启动链码.但只能通过docker logs查看链码日志,通过打印日志的方式进行链码调试.如果对链码进行了修改,需要重新开始上述流程.为了简化Fabric链码开发的调试过程,Fabric引入了