本文章使用的環(huán)境為1.4.0版本(都是一點(diǎn)一點(diǎn)磨出來的,有用請點(diǎn)個贊吧,嗚嗚嗚)
主要實(shí)現(xiàn)的功能:
- 客戶端初始化
- 通道初始化
- 鏈碼安裝
- 鏈碼初始化
- 調(diào)用鏈碼
- 通道創(chuàng)建
- 加入通道
Demo路徑
https://github.com/ccDown/fbsdkdemo
或者
https://github.com/ccDown/MyJavaFabricDemo
1. 客戶端初始化
HFClient client = HFClient.createNewInstance();
client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
client.setUserContext(new SimpleUser());
2. 通道初始化
orderer1Prop中的pemFile對應(yīng)的是orderer中的tls證書
peer1Prop中的pemFile對應(yīng)的是peer節(jié)點(diǎn)中的tls證書
peer1Prop中的clientKeyFile對應(yīng)的是peer節(jié)點(diǎn)中的tls文件下的keystore
peer1Prop中的clientCertFile對應(yīng)的是peer節(jié)點(diǎn)中的tls文件下的signcerts
// initialize Channel
private Channel initializeChannel(HFClient client, String channelName) throws InvalidArgumentException, TransactionException {
Properties orderer1Prop = new Properties();
orderer1Prop.setProperty("pemFile", Config.ORDERER1_TLS_CACERT_FILE);
orderer1Prop.setProperty("sslProvider", "openSSL");
orderer1Prop.setProperty("negotiationType", "TLS");
orderer1Prop.setProperty("hostnameOverride", "chainorder1.com");
orderer1Prop.setProperty("trustServerCertificate", "true");
orderer1Prop.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 9000000);
Orderer orderer = client.newOrderer("chainorder1.com", Config.ORDERER1_ADDRESS, orderer1Prop);
Properties peer1Prop = new Properties();
peer1Prop.setProperty("pemFile", Config.PEER1_TLS_CACERT_FILE);
peer1Prop.setProperty("sslProvider", "openSSL");
peer1Prop.setProperty("negotiationType", "TLS");
peer1Prop.setProperty("hostnameOverride", "peer1.operation.com");
peer1Prop.setProperty("trustServerCertificate", "true");
peer1Prop.setProperty("clientKeyFile", Config.PEER1_TLS_CLIENTKEY);
peer1Prop.setProperty("clientCertFile", Config.PEER1_TLS_CLIENTCERT);
peer1Prop.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 9000000);
Peer peer = client.newPeer("peer1.operation.com", Config.PEER1_ADDRESS, peer1Prop);
EventHub eventHub = client.newEventHub("peer1.operation.com",Config.EVENTHUB1_ADDRESS,peer1Prop);
Channel channel = client.newChannel(channelName);
channel.addOrderer(orderer);
channel.addPeer(peer);
channel.addEventHub(eventHub);
channel.initialize();
System.out.println("channel初始化");
return channel;
}
3. 鏈碼安裝
(鏈碼默認(rèn)會安裝到peer節(jié)點(diǎn)的/var/hyperledger/production/chaincodes目錄下,測試鏈碼安裝的時候可以去這個目錄下查找,一定要注意是peer節(jié)點(diǎn)的目錄下,我好多次都跑到cli里邊找半天找不到。。。)
鏈碼安裝分為go和java兩種方式,每種語言的鏈碼安裝也有兩種方式
3.1
installProposalRequest.setChaincodeSourceLocation(sourceLocation);
go鏈碼:
需要額外指定 installProposalRequest.setChaincodePath(chaincodePath);
使用這種方式要確保 sourceLocation子目錄下為chaincodePath,chaincodePath子目錄包含鏈碼,即整體的目錄為 sourceLocation/chaincodePath/鏈碼.go
java鏈碼:
sourceLocation要指定到項(xiàng)目的src目錄下,否則初始化會失敗。也就是說sourceLocation的路徑為:項(xiàng)目路徑/src
3.2
installProposalRequest.setChaincodeInputStream(inputStream);這個方法需要傳的參數(shù)是經(jīng)過tar壓縮之后的流(使用官方的Util包下的generateTarGzInputStream工具進(jìn)行壓縮)
java鏈碼、nodejs鏈碼:Util.generateTarGzInputStream(Paths.get(sourcePath, projectName).toFile(), "src")
sourcePath/projectName為項(xiàng)目全路徑,projectName為項(xiàng)目名稱,即壓縮的時候指定的兩個路徑為項(xiàng)目全路徑和代碼的"src"路徑
go鏈碼:Util.generateTarGzInputStream(Paths.get(sourcePath,"src", ccPath).toFile(), Paths.get("src",ccPath).toString())
sourcePath/src為gopath,ccPath為chaincode相對于gopath的路徑
另外go鏈碼需要額外指定 installProposalRequest.setChaincodePath(chaincodePath);
public static Collection<ProposalResponse> InstallChainCode(HFClient hfClient, Channel channel, String chainCodePath, String projectName, String chainCodeName, TransactionRequest.Type language) throws InvalidArgumentException, ProposalException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, CryptoException, ClassNotFoundException, IOException {
checkClientInit();
checkChannelInit();
InstallProposalRequest installProposalRequest = hfClient.newInstallProposalRequest();
ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chainCodeName).setVersion("1.0").build();
installProposalRequest.setChaincodeID(chaincodeID);
installProposalRequest.setChaincodeVersion("1.0");
installProposalRequest.setChaincodeLanguage(language);
if (language.equals(TransactionRequest.Type.GO_LANG)) {
// installProposalRequest.setChaincodeSourceLocation(new File(chainCodePath));
installProposalRequest.setChaincodeInputStream(Util.generateTarGzInputStream(Paths.get(chainCodePath, projectName).toFile(), ""));
installProposalRequest.setChaincodePath(chainCodeName);
} else {
installProposalRequest.setChaincodeSourceLocation(new File(chainCodePath+"\\"+ projectName));
// System.out.println("Paths>>>>>>>>>>>>>>>"+Paths.get(chainCodePath,projectName).toFile());
// installProposalRequest.setChaincodeInputStream(Util.generateTarGzInputStream(Paths.get(chainCodePath,projectName).toFile(),"src"));
}
Collection<ProposalResponse> proposalResponses = hfClient.sendInstallProposal(installProposalRequest, channel.getPeers());
return proposalResponses;
}
4. 鏈碼初始化
私有數(shù)據(jù)的鏈碼需要額外調(diào)用instantiateProposalRequest.setChaincodeCollectionConfiguration方法
public static Collection<ProposalResponse> InstantiateChainCode(HFClient hfClient, Channel channel, TransactionRequest.Type language, String chainCodeName, String[] args, String endorsementPolicy) throws ProposalException, InvalidArgumentException, InterruptedException, ExecutionException, TimeoutException, IOException, ChaincodeEndorsementPolicyParseException {
checkClientInit();
checkChannelInit();
InstantiateProposalRequest instantiateProposalRequest = hfClient.newInstantiationProposalRequest();
instantiateProposalRequest.setProposalWaitTime(120000);
ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chainCodeName).setVersion("1.0").setPath(chainCodeName).build();
instantiateProposalRequest.setChaincodeID(chaincodeID);
instantiateProposalRequest.setChaincodeLanguage(language);
instantiateProposalRequest.setFcn("init");
instantiateProposalRequest.setArgs(args);
//region 如果需要用到私有數(shù)據(jù),需要添加私有數(shù)據(jù)的配置
//instantiateProposalRequest.setChaincodeCollectionConfiguration(ChaincodeCollectionConfiguration.fromYamlFile(new File("src/test/fixture/collectionProperties/PrivateDataIT.yaml")));
//endregion
Map<String, byte[]> tm = new HashMap<>();
tm.put("HyperLedgerFabric", "InstantiateProposalRequest:JavaSDK".getBytes(UTF_8));
tm.put("method", "InstantiateProposalRequest".getBytes(UTF_8));
instantiateProposalRequest.setTransientMap(tm);
if (!endorsementPolicy.equals("")){
ChaincodeEndorsementPolicy chaincodeEndorsementPolicy = new ChaincodeEndorsementPolicy();
chaincodeEndorsementPolicy.fromYamlFile(new File(endorsementPolicy));
instantiateProposalRequest.setChaincodeEndorsementPolicy(chaincodeEndorsementPolicy);
}
Collection<ProposalResponse> responses = channel.sendInstantiationProposal(instantiateProposalRequest,channel.getPeers());
List<ProposalResponse> list = responses.stream().filter(ProposalResponse::isVerified).collect(Collectors.toList());
if (list.size() == 0) {
return responses;
}
BlockEvent.TransactionEvent event = channel.sendTransaction(responses).get(60, TimeUnit.SECONDS);
if (event.isValid()){
logger.info("InstantiateChainCode success");
}
return responses;
}
5. 鏈碼調(diào)用
調(diào)用完channel.sendTransactionProposal()方法之后實(shí)際上只是完成了模擬交易的環(huán)節(jié),接下來需要調(diào)用channel.sendTransaction()方法將成功的提案發(fā)送給Orderer節(jié)點(diǎn)進(jìn)行真正的交易,并對該方法的返回回調(diào)進(jìn)行事件監(jiān)聽獲取數(shù)據(jù)落鏈情況,此時交易才完成。(私有數(shù)據(jù)鏈碼需要額外調(diào)用transactionProposalRequest.setTransientMap方法)
public Collection<ProposalResponse> invoke(HFClient client, Channel channel, String chaincodeName, String func, String args) throws InvalidArgumentException, ProposalException {
TransactionProposalRequest request = client.newTransactionProposalRequest();
String cc = chaincodeName;
ChaincodeID ccid = ChaincodeID.newBuilder().setName(cc).build();
request.setChaincodeID(ccid);
request.setFcn(func);
request.setArgs(args);
request.setProposalWaitTime(3000);
Collection<ProposalResponse> responses = channel.sendTransactionProposal(request);
//region 如果使用私有數(shù)據(jù),需要通過map傳遞臨時數(shù)據(jù)
// Map<String, byte[]> tm = new HashMap<>();
// tm.put("A", "a".getBytes(UTF_8));
// tm.put("AVal", "500".getBytes(UTF_8));
// tm.put("B", "b".getBytes(UTF_8));
// String arg3 = "" + (200 + 1);
// tm.put("BVal", arg3.getBytes(UTF_8));
// transactionProposalRequest.setTransientMap(tm);
//endregion
Collection<ProposalResponse> respsponse = channel.sendTransactionProposal(transactionProposalRequest);
List<ProposalResponse> list = respsponse.stream().filter(ProposalResponse::isVerified).collect(Collectors.toList());
if (list.size() == 0) {
return respsponse;
}
BlockEvent.TransactionEvent event = channel.sendTransaction(respsponse).get(60, TimeUnit.SECONDS);
if (event.isValid()){
logger.info("Fabric 鏈碼交互事件成功");
}
logger.info("Fabric 鏈碼交互完成");
return respsponse;
}
6. 通道創(chuàng)建
在官方SDK里邊只有創(chuàng)建完通道之后馬上加入通道,而之后無法讓其他節(jié)點(diǎn)通過SDK加入(可能是自己菜,沒看到)。通過這種方式可以實(shí)現(xiàn)以后任意節(jié)點(diǎn)想加入通道就加入,姑且叫它c(diǎn)hannel. block(跟命令行生成的. block格式是不一樣的,不能在命令行中使用這個文件讓節(jié)點(diǎn)加入通道,如果需要通過cli的方式添加需要通過fetch命令獲取通道配置創(chuàng)世區(qū)塊:peer channel fetch oldest [outputfile] [flags])
public static Channel createChannel(HFClient hfClient,String channelName, byte[] txByte,Orderer orderer) throws InvalidArgumentException, TransactionException, IOException {
logger.info("Fabric 創(chuàng)建通道開始");
ChannelConfiguration configuration = new ChannelConfiguration(txByte);
byte[] signData = hfClient.getChannelConfigurationSignature(configuration,hfClient.getUserContext());
Channel channel = hfClient.newChannel(channelName,orderer,configuration,signData);
//保存channel信息供以后加入通道使用。
channel.serializeChannel(new File("/ channel.block"));
channel.initialize();
logger.info("Fabric 創(chuàng)建通道完成");
return channel;
}
7. 加入通道
通過之前序列化保存的channel對象文件channel. block創(chuàng)建channel從而使其他節(jié)點(diǎn)加入通道
public static Channel joinChannel(HFClient hfClient, byte[] channelByte, Peer peer, Channel.PeerOptions peerOptions) throws InvalidArgumentException, IOException, ClassNotFoundException, TransactionException, ProposalException {
logger.info("Fabric 加入通道開始");
//反序列化之前保存的通道信息文件
Channel channel = hfClient.deSerializeChannel(channelByte);
channel.initialize();
channel.joinPeer(peer,peerOptions);
logger.info("Fabric 加入通道完成");
return channel;
}
坑點(diǎn):
1. TLS驗(yàn)證不通過
大概率是你的證書指定不對,這時候可以使用wireshark抓包看看SSL握手過程。在tls握手的時候使用的證書包括
· order的tls下的ca證書
· peer的tls的ca證書、server.key、server.crt證書
· admin的msp中的keystore和signcerts證書
如果實(shí)在繞不過去tls證書校驗(yàn),可以先把SDK源碼中的EndPoint類的262-266行改成下面的,等別的調(diào)通了再搞這個,萬一哪天運(yùn)氣來了不是。
// try (InputStream myInputStream = new ByteArrayInputStream(pemBytes)) {
// sslContext = clientContextBuilder
// .trustManager(myInputStream)
// .build();
// }
// //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
sslContext = clientContextBuilder
.trustManager(InsecureTrustManagerFactory.INSTANCE)
.build();
// //>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
2. 初始化go鏈碼的時候出現(xiàn)的問題
· 2.1可能會報找不到類之類的錯誤,官網(wǎng)的解決辦法是使用vendor來加載依賴包。

3. 鏈碼實(shí)例化可能會炒雞慢哦
4. 需要發(fā)送給orderer的都需要調(diào)用channel.sendTranscation方法將模擬交易的結(jié)果提案到排序節(jié)點(diǎn)
5. grpc通訊4M限制
因?yàn)镾DK使用的grpc進(jìn)行通訊,所以會有通訊報文大小的限制,默認(rèn)是4M,那么怎么突破這種限制呢,官方是這么縮滴

然后我親自去翻開End2endIT類

所以縮,只要在通道初始化的時候給peer和order添加這一行配置,我們就解決了這個4M限制。偶吼吼。
6. Android設(shè)備如何使用fabric-sdk-java
本人閑來無事在GitHub上閑逛,就跟女孩子內(nèi)種只逛不買的一樣。一不小心就看到了一個項(xiàng)目 android-fabric-sdk
人家是把grpc-netty換成了grpc-okhttp來支持Android設(shè)備,看起來很吊的樣子
7.nodejs鏈碼實(shí)例化失敗
Error: could not assemble transaction, err proposal response was not successful, error code 500, msg error starting container: error starting container: Failed to generate platform-specific docker build: Error returned from build: 1 "npm ERR! code EAI_AGAIN
npm ERR! errno EAI_AGAIN
npm ERR! request to https://registry.npmjs.org/fabric-shim failed, reason: getaddrinfo EAI_AGAIN registry.npmjs.org:443
npm ERR! A complete log of this run can be found in:
npm ERR! /root/.npm/_logs/2019-09-16T06_57_58_381Z-debug.log
"
出現(xiàn)這個問題是因?yàn)閐ocker內(nèi)部DNS無法解析。解決方案:
$ sudo apt-get install bridge-utils -y
$ sudo service docker stop
$ sudo ip link set dev docker0 down
$ sudo brctl delbr docker0
$ sudo service docker start
$ docker network inspect bridge
之后我的node鏈碼可以進(jìn)行實(shí)例化了,不過依然很慢。后來詢問過某些大佬,可以修改源加快一點(diǎn)構(gòu)建容器的速度。嘗試從fabric源碼里邊查找關(guān)于創(chuàng)建Dockerfile的方法自己重新構(gòu)建一個fabric-ccenv,發(fā)現(xiàn)挺復(fù)雜。。。后來 在這里卡了好幾天。
最后嘗試重新構(gòu)建一個淘寶npm源的hyperledger/fabric-ccenv,速度快了很多。
FROM hyperledger/fabric-ccenv:1.4.1
ENV NPM_CONFIG_BUILD_FROM_SOURCE=true
ENV NPM_CONFIG_REGISTRY=https://registry.npm.taobao.org
然后執(zhí)行docker build -t hyperledger/fabric-ccenv:latest .
也可以執(zhí)行以下命令拉取我已經(jīng)build好的鏡像
docker push soullistener/fabric-ccenv:tagname
然后修改tag為hyperledger/fabric-ccenv:latest
8.指紋不匹配
response: status:500 message:"failed to execute transaction 51e5aae96105281fb96e26700ed9ed4fcb25c42fd1c702bbc5b3f2ffd0a58338: [channel mychannel] failed to get chaincode container info for chaincode:1.0.0: could not get chaincode code: chaincode fingerprint mismatch: data mismatch"
指紋不同是因?yàn)?鏈碼名稱、版本、讀寫權(quán)限,owner/group,時間戳、不同的fabric版本,不同的go語言版本(tar庫版本)、不同的安裝方式(SDK,peer命令行安裝)等信息,任何不一致都會導(dǎo)致這個問題。
使用程序打包安裝的時候要把generateTarGzInputStream生成的byte數(shù)組保存下來供以后其他的節(jié)點(diǎn)安裝使用,否則會因?yàn)榇虬鼤r間不同等問題安裝之后的鏈碼hash不一樣導(dǎo)致鏈碼實(shí)例化失敗。
詳情:http://www.itdecent.cn/p/dca0546d85f6
9.java鏈碼實(shí)例化慢或超時
當(dāng)出現(xiàn)這種情況很大的可能是因?yàn)樵诎裫ava鏈碼打包為jar的時候會下載依賴的三方庫,而下載三方庫是個很漫長的過程,大概率會因?yàn)橄螺d到一半就超時了。因此需要修改為國內(nèi)的maven庫加速下載。
首先需要修改settings文件,添加自己的倉庫或者阿里云等庫
<mirror>
<id>maven-public</id>
<mirrorOf>*</mirrorOf>
<url>http://192.168.7.2:8081/repository/maven-public/</url>
</mirror>
其次重新構(gòu)建javaenv(替換settings,從其他倉庫下載三方庫)
FROM hyperledger/fabric-javaenv:amd64-1.4.4
ADD ./settings.xml /root/.sdkman/candidates/maven/current/conf
WORKDIR /root/chaincode-java
然后build后替換掉之前的javaenv,之后下載三方庫會快很多。
10 fabric-SDK-go實(shí)例化鏈碼必須指定背書策略問題
有的小伙伴在實(shí)例化鏈碼的時候不想指定背書,想使用默認(rèn)的背書策略(任意節(jié)點(diǎn)背書即可),卻發(fā)現(xiàn)通過go的SDK指定空的背書策略會報錯,這可咋整
我們要修改SDK的代碼lscc的createChaincodeDeployProposal
- 去除policy為空校驗(yàn)
- 添加一個policy占位

可供參考的地方:
- 官方SDK地址:https://github.com/hyperledger/fabric-sdk-java/tree/v1.4.0(一定要多看test類,你要用的東西都能在這里找到)
- API :http://central.maven.org/maven2/org/hyperledger/fabric-sdk-java/fabric-sdk-java/
(在整個SDK中最常用的類就是 HFClient、Channel、EventHub,還有請求對應(yīng)的request類:InstallProposalRequest, InstantiateProposalRequest, QueryByChaincodeRequest, QuerySCCRequest, TransactionProposalRequest, UpgradeProposalRequest) - IBM教程:https://developer.ibm.com/tutorials/hyperledger-fabric-java-sdk-for-tls-enabled-fabric-network/
- hyperledger中文文檔 https://hyperledgercn.github.io/hyperledgerDocs/sdk_java_zh/
- IBM的官方例子 https://github.com/IBM/blockchain-application-using-fabric-java-sdk
- GitHub上好多可以借鑒的項(xiàng)目,搜索關(guān)鍵字 fabric-java就出來好多
- 可以加入fabric國際大家庭聊天群來問問題 https://chat.hyperledger.org/channel/fabric-sdk-java