使用Node.js开发Hyperledger Fabric Chaincode

Hyperledger Fabric术语中的Chaincode是什么?
在Hyperledger Fabric中,Chaincode是在网络peer上运行的一段代码,用于实现应用程序如何与分类帐交互的业务逻辑。提出交易时,它会触发Chaincode,该Chaincode决定应将哪种状态更改应用于分类帐。因此为了在Hyperledger Fabric上开发去中心化的应用程序,必须编写称为Chaincode的应用程序逻辑。

Chaincode可以使用Go,Nodejs,Java编写。其他两种语言相比,Node.js是一种更容易理解和使用的语言。由于Node.js文档中可用的信息非常少,但编写和部署Node.js Chaincode并不困难。因此我决定编写一些Node.js Chaincode的概念,并逐步编写和部署简单的Chaincode。
Hyperledger Fabric数据库
在深入研究Chaincode之前,首先让我们看一下Hyperledger Fabric Network中数据的存储位置。Hyperledger Fabric使用键值数据库存储其状态。默认情况下,Fabric使用LevelDB。该数据库保存可以使用其键查询的特定对象的二进制数据。与传统的数据库不同,区块链数据库位于网络的每个peer上。因此它被称为分散网络。
除了LevelDB,还有另一个用于Hyperledger Fabric的数据库称为CouchDB。CouchDB是可选的替代外部可插拔状态数据库。与LevelDB键值存储一样,CouchDB可以存储以链码建模的任何二进制数据。但作为JSON文档存储,当将Chaincode值(例如资产)建模为JSON数据时,CouchDB还可以对Chaincode数据进行丰富的查询。
 Chaincode组件
1.fabric-contract-api:
用于实现智能合约的高级合约API(作为npm模块提供)
2.fabric-shim:
用于实现智能合约的低级合约API(作为npm模块提供)
我们可以将fabric-shim视为fabric-contract-api的递减版本。对新版本的fabric使用高级API是一个好习惯。但是fabric-contract-api可以完成shim可以做的所有事情。当然不止如此。
3.stub:
它是fabric-contract-api中的接口,用于访问和修改分类帐(数据库状态)。因此它是主要数据Chaincode接口,用于在分类账上读写数据,我们如何读写数据?让我们看一下Stub界面中的一些常用方法
Stub接口中的常用方法
1.getState(k):
众所周知,Hyperledger Fabric数据库以键值对的形式存储数据。
此方法从分类帐读取数据。它以输入“ k”作为键,并返回与键“ k”关联的二进制值。
2.putState(k,v):
此方法将数据写入分类帐。它以“ k”为键,“ v”为值。明确地说,假设我们想将Alice的年龄存储到分类帐中,我们可以将Alice作为键,并将AGE作为值。
3.deleteState(k):
此方法从分类账中删除关联密钥“ k”的值。
4.getStateByRange(k1,k9):
此方法在分类帐中的一组键上返回范围迭代器。它将迭代startKey(k1)和endKey(k9)并返回这两个键之间的所有键值。这类似于javascript中的for循环。假设如果我们已经按键k1,k2,k3…k99的顺序存储了一些用户数据,则可以使用此方法简单地获取所有这些值的状态。
5.getTxID():
此方法返回被调用事务的事务ID。交易ID对于链上的每个交易都是唯一的。因此交易ID在跟踪交易中起着至关重要的作用。
6.getTxTimestamp():
此方法返回创建事务时的时间戳。这是从交易ChannelHeader获取的,因此它将指示客户的时间戳,并且在所有背书人中都具有相同的值。
编写您的第一个Chaincode
您已经了解了在node.js中编写链码的一些先决条件。因此就目前而言,您可能会非常兴奋地编写您的第一个chaincode。
由于我们要用Nodejs编写Chaincode,因此我们首先需要创建传统的npm东西,例如package.json和index.js。如果您不知道此package.json。
列出您的项目所依赖的软件包
使用语义版本控制规则指定项目可以使用的软件包的版本
使您的构建具有可复制性,因此更易于与其他开发人员共享.
简而言之,我们的Chaincode取决于fabric-contract-api和fabric-shim模块。我们在package.json中提到了这些软件包和版本。
我们还将添加fabric-chaincode-node start作为我们的启动脚本,这是在peer节点上安装chaincode所需的。
这是我们的package.json:
{
    “name”: “Test-Chaincode”,
    “version”: “1.0.0”,
    “description”: “my first exciting node.js chaincode on Hyperledger-fabric”,
    “main”: “index.js”,
    “engines”: {
        “node”: “>=8”,
        “npm”: “>=5”
    },
    “scripts”: {
        “lint”: “eslint .”,
        “pretest”: “npm run lint”,
        “test”: “nyc mocha –recursive”,
        “start”: “fabric-chaincode-node start”
    },
    “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
    }
}
如果您可以仔细观察代码,则有一行“ main”:“ index.js”。这意味着什么—在启动时(安装chaincode期间),npm模块用于检查index.js并在peer节点上安装提到的合约。”因此我们的index.js包含作为模块导出的合约。
这是我们的index.js文件:
‘use strict’;
const testContract = require(’./logic’);
module.exports.contracts = [ testContract ];
智能合约:
我们的业务逻辑是什么?
添加,检索和删除学生标记。
1.添加标记涉及将数据写入分类帐。因此我们将从chaincode的stub接口使用putState(k,v)方法。
2.检索标记涉及从分类帐读取数据。因此我们需要使用getState(k)方法。
3.删除标记涉及删除数据。因此我们需要使用deleteState(k)方法。
chaincode首先从fabric-contract-api模块引入作用域密钥类Contract。此类将用于编写逻辑,所有chaincode函数都应使用This库类。
const { Contract}=require(’fabric-contract-api’);
 class testContract extends Contract {
//Functions go here
}
添加标记:
我们将创建一个JavaScript对象来存储每个学科中学生的成绩,并将该对象存储为一个值,并将StudentId存储为一个键。通过服务器将数据发送到数据库时,数据必须是字符串。因此我们需要使用JSON.stringify()方法将此标记对象转换为字符串,并应用缓冲区以二进制数据的形式发送到数据库。
async addMarks(ctx,studentId,subject1,subject2,subject3) { 
    let marks={
     subj1:subject1, 
     subj2:subject2,
     subj3:subject3 
     }; 
await  ctx.stub.putState(studentId,Buffer.from(JSON.stringify(marks))); 
console.log(’Student Marks added To the ledger Succesfully..’); 
}
删除标记
async deleteMarks(ctx,studentId) {
 await ctx.stub.deleteState(studentId); 
console.log(’Student Marks deleted from the ledger Succesfully..’);
    }
查询学生成绩:
由于我们在先前的addMarks()函数中将值以缓冲区的形式放置。一旦查询,它将返回buffer。因此我们需要将缓冲区转换为字符串并将其解析为原始javascript对象。
async queryMarks(ctx,studentId){
     let marksAsBytes = await ctx.stub.getState(studentId); 
     if (!marksAsBytes || marksAsBytes.toString().length <= 0) { 
       throw new Error(’Student with this Id does not exist: ‘); 
         } 
    let marks=JSON.parse(marksAsBytes.toString()); 
    return JSON.stringify(marks); 
   }
最终版智能合约代码
您可以在此处找到完整的智能合约。https://gist.github.com/Salmandabbakuti/fb25d0429359c6d77ab64d097c5b588c
‘use strict’;
const { Contract} = require(‘fabric-contract-api’);
class testContract extends Contract {
async queryMarks(ctx,studentId) {
    let marksAsBytes = await ctx.stub.getState(studentId); 
    if (!marksAsBytes || marksAsBytes.toString().length <= 0) {
      throw new Error(‘Student with this Id does not exist: ‘);
       }
      let marks=JSON.parse(marksAsBytes.toString());
      return JSON.stringify(marks);
  }
async addMarks(ctx,studentId,subject1,subject2,subject3) {
   let marks={
       subj1:subject1,
       subj2:subject2,
       subj3:subject3
       };
    await ctx.stub.putState(studentId,Buffer.from(JSON.stringify(marks))); 
    console.log(‘Student Marks added To the ledger Succesfully..’);
  }
async deleteMarks(ctx,studentId) {
    await ctx.stub.deleteState(studentId); 
    console.log(‘Student Marks deleted from the ledger Succesfully..’);
    }
}
module.exports=testContract;
为了安装和测试此智能合约,我将使用包含单个peer的基本网络。在这个网络上,我们将安装名为mycc的Node.js的chaincode到peer0.org1.example.com上,并在通道mychannel上实例化它。然后我们可以调用这些chaincode函数。确保在您的设置中安装了docker。为了简单起见,我已经将chaincode文件(logic.js,index.js,package.json)安装在chaincode/newcc目录中。
首先,我们需要启动网络并创建通道。
git clone https://github.com/Salmandabbakuti/hlf-chaincodeTest.git
cd hlf-chaincodeTest/basic-network
./start.sh
等待片刻。建立网络将需要一段时间。如果遇到任何权限错误,只需在root用户权限下运行就行。一旦我们的具有单个peer的网络建立并运行,我们就可以安装chaincode。
为了安装和调用chaincode,我们可以使用Peer的CLI容器。输入CLI容器
docker exec -it cli bash
安装和实例化Chaincode
peer chaincode install -n mycc -v 1.0 -p “/opt/gopath/src/github.com/newcc” -l “node”
peer chaincode instantiate -o orderer.example.com:7050 -C mychannel -n mycc -l “node” -v 1.0 -c ‘{“Args”:[]}’
增加学生标志
peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n 
mycc -c ‘{“function”:”addMarks”,”Args”:[“Alice”,”68″,”84″,”89″]}’
查询学生“Alice”的标志
peer chaincode query -o orderer.example.com:7050 -C mychannel -n 
mycc -c ‘{“function”:”queryMarks”,”Args”:[“Alice”]}’
从Ledger删除“Alice”标志
peer chaincode invoke -o orderer.example.com:7050 -C mychannel -n 
mycc -c ‘{“function”:”deleteMarks”,”Args”:[“Alice”]}’
上面的脚本将从账本中删除密钥“ Alice”和相关数据。如果再次查询Alice的标记,则会收到一条错误消息,提示“ studentId不存在”。
我还制作了一个自动化脚本,用于在客户端目录中安装和测试此chaincode。请按照以下步骤进行快速演示
首先,退出CLI容器并在客户端目录中运行脚本
exit # exits from CLI docker container if you’re in
cd ..
cd client  #change your directory to client
chmod a+x start.sh
./start.sh  #Automated script for testing
您也可以使用client / start.sh文件中定义的脚本手动调用chaincode函数。
结论
我们在这里演示了什么是Chaincode,chaincode的stub接口中的方法,chaincode的部署结构以及编写chaincode和在网络上进行部署的难易程度。希望本文能以某种方式帮助您开始编写chaincode并在网络上进行部署。