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案例把执行过程集成,通过一条命令即可完成全 ...
随机推荐
- spring cloud Zuul 多层拦截 --- 心得
1.前言 根据教材.博客文章的实例实操,基本都是单层拦截,没有找到多层拦截的具体写法 ,让我走了很多弯路,我将其写在这里,以待以后参考. 2.环境 spring boot : 2.1.6.RELEAS ...
- SpringBoot学习笔记二之Spring整合Mybatis
原文链接: https://www.toutiao.com/i6803235766274097678/ 在learn-admin-component子工程中加入搭建环境所需要的具体依赖(因为比较长配置 ...
- vscode 前端好用插件汇总
本篇文章根据实际开发中使用的扩展插件,结合<精选!15 个必备的 VSCode 插件(前端类)>.<vscode 插件推荐 - 献给所有前端工程师(2017.8.18更新)>中 ...
- java基础04-数据类型扩展及面试题
java基础04-数据类型扩展及面试题讲解 public class demo02 { public static void main(String[] args){ // 一.整数拓展: 进制 二进 ...
- 【记录一个问题】tesla model 3在行驶1000公里后,行驶中踩下刹车出现告警“同时踩下制动踏板和电门”
问题的描述如下:1.行驶1000公里左右后出现,之前没有:2.超过一定速度后再踩刹车出现此告警,如果低速环境频繁起停则不会出现:3.告警的意思是使用者同时踩下了刹车和电门,第一次电话客服反馈--检查脚 ...
- 【小记录】arm64下的原子加
1.代码中使用atomic_add aarch64下面并没有任何关于atomic的头文件 编译出现错误: /Users/ahfu/code/android/android-ndk-r14b/toolc ...
- presence_of_element_located对比visibility_of_element_located
presence_of_element_located和visibility_of_element_located都是selenium里判断元素展示的方法,相信做ui自动化的小伙伴一定被这俩困扰过,本 ...
- [源码分析] Facebook如何训练超大模型---(4)
[源码分析] Facebook如何训练超大模型 --- (4) 目录 [源码分析] Facebook如何训练超大模型 --- (4) 0x00 摘要 0x01 背景知识 1.1 单精度.双精度和半精度 ...
- Spring系列7:`autowire`自动装配怎么玩
回顾 前几篇我们介绍各种依赖依赖注入,都是显式指定的,配置明确但同时也有些繁杂和重复."很多发明的出发点,都是为了偷懒,懒人是推动社会进步的原动力".Spring 提供了自动注入依 ...
- django之memcached缓存系统
django其他缓存方法:(https://www.cnblogs.com/jishuweiwang/p/6110809.html) memcached版本 <1.5 1. memcached缓 ...