一、说明
为了持续地进行信息的更新,以及对账本进行管理(写入交易,进行查询等),区块链网络引入了智能合约来实现对账本的访问和控制;智能合约在 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
三、下载合约代码
gitee:https://gitee.com/zlt2000_admin/my-fabric-chaincode-java
github:https://github.com/zlt2000/my-fabric-chaincode-java
四、代码解析
在 fabric 2.x
版本后的合约编写方式与旧版本略有不同,需要实现 contractinterface
接口,下面是官方的一段说明:
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
<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
注解标识 - 包含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
成功后返回:
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
成功后返回:
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
}
}
八、提交合约
执行以下命令,向通道提交合约:
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. 查询数据
执行以下命令,设置 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"]}'
扫码关注有惊喜!
发表评论