一、阐明
为了继续地进行信息的更新,以及对账本进行治理(写入交易,进行查问等),区块链网络引入了智能合约来实现对账本的拜访和管制;智能合约在 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"]}'