• 使用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: 'Admin@org1.example.com', 
        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/Admin@org1.example.com/msp/keystore', 
        signedCert:'/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem', 
        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元。

    studyzy@ubuntu1:~/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: 'Admin@org1.example.com', 
         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/Admin@org1.example.com/msp/keystore', 
        signedCert:'/home/studyzy/go/src/github.com/hyperledger/fabric/examples/e2e_cli/crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem', 
        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也是不一样的。

    最后,大家如果想进一步探讨Fabric或者使用中遇到什么问题可以加入QQ群【494085548】大家一起讨论。

  • 相关阅读:
    开源监控软件之争
    为什么很多公司都自主开发监控系统?
    为 UWP 应用提供的 .NET 网络 API
    亲,根据二八定律,你的监控工具可能白装了哦
    PHP7正式版测试,性能惊艳!
    Java Web 前端高性能优化(一)
    天下武功无坚不破,唯快不破!
    告警信息大爆炸,运维解放秘籍!
    第33节:Java面向对象中的异常
    第33节:Java面向对象中的异常
  • 原文地址:https://www.cnblogs.com/studyzy/p/7524245.html
Copyright © 2020-2023  润新知