
一、說(shuō)明
為了持續(xù)地進(jìn)行信息的更新,以及對(duì)賬本進(jìn)行管理(寫(xiě)入交易,進(jìn)行查詢(xún)等),區(qū)塊鏈網(wǎng)絡(luò)引入了智能合約來(lái)實(shí)現(xiàn)對(duì)賬本的訪問(wèn)和控制;智能合約在 Fabric 中稱(chēng)之為 鏈碼,是區(qū)塊鏈應(yīng)用的業(yè)務(wù)邏輯。
本文分享如何使用 Java 語(yǔ)言開(kāi)發(fā)智能合約,以及合約的安裝與使用。
?
二、環(huán)境準(zhǔn)備
1、部署好 Fabric 的測(cè)試網(wǎng)絡(luò),按照上一篇文章《Hyperledger Fabric 2.x 環(huán)境搭建》的內(nèi)容執(zhí)行第1至5步
- 啟動(dòng)好兩個(gè) peer 節(jié)點(diǎn)和一個(gè) orderer 節(jié)點(diǎn)
- 創(chuàng)建好 mychannel 通道

2、在環(huán)境變量中配置好執(zhí)行命令(bin)、配置(config)與MSP文件夾的路徑:
執(zhí)行 vim /etc/profile 添加以下內(nèi)容:
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路徑按實(shí)際進(jìn)行修改。

?
三、下載合約代碼
gitee:https://gitee.com/zlt2000_admin/my-fabric-chaincode-java
github:https://github.com/zlt2000/my-fabric-chaincode-java
?
四、代碼解析
在 Fabric 2.x 版本后的合約編寫(xiě)方式與舊版本略有不同,需要實(shí)現(xiàn) ContractInterface 接口,下面是官方的一段說(shuō)明:
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文件
配置遠(yuǎn)程倉(cāng)庫(kù)
<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>
?
依賴(lài)合約sdk
<dependency>
<groupId>org.hyperledger.fabric-chaincode-java</groupId>
<artifactId>fabric-chaincode-shim</artifactId>
<version>${fabric-chaincode-java.version}</version>
</dependency>
?
通過(guò)插件 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
創(chuàng)建合約的數(shù)據(jù)對(duì)象 User 使用 @DataType 注解標(biāo)識(shí),定義三個(gè)字段 userId、name、money 使用 @Property 注解標(biāo)識(shí):
@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. 合約邏輯
- 合約類(lèi)使用
@Contract與@Default注解標(biāo)識(shí),并實(shí)現(xiàn)ContractInterface接口 - 合約方法使用
@Transaction注解標(biāo)識(shí)Transaction.TYPE.SUBMIT 為 寫(xiě)入交易
Transaction.TYPE.EVALUATE 為 查詢(xún) - 包含3個(gè)交易方法:
init、addUser、transfer - 包含2個(gè)查詢(xún)方法:
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);
}
/**
* 新增用戶(hù)
*/
@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;
}
/**
* 查詢(xún)某個(gè)用戶(hù)
*/
@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;
}
/**
* 查詢(xún)所有用戶(hù)
*/
@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);
}
/**
* 轉(zhuǎn)賬
* @param sourceId 源用戶(hù)id
* @param targetId 目標(biāo)用戶(hù)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));
}
}
?
五、打包合約代碼
把合約源代碼打包成壓縮文件,用于后續(xù)安裝:
peer lifecycle chaincode package mycc.tar.gz --path /opt/app/my-fabric-chaincode-java --lang java --label mycc
?
六、安裝合約
在指定 peer 節(jié)點(diǎn)上安裝鏈碼,下面分別為兩個(gè)機(jī)構(gòu)安裝。
6.1. 為機(jī)構(gòu)peer0.org1安裝合約
執(zhí)行以下命令,設(shè)置 peer0.org1 環(huán)境:
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
執(zhí)行以下命令安裝:
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. 為機(jī)構(gòu)peer0.org2安裝合約
執(zhí)行以下命令,設(shè)置 peer0.org2 環(huán)境:
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
執(zhí)行以下命令安裝:
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
?
七、審批合約
當(dāng)合約安裝后,需經(jīng)過(guò)機(jī)構(gòu)的審批達(dá)成一致后才允許使用。
?
7.1. 為機(jī)構(gòu)peer0.org1審批合約定義
執(zhí)行以下命令,設(shè)置 peer0.org1 環(huán)境:
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
執(zhí)行以下命令審批合約:
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 的值按實(shí)際進(jìn)行修改。
成功后返回:
2022-02-09 22:22:38.403 EST 0001 INFO [chaincodeCmd] ClientWait -> txid [2531db2811945a641947000cb15cfd19e0b72da594dfba994f5f79b6bc51bce2] committed with status (VALID) at localhost:7051
?
7.2. 為機(jī)構(gòu)peer0.org2審批合約定義
執(zhí)行以下命令,設(shè)置 peer0.org2 環(huán)境:
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
執(zhí)行以下命令審批合約:
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 的值按實(shí)際進(jìn)行修改。
成功后返回:
2022-02-09 22:22:47.711 EST 0001 INFO [chaincodeCmd] ClientWait -> txid [796a1e0a735e69425bcd5911bdf4b2a8003bbac977c5e60c769f84da6b86ef86] committed with status (VALID) at localhost:9051
?
7.3. 合約提交檢查
檢查合約的審批情況,是否可以向通道進(jìn)行提交:
peer lifecycle chaincode checkcommitreadiness --channelID mychannel --name mycc --version 1.0 --sequence 1 --output json
返回:
{
"approvals": {
"Org1MSP": true,
"Org2MSP": true
}
}
代表 Org1 和 Org2 都審批通過(guò)
?
八、提交合約
執(zhí)行以下命令,向通道提交合約:
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
查看通道上已經(jīng)提交的合約:
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
}
}
?
九、測(cè)試智能合約
- 交易數(shù)據(jù)使用
peer chaincode invoke [flags]命令,該命令將嘗試向網(wǎng)絡(luò)提交背書(shū)過(guò)的交易。 - 查詢(xún)數(shù)據(jù)使用
peer chaincode query [flags],該命令不會(huì)生成交易。
由于 invoke 命令所需要的參數(shù)較多,所以我們先創(chuàng)建一個(gè)腳本命令。
執(zhí)行 vim invoke.sh 添加以下內(nèi)容:
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. 初始化賬本
執(zhí)行以下命令,調(diào)用合約的 init 方法初始化3條賬本記錄:
sh invoke.sh '{"function":"init","Args":[]}'
?
9.2. 查詢(xún)數(shù)據(jù)
需要連接其中一個(gè) peer 節(jié)點(diǎn)進(jìn)行數(shù)據(jù)查詢(xún)
執(zhí)行以下命令,設(shè)置 peer0.org1 環(huán)境:
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
執(zhí)行下面命令,調(diào)用 queryAll 方法,查詢(xún)所有數(shù)據(jù):
peer chaincode query -C mychannel -n mycc -c '{"Args":["queryAll"]}'
執(zhí)行后返回3條數(shù)據(jù)的數(shù)組:
[{"money":100.0,"name":"zlt","userId":"1"},{"money":200.0,"name":"admin","userId":"2"},{"money":300.0,"name":"guest","userId":"3"}]
?
執(zhí)行下面命令,調(diào)用 getUser 方法傳入 1 參數(shù),查詢(xún)單個(gè)數(shù)據(jù):
peer chaincode query -C mychannel -n mycc -c '{"Args":["getUser", "1"]}'
執(zhí)行后返回id為1的數(shù)據(jù):
{"money":100,"name":"zlt","userId":"1"}
?
9.3. 新增數(shù)據(jù)
執(zhí)行以下命令,調(diào)用 addUser 方法,新增一條id為4的記錄:
sh invoke.sh '{"function":"addUser","Args":["4","test","400"]}'
?
9.4. 轉(zhuǎn)賬
執(zhí)行以下命令,調(diào)用 transfer 方法,進(jìn)行轉(zhuǎn)賬操作:
sh invoke.sh '{"function":"transfer","Args":["4","1","400"]}'
轉(zhuǎn)賬成功后,使用查詢(xún)命令進(jìn)行查看:
peer chaincode query -C mychannel -n mycc -c '{"Args":["queryAll"]}'
