到目前为止都是通过在cli容器执行peer chaincode
命令调用chaincode,实际上官方提供SDK(当前只有Node.js和Java两种)。这里提供基于Fabric Java SDK的commercial paper客户端,设置使用TLS,详细项目代码在github。项目采用Springboot,swagger提供网页调用。注意,这里只是demo,不能作为生产环境使用。具体步骤大致拆解如下:
- 构建HFClient
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40public HFClient getObject() throws Exception {
// 使用Org1的admin用户身份。由于使用TSL,这里提供keyFile:admin的私钥和certFile:由ca.org1.example.com签发的证书
// 也可以通过使用HFCAClient向ca发送enroll和register请求用于新增注册和登记身份证书。具体可以参看参考文档。
Enrollment enrollment = PaperUser.createEnrollmentFromPemFile(keyFile, certFile);
PaperUser admin = new PaperUser(name, mspId, enrollment);
HFClient client = HFClient.createNewInstance();
client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
client.setUserContext(admin);
initChannel();
return client;
}
protected static Enrollment createEnrollmentFromPemFile(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对象
}
// 使用TSL,需要提供peerTLScaCertFile:tlsca.org1.example.com的证书和ordererTLScaCertFile:tlsca.example.com的证书
private void initChannel() throws Exception {
Channel channel = client.newChannel(channelID);
Properties peerProps = new Properties();
peerProps.put("pemFile", peerTLScaCertFile);
peerProps.setProperty("sslProvider", "openSSL");
peerProps.setProperty("negotiationType", "TLS");
Peer peer = client.newPeer(peerName, peerURL, peerProps);
channel.addPeer(peer);
Properties ordererProps = new Properties();
ordererProps.put("pemFile", ordererTLScaCertFile);
ordererProps.setProperty("sslProvider", "openSSL");
ordererProps.setProperty("negotiationType", "TLS");
Orderer orderer = client.newOrderer(ordererName, ordererURL, ordererProps);
channel.addOrderer(orderer);
channel.initialize();
}
peerURL
设置为grpcs://peer0.org1.example.com:7051
。首先,TLS需要设置使用grpcs
。其次,只能使用peer0.org1.example.com
(同时需要配置hosts文件),不能使用127.0.0.1
或者localhost
(报错Caused by: java.security.cert.CertificateException: No subject alternative DNS name matching IP address 127.0.0.1/localhost found
) 。因为peer0返回的证书的CN是peer0.org1.example.com,java ssl会校验这个域名。
chaincode - query
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public Result query(String issuer, String paperNumber) {
QueryByChaincodeRequest req = client.newQueryProposalRequest();
ChaincodeID cid = ChaincodeID.newBuilder().setName(chaincodeName).build();
req.setChaincodeID(cid);
req.setFcn(QUERY_FUNC);
req.setArgs(issuer, paperNumber);
// Channel对象已在上面构建
Channel channel = client.getChannel(channelID);
Collection<ProposalResponse> propResps = channel.queryByChaincode(req);
// endorser返回的ProposalResponse需要保持一致
Collection<Set<ProposalResponse>> proposalConsistencySets = SDKUtils.getProposalConsistencySets(propResps);
if (proposalConsistencySets.size() != 1) {
return Result.fail(ErrorCode.CHAINCOED_SERVICE_ERROR)
.withErrorMsg("Expected only one set of consistent proposal responses but got more");
}
FabricProposalResponse.Response res = propResps.iterator().next().getProposalResponse().getResponse();
if (res.getStatus() == 200) {
return Result.success().withResponse(res.getPayload().toStringUtf8());
}
return Result.fail(ErrorCode.CHAINCOED_SERVICE_ERROR).withErrorMsg(res.getMessage());
}chaincode - invoke
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public Result issue(IssueRequest request) {
TransactionProposalRequest proposalRequest = client.newTransactionProposalRequest();
ChaincodeID cid = ChaincodeID.newBuilder().setName(chaincodeName).build();
proposalRequest.setChaincodeID(cid);
proposalRequest.setFcn(ISSUE_FUNC);
proposalRequest.setArgs(request.getIssuer(), request.getPaperNumber(), issueDate, maturityDate,
String.valueOf(request.getFaceValue()));
Channel channel = client.getChannel(channelID);
// Endorsing(simulate) phase
Collection<ProposalResponse> propResps = channel.sendTransactionProposal(proposalRequest);
// endorser返回的ProposalResponse需要保持一致
Collection<Set<ProposalResponse>> proposalConsistencySets = SDKUtils.getProposalConsistencySets(propResps);
if (proposalConsistencySets.size() != 1) {
return Result.fail(ErrorCode.CHAINCOED_SERVICE_ERROR)
.withErrorMsg("Expected only one set of consistent proposal responses but got more");
}
// Orderer phase
// 这里同步调用,需要等到提交orderer并且orderer排序后发送到各个peer,peer处理了block里的交易后才返回。
// 或者同步返回txid。可以配置eventHub异步接收和执行block,通过txid来返回最终的执行结果
BlockEvent.TransactionEvent event = channel.sendTransaction(propResps).get();
if (event.isValid()) {
return Result.success().withResponse("txid : " + event.getTransactionID());
}
return Result.fail(ErrorCode.CHAINCOED_SERVICE_ERROR);
}
WIP:
- eventHub