Hyperledger Fabric 2.x 自定义智能合约

一、说明
为了持续地进行信息的更新,以及对账本进行管理(写入交易,进行查询等),区块链网络引入了智能合约来实现对账本的访问和控制;智能合约在 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 指定 mainClass 为 org.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 注解标识,定义三个字段 userId、name、money 使用 @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. 合约逻辑
- 合约类使用
@Contract与@Default注解标识,并实现ContractInterface接口 - 合约方法使用
@Transaction注解标识
Transaction.TYPE.SUBMIT 为 写入交易
Transaction.TYPE.EVALUATE 为 查询 - 包含3个交易方法:
init、addUser、transfer - 包含2个查询方法:
getUser、queryAll
@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 ID 与 Label:
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
}
}
九、测试智能合约
- 交易数据使用
peer chaincode invoke [flags]命令,该命令将尝试向网络提交背书过的交易。 - 查询数据使用
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 自定义智能合约的更多相关文章
- Hyperledger Fabric 2.x 动态更新智能合约
一.说明 在上一篇文章中分享了智能合约的安装与使用,如果业务有变更代码需要修改怎么办呢?本文分享如何对已安装的合约进行版本更新. 二.环境准备 区块链网络安装:<Hyperledger Fabr ...
- Hyperledger Fabric 2.x Java区块链应用
一.说明 在上一篇文章中 <Hyperledger Fabric 2.x 自定义智能合约> 分享了智能合约的安装并使用 cli 客户端进行合约的调用:本文将使用 Java 代码基于 fab ...
- Hyperledger Fabric 核心概念
一.说明 区块链是一个透明的,基于不可变模式的去中心化系统,核心就是一个分布式账本,记录网络上发生的所有交易. 区块链网络主要有三种类型:公共区块链.联盟区块链,以及私有区块链:我们熟知的比特币.以太 ...
- Hyperledger Fabric Model——超级账本组成模型
超级账本组成模型 本文主要讲述Hyperledger Fabric的关键设计特性,并细述如何实现了一个全面的.可定制的企业级区块链解决方案: 资产定义--资产这里理解为任何具有货币价值的东西,它们都可 ...
- SAP云平台,区块链,超级账本和智能合约
前一篇文章<Hyperledger Fabric on SAP Cloud Platform>,我的同事Aviva已经给大家介绍了基于区块链技术的超级账本(Hyperledger)的一些概 ...
- hyperledger fabric 智能合约开发
开发步奏: 1.创建教育联盟 2.区块链服务平台自动生成通道id 3.区块链网络服务人员通过命令行在区块链网络中创建对应通道 4.创建相关教育组织 5.邀请相关组织加入联盟 6.区块链网络管理人员通过 ...
- Hyperledger Fabric Chaincode for Operators——实操智能合约
什么是Chaincode(智能合约)? chaincode是一个程序,它是使用Go语言编写的,最终在Java等其他编程语言中实现了指定的接口.chaincode运行在一个被背书peer进程独立出来的安 ...
- 用Java为Hyperledger Fabric(超级账本)编写区块链智能合约链代码
编写第一个 Java 链代码程序 在上一节中,您已经熟悉了如何构建.运行.部署和调用链代码,但尚未编写任何 Java 代码. 在本节中,将会使用 Eclipse IDE.一个用于 Eclipse 的 ...
- HyperLedger Fabric 1.4 智能合约 Helloworld运行(9)
9.1 Helloworld案例简介 通过执行官方End-2-End案例,初始了解Fabric网络的运行流程及yaml配置,官方End-2-End案例把执行过程集成,通过一条命令即可完成全 ...
随机推荐
- Shell 中的 expect 命令
目录 expect 介绍 expect 安装 expect 语法 自动拷贝文件到远程主机 示例一 示例二 示例三 示例四 expect 介绍 借助 expect 处理交互的命令,可以将交互过程如 ss ...
- 闯祸了,生成环境执行了DDL操作《死磕MySQL系列 十四》
由于业务随着时间不停的改变,起初的表结构设计已经满足不了如今的需求,这时你是不是想那就加字段呗!加字段也是个艺术活,接下来由本文的主人咔咔给你吹. 试想一下这个场景 事务A在执行一个非常大的查询 事务 ...
- yum安装软件时,出现"No package XXX available"的解决办法
第一种: 依次执行以下命令解决 1,cd /home 2,wget http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noa ...
- leetcode 233. 数字 1 的个数
问题描述 给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数. 示例: 输入: 13 输出: 6 解释: 数字 1 出现在以下数字中: 1, 10, 11, 12, 13 . 问 ...
- [开发笔记usbTOcan]用树莓派搭建私有Git服务器
0 | 思路 在开始编程前,先创建一个版本管理库,以前一直用SVN,但目前用Git的还是比较,正好利用这个机会学习GIt. 想过使用Github提供的免费服务器,但项目目前还没有做开源的准备,于是就有 ...
- ros实例_百度语音+图灵
1 百度语音模块 参考http://blog.csdn.net/u011118482/article/details/55001444 1.1 百度语音识别包 git clonehttps://git ...
- 使用MobaXterm发布服务器项目文件
注:笔记旨在记录 目录 一.使用MobaXterm登录服务器 二.后端发布 三.前端发布 一.使用MobaXterm登录服务器 打开MobaXterm,点击左上角Session->SSH,输入账 ...
- iptables规则管理
查看规则 iptables -t filter -L INPUT -n -v --line 省略-t选项时,表示默认操作filter表中的规则 添加规则 注意点:添加规则时,规则的顺序非常重要 - ...
- Redis入门及环境搭建
一:Redis简介 Redis(Remote Dictionary Server 远程字典服务)是一个开源的(BSD许可的)内存数据结构存储,用作数据库.高速缓存和消息队列代理. Redis提供五大基 ...
- 如何快速写出高质量的 Go 代码?
前言 团队协作开发中,必然存在着不同的代码风格,并且诸如 http body close,unhandled error 等低级错误不能完全避免.通过使用 ci lint 能够及早的发现并修复问题,提 ...