Java智能合约部署及调用
之前学习的Fabric智能合约及SDK都是使用Golang语言编写的,但做应用的同事使用的都是Java语言;为了打通这层语言上的隔阂,开始学习使用Java语言编写智能合约及SDK。
环境概述
- 开发环境:Windows 10 家庭版、WSL2 - Ubuntu 20.04 LTS
- 开发软件:eclipse
- Java版本:1.8.0_191
- 依赖管理:maven
区块链
这里使用fabric-samples
v1.4.4
版本的区块链网络,共识算法为 solo
。
智能合约
这里使用eclipse
作为开发环境,使用maven
管理项目依赖,其它平台及项目依赖管理方法请自行摸索。
开发
在eclipse中新建一个maven项目

修改pom.xml
文件,管理项目依赖
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hu</groupId>
<artifactId>chaincode2</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.hyperledger.fabric-chaincode-java</groupId>
<artifactId>fabric-chaincode-shim</artifactId>
<!-- fabric版本 -->
<version>1.4.0</version>
</dependency>
</dependencies>
<build>
<defaultGoal>compile</defaultGoal>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<!-- Java版本 -->
<source>1.8</source>
<target>1.8</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>com.hu.ChaincodeDemo</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>
</project>
启动类
ChaincodeDemo.java
package com.hu;
import java.util.List;
import org.bouncycastle.util.Strings;
import org.hyperledger.fabric.shim.ChaincodeBase;
import org.hyperledger.fabric.shim.ChaincodeStub;
public class ChaincodeDemo extends ChaincodeBase {
public static void main(String[] args) {
// TODO Auto-generated method stub
new ChaincodeDemo().start(args);
}
@Override
public Response init(ChaincodeStub stub) {
// TODO Auto-generated method stub
// 获取初始化参数
List<String> parameters = stub.getParameters();
System.out.println("Chaincode init success. args: " + parameters.toString());
return new Response(200, "success", null);
}
@Override
public Response invoke(ChaincodeStub stub) {
// TODO Auto-generated method stub
//获取方法名
String function = stub.getFunction();
//获取参数
List<String> parameters = stub.getParameters();
System.out.println("function: " + function + " args:" + parameters.toString());
return new Response(200, "success", Strings.toByteArray("链码调用成功"));
}
}
尝试build一下,防止实例化链码时编译失败:右键项目 - Run As - Maven Build - Run 。

安装
将项目复制到fabric-samples
项目的chaincode
目录下:

进入cli容器,fabric-samples
的 cli
容器中已经设置好了一些默认的环境变量,追加设置一些环境变量方便操作:
hu@DESKTOP-5UBMFB7:~/goProject/fabricProject/fabric-samples/chaincode$ docker exec -it cli /bin/bash
root@792829121c3b:/opt/gopath/src/github.com/hyperledger/fabric/peer# echo $CORE_PEER_ADDRESS
peer0.org1.example.com:7051
root@792829121c3b:/opt/gopath/src/github.com/hyperledger/fabric/peer# echo $CORE_PEER_LOCALMSPID
Org1MSP
# orderer证书位置
root@792829121c3b:/opt/gopath/src/github.com/hyperledger/fabric/peer# export ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem
安装合约:peer chaincode install -n 合约名称 -v 合约版本号 -p 合约位置 -l 合约语言
root@792829121c3b:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode install -n chaincode2 -v 1.0 -p /opt/gopath/src/github.com/chaincode/chaincode2/ -l java
2021-08-12 08:09:01.574 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2021-08-12 08:09:01.574 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
2021-08-12 08:09:01.584 UTC [chaincodeCmd] install -> INFO 003 Installed remotely response:<status:200 payload:"OK" >
实例化合约:peer chaincode instantiate -o orderer节点 --tls 是否启用tls --cafile 节点证书 -C 通道名 -n 合约名 -v 合约版本 -c 合约参数
root@792829121c3b:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer chaincode instantiate -o orderer.example.com:7050 --tls true --cafile $ORDERER_CA -C mychannel -n chaincode2 -v 1.0 -c '{"Args":["init"]}'
2021-08-12 08:20:07.810 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 001 Using default escc
2021-08-12 08:20:07.810 UTC [chaincodeCmd] checkChaincodeCmdParams -> INFO 002 Using default vscc
合约实例化时,会使用fabric-javaenv
容器尝试编译合约。

编译日志

编译完成后,会有一个该合约的容器

ps:合约编译可能会花费很多时间,导致cli容器执行实例化合约命令时超时。如果实例化命令超时,可再次执行该实例化命令。
SDK
开发
在eclipse
中新建一个maven
项目,将fabric-samples
项目生成的身份证书(fabric-samples/first-network/crypto-config
)复制到该项目下。

修改pom.xml
文件,管理项目依赖
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.hu</groupId>
<artifactId>sdkDemo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<dependencies>
<dependency>
<groupId>org.hyperledger.fabric-sdk-java</groupId>
<artifactId>fabric-sdk-java</artifactId>
<version>2.0.0</version>
</dependency>
</dependencies>
</project>
新建一个User类,用于身份合约调用时的身份认证
User.java
package com.hu.entity;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.PrivateKey;
import java.util.Set;
import org.hyperledger.fabric.sdk.Enrollment;
import org.hyperledger.fabric.sdk.User;
import org.hyperledger.fabric.sdk.identity.X509Enrollment;
import org.hyperledger.fabric.sdk.security.CryptoPrimitives;
public class MyUser implements User {
private String name;
private String mspId;
private Enrollment enrollment;
public MyUser(String name, String mspId, String keyFile, String certFile) throws Exception {
this.name = name;
this.mspId = mspId;
enrollment = loadFromPemFile(keyFile, certFile);
}
private Enrollment loadFromPemFile(String keyFile, String certFile) throws Exception {
byte[] keyPem = Files.readAllBytes(Paths.get(keyFile)); // 载入私钥PEM文本
byte[] certPem = Files.readAllBytes(Paths.get(certFile)); // 载入证书PEM文本
CryptoPrimitives suite = new CryptoPrimitives(); // 载入密码学套件
PrivateKey privateKey = suite.bytesToPrivateKey(keyPem); // 将PEM文本转换为私钥对象
return new X509Enrollment(privateKey, new String(certPem)); // 创建并返回X509Enrollment对象
}
public String getName() {
// TODO Auto-generated method stub
return name;
}
public Set<String> getRoles() {
// TODO Auto-generated method stub
return null;
}
public String getAccount() {
// TODO Auto-generated method stub
return null;
}
public String getAffiliation() {
// TODO Auto-generated method stub
return null;
}
public Enrollment getEnrollment() {
// TODO Auto-generated method stub
return enrollment;
}
public String getMspId() {
// TODO Auto-generated method stub
return mspId;
}
}
新建一个启动类,进行合约调试
package com.hu.demo;
import org.hyperledger.fabric.sdk.User;
import org.hyperledger.fabric.sdk.exception.CryptoException;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.HFClient;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.Peer;
import org.hyperledger.fabric.sdk.Orderer;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import com.hu.entity.MyUser;
import org.hyperledger.fabric.sdk.ChaincodeID;
import org.hyperledger.fabric.sdk.ChaincodeResponse;
import org.hyperledger.fabric.sdk.QueryByChaincodeRequest;
import org.hyperledger.fabric.sdk.ProposalResponse;
import org.hyperledger.fabric.sdk.TransactionProposalRequest;
import org.hyperledger.fabric.sdk.BlockEvent;
import org.hyperledger.fabric.sdk.BlockEvent.TransactionEvent;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.Properties;
import java.util.concurrent.CompletableFuture;
public class Demo {
public static void main(String[] args) throws Exception {
// 创建User实例
String keyFile = "crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/keystore/e81bbfd91136ca08060ee3bd7ae8a104d916a8557a62722571ff801aacab41db_sk";
String certFile = "crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/signcerts/Admin@org1.example.com-cert.pem";
MyUser user;
try {
user = new MyUser("admin", "Org1MSP", keyFile, certFile);
} catch (Exception e) {
// TODO: handle exception
System.out.println("创建用户失败:" + e.toString());
return;
}
// 创建HFClient实例
HFClient client = HFClient.createNewInstance();
try {
client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
client.setUserContext(user);
} catch (Exception e) {
// TODO: handle exception
System.out.println("创建客户端失败:" + e.toString());
return;
}
// 创建通道实例
Channel channel = client.newChannel("mychannel");
// 设置节点信息
Peer peer = client.newPeer("peer0.org1.example.com", "grpcs://localhost:7051",
loadTLSFile("crypto-config/peerOrganizations/org1.example.com/tlsca/tlsca.org1.example.com-cert.pem",
"peer0.org1.example.com"));
try {
channel.addPeer(peer);
} catch (Exception e) {
// TODO: handle exception
System.out.println("添加节点失败:" + e.toString());
return;
}
// Peer peer1 = client.newPeer("peer1.org1.example.com", "grpcs://localhost:9051",
// loadTLSFile("crypto-config/peerOrganizations/org2.example.com/tlsca/tlsca.org2.example.com-cert.pem",
// "peer0.org2.example.com"));
// channel.addPeer(peer1);
Orderer orderer = client.newOrderer("orderer.example.com", "grpcs://localhost:7050",
loadTLSFile("crypto-config/ordererOrganizations/example.com/orderers/orderer.example.com/tls/ca.crt",
"orderer.example.com"));
try {
channel.addOrderer(orderer);
channel.initialize();
} catch (Exception e) {
// TODO: handle exception
System.out.println("添加节点失败:" + e.toString());
return;
}
// 设置链码名
ChaincodeID cid = ChaincodeID.newBuilder().setName("chaincode2").build();
System.out.println("================================调用链码==============================");
// 调用链码
TransactionProposalRequest invoke = client.newTransactionProposalRequest();
invoke.setChaincodeID(cid);
invoke.setFcn("test");
invoke.setArgs("test invoke");
invoke.setProposalWaitTime(3000);
try {
Collection<ProposalResponse> transactionProposal = channel.sendTransactionProposal(invoke);
for (ProposalResponse pres : transactionProposal) {
if (pres.getStatus() != ChaincodeResponse.Status.SUCCESS) {
System.out.println("链码调用失败:"+pres.getMessage());
return;
}
System.out.println(pres.getProposalResponse().getResponse().getPayload().toStringUtf8());
}
TransactionEvent event = channel.sendTransaction(transactionProposal).get();
System.out.format("交易ID: %s\n", event.getTransactionID());
System.out.format("有效性: %b\n", event.isValid());
} catch (Exception e) {
// TODO: handle exception
System.out.println("链码调用失败:" + e.toString());
return;
}
}
private static Properties loadTLSFile(String rootTLSCert, String hostName) throws IOException {
Properties properties = new Properties();
properties.put("pemBytes", Files.readAllBytes(Paths.get(rootTLSCert)));
properties.setProperty("sslProvider", "openSSL");
properties.setProperty("negotiationType", "TLS");
properties.setProperty("trustServerCertificate", "true");
properties.setProperty("hostnameOverride", hostName);
return properties;
}
}
调试
查看合约容器日志,执行启动类。

