一、说明

为了持续地进行信息的更新,以及对账本进行管理(写入交易,进行查询等),区块链网络引入了智能合约来实现对账本的访问和控制;智能合约在 Fabric 中称之为 链码,是区块链应用的业务逻辑。

本文分享如何使用 Java 语言开发智能合约,以及合约的安装与使用。

二、环境准备

1、部署好 Fabric 的测试网络,按照上一篇文章《Hyperledger Fabric 2.x 环境搭建》的内容执行第1至5步

- 启动好两个 peer 节点和一个 orderer 节点
- 创建好 mychannel 通道

2、在环境变量中配置好执行命令(bin)、配置(config)与MSP文件夹的路径:

执行 vim /etc/profile 添加以下内容:

export FABRIC_PATH=/opt/gopath/src/github.com/hyperledger/fabric-samples
export FABRIC_CFG_PATH=${FABRIC_PATH}/config/
export MSP_PATH=${FABRIC_PATH}/test-network/organizations
export CORE_PEER_TLS_ENABLED=true
export PATH=${FABRIC_PATH}/bin:$PATH

FABRIC_PATH路径按实际进行修改。

三、下载合约代码

gitee:https://gitee.com/zlt2000_admin/my-fabric-chaincode-java

github:https://github.com/zlt2000/my-fabric-chaincode-java

四、代码解析

Fabric 2.x 版本后的合约编写方式与旧版本略有不同,需要实现 ContractInterface 接口,下面是官方的一段说明:

All chaincode implementations must extend the abstract class ChaincodeBase. It is possible to implement chaincode by extending ChaincodeBase directly however new projects should implement org.hyperledger.fabric.contract.ContractInterface and use the contract programming model instead.

4.1. pom.xml文件

配置远程仓库

<repositories>
<repository>
<id>central</id>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
<releases>
<enabled>true</enabled>
</releases>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://www.jitpack.io</url>
</repository>
<repository>
<id>artifactory</id>
<url>https://hyperledger.jfrog.io/hyperledger/fabric-maven</url>
</repository>
</repositories>

依赖合约sdk

<dependency>
<groupId>org.hyperledger.fabric-chaincode-java</groupId>
<artifactId>fabric-chaincode-shim</artifactId>
<version>${fabric-chaincode-java.version}</version>
</dependency>

通过插件 maven-shade-plugin 指定 mainClassorg.hyperledger.fabric.contract.ContractRouter

新版本所有合约的 mainClass 都为 org.hyperledger.fabric.contract.ContractRouter

<build>
<sourceDirectory>src/main/java</sourceDirectory>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<finalName>chaincode</finalName>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
<mainClass>org.hyperledger.fabric.contract.ContractRouter</mainClass>
</transformer>
</transformers>
<filters>
<filter>
<!-- filter out signature files from signed dependencies, else repackaging fails with security ex -->
<artifact>*:*</artifact>
<excludes>
<exclude>META-INF/*.SF</exclude>
<exclude>META-INF/*.DSA</exclude>
<exclude>META-INF/*.RSA</exclude>
</excludes>
</filter>
</filters>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>

4.2. model

创建合约的数据对象 User 使用 @DataType 注解标识,定义三个字段 userIdnamemoney 使用 @Property 注解标识:

@DataType
public class User {
@Property
private final String userId; @Property
private final String name; @Property
private final double money; public User(final String userId, final String name, final double money) {
this.userId = userId;
this.name = name;
this.money = money;
} @Override
public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
if ((obj == null) || (getClass() != obj.getClass())) {
return false;
}
User other = (User) obj;
return Objects.deepEquals(
new String[] {getUserId(), getName()},
new String[] {other.getUserId(), other.getName()})
&&
Objects.deepEquals(
new double[] {getMoney()},
new double[] {other.getMoney()});
} @Override
public int hashCode() {
return Objects.hash(getUserId(), getName(), getMoney());
} @Override
public String toString() {
return JSON.toJSONString(this);
} public String getUserId() {
return userId;
} public String getName() {
return name;
} public double getMoney() {
return money;
}
}

4.3. 合约逻辑

  1. 合约类使用 @Contract@Default 注解标识,并实现 ContractInterface 接口
  2. 合约方法使用 @Transaction 注解标识

    Transaction.TYPE.SUBMIT 为 写入交易

    Transaction.TYPE.EVALUATE 为 查询

  3. 包含3个交易方法:initaddUsertransfer
  4. 包含2个查询方法:getUserqueryAll
@Contract(name = "mycc")
@Default
public class MyAssetChaincode implements ContractInterface {
public MyAssetChaincode() {} /**
* 初始化3条记录
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void init(final Context ctx) {
addUser(ctx, "1", "zlt",100D);
addUser(ctx, "2", "admin",200D);
addUser(ctx, "3", "guest",300D);
} /**
* 新增用户
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public User addUser(final Context ctx, final String userId, final String name, final double money) {
ChaincodeStub stub = ctx.getStub();
User user = new User(userId, name, money);
String userJson = JSON.toJSONString(user);
stub.putStringState(userId, userJson);
return user;
} /**
* 查询某个用户
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public User getUser(final Context ctx, final String userId) {
ChaincodeStub stub = ctx.getStub();
String userJSON = stub.getStringState(userId);
if (userJSON == null || userJSON.isEmpty()) {
String errorMessage = String.format("User %s does not exist", userId);
throw new ChaincodeException(errorMessage);
}
User user = JSON.parseObject(userJSON, User.class);
return user;
} /**
* 查询所有用户
*/
@Transaction(intent = Transaction.TYPE.EVALUATE)
public String queryAll(final Context ctx) {
ChaincodeStub stub = ctx.getStub();
List<User> userList = new ArrayList<>();
QueryResultsIterator<KeyValue> results = stub.getStateByRange("", "");
for (KeyValue result: results) {
User user = JSON.parseObject(result.getStringValue(), User.class);
System.out.println(user);
userList.add(user);
}
return JSON.toJSONString(userList);
} /**
* 转账
* @param sourceId 源用户id
* @param targetId 目标用户id
* @param money 金额
*/
@Transaction(intent = Transaction.TYPE.SUBMIT)
public void transfer(final Context ctx, final String sourceId, final String targetId, final double money) {
ChaincodeStub stub = ctx.getStub();
User sourceUser = getUser(ctx, sourceId);
User targetUser = getUser(ctx, targetId);
if (sourceUser.getMoney() < money) {
String errorMessage = String.format("The balance of user %s is insufficient", sourceId);
throw new ChaincodeException(errorMessage);
}
User newSourceUser = new User(sourceUser.getUserId(), sourceUser.getName(), sourceUser.getMoney() - money);
User newTargetUser = new User(targetUser.getUserId(), targetUser.getName(), targetUser.getMoney() + money);
stub.putStringState(sourceId, JSON.toJSONString(newSourceUser));
stub.putStringState(targetId, JSON.toJSONString(newTargetUser));
}
}

五、打包合约代码

把合约源代码打包成压缩文件,用于后续安装:

peer lifecycle chaincode package mycc.tar.gz --path /opt/app/my-fabric-chaincode-java --lang java --label mycc

六、安装合约

在指定 peer 节点上安装链码,下面分别为两个机构安装。

6.1. 为机构peer0.org1安装合约

执行以下命令,设置 peer0.org1 环境:

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

执行以下命令安装:

peer lifecycle chaincode install mycc.tar.gz

成功后返回:

2022-02-09 22:09:13.498 EST 0001 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Installed remotely: response:<status:200 payload:"\nEmycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae\022\004mycc" >
2022-02-09 22:09:13.498 EST 0002 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Chaincode code package identifier: mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae

6.2. 为机构peer0.org2安装合约

执行以下命令,设置 peer0.org2 环境:

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

执行以下命令安装:

peer lifecycle chaincode install mycc.tar.gz

成功后返回:

2022-02-09 22:14:14.862 EST 0001 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Installed remotely: response:<status:200 payload:"\nEmycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae\022\004mycc" >
2022-02-09 22:14:14.862 EST 0002 INFO [cli.lifecycle.chaincode] submitInstallProposal -> Chaincode code package identifier: mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae

查看安装的合约清单:

peer lifecycle chaincode queryinstalled

返回合约的 Package IDLabel

Installed chaincodes on peer:
Package ID: mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae, Label: mycc

七、审批合约

当合约安装后,需经过机构的审批达成一致后才允许使用。

7.1. 为机构peer0.org1审批合约定义

执行以下命令,设置 peer0.org1 环境:

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

执行以下命令审批合约:

peer lifecycle chaincode approveformyorg \
-o localhost:7050 \
--ordererTLSHostnameOverride orderer.example.com \
--tls
--cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
--channelID mychannel \
--name mycc \
--version 1.0 \
--package-id mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae \
--sequence 1

package-id 的值按实际进行修改。

成功后返回:

2022-02-09 22:22:38.403 EST 0001 INFO [chaincodeCmd] ClientWait -> txid [2531db2811945a641947000cb15cfd19e0b72da594dfba994f5f79b6bc51bce2] committed with status (VALID) at localhost:7051

7.2. 为机构peer0.org2审批合约定义

执行以下命令,设置 peer0.org2 环境:

export CORE_PEER_LOCALMSPID="Org2MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org2.example.com/users/Admin@org2.example.com/msp
export CORE_PEER_ADDRESS=localhost:9051

执行以下命令审批合约:

peer lifecycle chaincode approveformyorg \
-o localhost:7050 \
--ordererTLSHostnameOverride orderer.example.com \
--tls \
--cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
--channelID mychannel \
--name mycc \
--version 1.0 \
--package-id mycc:4c8dce2c7f746d26293ca8f27a3ccdec8d6438090f873f40f8ac9508c01973ae \
--sequence 1

package-id 的值按实际进行修改。

成功后返回:

2022-02-09 22:22:47.711 EST 0001 INFO [chaincodeCmd] ClientWait -> txid [796a1e0a735e69425bcd5911bdf4b2a8003bbac977c5e60c769f84da6b86ef86] committed with status (VALID) at localhost:9051

7.3. 合约提交检查

检查合约的审批情况,是否可以向通道进行提交:

peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name mycc --version 1.0 --sequence 1 --output json

返回:

{
"approvals": {
"Org1MSP": true,
"Org2MSP": true
}
}

代表 Org1 和 Org2 都审批通过

八、提交合约

执行以下命令,向通道提交合约:

peer lifecycle chaincode commit \
-o localhost:7050 \
--ordererTLSHostnameOverride orderer.example.com \
--tls \
--cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
--channelID mychannel \
--name mycc \
--peerAddresses localhost:7051 \
--tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \
--peerAddresses localhost:9051 \
--tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \
--version 1.0 \
--sequence 1

成功后返回:

2022-02-09 22:22:57.445 EST 0001 INFO [chaincodeCmd] ClientWait -> txid [97ded758675113b9339dc9b378a13c0790ea3780855bb8f651758bfb007fc1ec] committed with status (VALID) at localhost:7051
2022-02-09 22:22:57.456 EST 0002 INFO [chaincodeCmd] ClientWait -> txid [97ded758675113b9339dc9b378a13c0790ea3780855bb8f651758bfb007fc1ec] committed with status (VALID) at localhost:9051

查看通道上已经提交的合约:

peer lifecycle chaincode querycommitted --channelID mychannel --name mycc --output json

返回:

{
"sequence": 1,
"version": "1.0",
"endorsement_plugin": "escc",
"validation_plugin": "vscc",
"validation_parameter": "EiAvQ2hhbm5lbC9BcHBsaWNhdGlvbi9FbmRvcnNlbWVudA==",
"collections": {},
"approvals": {
"Org1MSP": true,
"Org2MSP": true
}
}

九、测试智能合约

  1. 交易数据使用 peer chaincode invoke [flags] 命令,该命令将尝试向网络提交背书过的交易。
  2. 查询数据使用 peer chaincode query [flags],该命令不会生成交易。

由于 invoke 命令所需要的参数较多,所以我们先创建一个脚本命令。

执行 vim invoke.sh 添加以下内容:

peer chaincode invoke -o localhost:7050 \
--ordererTLSHostnameOverride orderer.example.com \
--tls \
--cafile ${MSP_PATH}/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem \
-C mychannel \
-n mycc \
--peerAddresses localhost:7051 \
--tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt \
--peerAddresses localhost:9051 \
--tlsRootCertFiles ${MSP_PATH}/peerOrganizations/org2.example.com/peers/peer0.org2.example.com/tls/ca.crt \
-c ${1}

9.1. 初始化账本

执行以下命令,调用合约的 init 方法初始化3条账本记录:

sh invoke.sh '{"function":"init","Args":[]}'

9.2. 查询数据

需要连接其中一个 peer 节点进行数据查询

执行以下命令,设置 peer0.org1 环境:

export CORE_PEER_LOCALMSPID="Org1MSP"
export CORE_PEER_TLS_ROOTCERT_FILE=${MSP_PATH}/peerOrganizations/org1.example.com/peers/peer0.org1.example.com/tls/ca.crt
export CORE_PEER_MSPCONFIGPATH=${MSP_PATH}/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp
export CORE_PEER_ADDRESS=localhost:7051

执行下面命令,调用 queryAll 方法,查询所有数据:

peer chaincode query -C mychannel -n mycc -c '{"Args":["queryAll"]}'

执行后返回3条数据的数组:

[{"money":100.0,"name":"zlt","userId":"1"},{"money":200.0,"name":"admin","userId":"2"},{"money":300.0,"name":"guest","userId":"3"}]

执行下面命令,调用 getUser 方法传入 1 参数,查询单个数据:

peer chaincode query -C mychannel -n mycc -c '{"Args":["getUser", "1"]}'

执行后返回id为1的数据:

{"money":100,"name":"zlt","userId":"1"}

9.3. 新增数据

执行以下命令,调用 addUser 方法,新增一条id为4的记录:

sh invoke.sh '{"function":"addUser","Args":["4","test","400"]}'

9.4. 转账

执行以下命令,调用 transfer 方法,进行转账操作:

sh invoke.sh '{"function":"transfer","Args":["4","1","400"]}'

转账成功后,使用查询命令进行查看:

peer chaincode query -C mychannel -n mycc -c '{"Args":["queryAll"]}'

扫码关注有惊喜!

Hyperledger Fabric 2.x 自定义智能合约的更多相关文章

  1. Hyperledger Fabric 2.x 动态更新智能合约

    一.说明 在上一篇文章中分享了智能合约的安装与使用,如果业务有变更代码需要修改怎么办呢?本文分享如何对已安装的合约进行版本更新. 二.环境准备 区块链网络安装:<Hyperledger Fabr ...

  2. Hyperledger Fabric 2.x Java区块链应用

    一.说明 在上一篇文章中 <Hyperledger Fabric 2.x 自定义智能合约> 分享了智能合约的安装并使用 cli 客户端进行合约的调用:本文将使用 Java 代码基于 fab ...

  3. Hyperledger Fabric 核心概念

    一.说明 区块链是一个透明的,基于不可变模式的去中心化系统,核心就是一个分布式账本,记录网络上发生的所有交易. 区块链网络主要有三种类型:公共区块链.联盟区块链,以及私有区块链:我们熟知的比特币.以太 ...

  4. Hyperledger Fabric Model——超级账本组成模型

    超级账本组成模型 本文主要讲述Hyperledger Fabric的关键设计特性,并细述如何实现了一个全面的.可定制的企业级区块链解决方案: 资产定义--资产这里理解为任何具有货币价值的东西,它们都可 ...

  5. SAP云平台,区块链,超级账本和智能合约

    前一篇文章<Hyperledger Fabric on SAP Cloud Platform>,我的同事Aviva已经给大家介绍了基于区块链技术的超级账本(Hyperledger)的一些概 ...

  6. hyperledger fabric 智能合约开发

    开发步奏: 1.创建教育联盟 2.区块链服务平台自动生成通道id 3.区块链网络服务人员通过命令行在区块链网络中创建对应通道 4.创建相关教育组织 5.邀请相关组织加入联盟 6.区块链网络管理人员通过 ...

  7. Hyperledger Fabric Chaincode for Operators——实操智能合约

    什么是Chaincode(智能合约)? chaincode是一个程序,它是使用Go语言编写的,最终在Java等其他编程语言中实现了指定的接口.chaincode运行在一个被背书peer进程独立出来的安 ...

  8. 用Java为Hyperledger Fabric(超级账本)编写区块链智能合约链代码

    编写第一个 Java 链代码程序 在上一节中,您已经熟悉了如何构建.运行.部署和调用链代码,但尚未编写任何 Java 代码. 在本节中,将会使用 Eclipse IDE.一个用于 Eclipse 的 ...

  9. HyperLedger Fabric 1.4 智能合约 Helloworld运行(9)

    9.1 Helloworld案例简介       通过执行官方End-2-End案例,初始了解Fabric网络的运行流程及yaml配置,官方End-2-End案例把执行过程集成,通过一条命令即可完成全 ...

随机推荐

  1. [Docker] 在CentOS6.8 安装 Docker

    运行docker Linux内核版本需要在3.8以上,针对centos6.5 内核为2.6的系统需要先升级内核.不然会特别卡,退出容器. # 查看当前版本: cat /etc/issue # 导入pu ...

  2. 为什么重写equals的同时要重写hashcode

    在覆盖equals方法的时候,你必须要遵守它的通用约定.下面是约定的内容,来自Object的规范[JavaSE6] 自反性.对于任何非null的引用值x,x.equals(x)必须返回true. 对称 ...

  3. linux高大上命令001

        [ linux命令 sed ] sed是一个管道命令,可以分析standard input 而且sed可以将数据进行替换.删除.新增.选取的特定的功能 sed [-nefr] [动作] -n ...

  4. scanf报错问题

    VS2013 scanf()函数报错问题. 编译器就是说这个环境下用scanf()函数不安全,建议使用scanf_s()函数.如何要使用scanf()函数的话,就需要在头文件或者源文件开头加上:use ...

  5. 【reverse】逆向4 初识堆栈

    [reverse]逆向4 初识堆栈 1.问题引入 假设我们需要一块内存,有如下的要求 主要用于临时存储一些数据(如果数据很少可以放入寄存器中) 能够记录存了多少数据 能够非常快速的找到某个数据 2.模 ...

  6. 图片不清晰?Graphics 高质量绘制

    Graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic; Graphics ...

  7. 5.12-jsp分页功能学习

    1.分页功能相关资料查询 分页须知知识点: (1)JDBC2.0的可滚动结果集. (2)HTTP GET请求. 一.可滚动结果集   Connection con  = DriverManager.g ...

  8. 使用3D Tiles Overview学习3D Tiles

    Cesium中文网:http://cesiumcn.org/ | 国内快速访问:http://cesium.coinidea.com/ 3D Tiles的创建是为了在web上传输大量的3D数据集.作为 ...

  9. Vue框架怎么使用mediainfo.js来检测视频文件是否有音轨?

    啥是MediaInfo? 简而言之,MediaInfo是一个能读取音频和视频文件并分析和输出音视频信息的工具,能输出的内容包括视频信息,音轨,字幕等. MediaInfo也可以在web端使用,需要使用 ...

  10. python文档2-unittest单元测试之mock.patch

    介绍mock里面另一种实现方式,patch装饰器的使用,patch() 作为函数装饰器,为您创建模拟并将其传递到装饰函数 patch简介 1.unittest.mock.patch(target,ne ...