HyperledgerFarbic1.4- Fabric的SDK使用
- 一、SDK提供的功能
- 二、使用java-fabric-sdk
- 三、使用sdk創建channel 並將peer加入到channel中
- 3.1 啓動first-network
- 3.2 生成channel的配置文件
- 3.3 創建一個springboot項目
- 3.4 修改application.properties文件
- 3.5 編寫Fabric用戶信息實現Fabric User接口
- 3.6 編寫FabricClient用於與網絡的交互
- 3.7 編寫Service來提供與Fabric網絡交互的能力
- 3.8 完整的目錄結構
- 3.9 編寫測試類 測試是否可以成功創建channel並將節點org1Peer0加入到channel中
- 四、使用java sdk 安裝鏈碼到peer節點中
- 五、使用java sdk實例化鏈碼
- 六、更新鏈碼
- 七、 調用鏈碼-invoke
- 八 、使用sdk提供的查詢功能
一、SDK提供的功能
在之前的操作中 無論是創建channel還是 鏈碼 安裝實例化 合約的查詢 調用 在CA中註冊用戶,都是通過命令行的形式去操作的。
sdk提供了一系列的功能,可以通過sdk去實現之前使用命令行操作的效果。
現階段fabric提供的比較穩定的sdk有:Node、java、go
如果是創建一個區塊鏈管理平臺。就更需要SDK來獲取到區塊鏈中的信息。
總之,業務系統想要與fabric工程交互就應該使用sdk。
二、使用java-fabric-sdk
創建一個springboot項目pom中引用對應版本的sdk
<!--fabric java sdk -->
<dependency>
<groupId>org.hyperledger.fabric-sdk-java</groupId>
<artifactId>fabric-sdk-java</artifactId>
<version>1.4.6</version>
</dependency>
三、使用sdk創建channel 並將peer加入到channel中
創建channel 需要使用channel配置文件中指定在Channel中的org的Admin用戶。
創建channel需要生成創世區塊,這一步需要有orderer 的配置信息
所以 使用sdk創建channel要準備一下的配置信息
- channel中任意一個org的 admin用戶信息
- orderer節點的信息
3.1 啓動first-network
使用fabric-samples 提供的byfn.sh腳本啓動first-network
./byfn.sh uo -s couchdb
啓動完成後會在first-network目錄下生成兩個目錄
crypto-config : 存放了orderer的證書和peer的證書
channel-artifacts:存放了channel的配置和peer錨節點的配置以及channel的創世區塊
3.2 生成channel的配置文件
使用first-network中提供的configtx.yaml配置文件,配置文件中有關於channel的一個配置。
TwoOrgsChannel:
Consortium: SampleConsortium
<<: *ChannelDefaults
Application:
<<: *ApplicationDefaults
Organizations:
- *Org1
- *Org2
Capabilities:
<<: *ApplicationCapabilities
按照這個配置文件的描述,將會生成一個SampleConsortium聯盟,聯盟中包含Org1和Org2兩個組織
使用configtxgen工具 生成channel的配置文件
configtxgen -profile TwoOrgsChannel -outputCreateChannelTx channel-artifacts/mychannel1.tx -channelID mychannel1
以上命令中的幾個參數
-profile 使用配置文件中Profiles下的那個配置項,這裏用到的Profiles.TwoOrgsChannel
-outputCreateChannelTx 執行最終生成的channel的tx配置文件的位置
-channelID 使用這個configtx的channel的channelId
-configPath 指定包含configtx.yaml配置文件的目錄(如果命令執行的目錄就包含configtx.yaml則不需要使用該參數)
執行完命令後在指定的目錄下會出現mychannel1.tx配置文件。
3.3 創建一個springboot項目
通過maven引入fabric-sdk-java
<!--fabric java sdk -->
<dependency>
<groupId>org.hyperledger.fabric-sdk-java</groupId>
<artifactId>fabric-sdk-java</artifactId>
<version>1.4.6</version>
</dependency>
將上一步生成的channel1.tx和聯盟中的證書都複製到springboot的resources目錄下
爲了方便從resources目錄下讀取文件編寫一個utils類
import org.springframework.core.io.ClassPathResource;
import java.io.File;
public class ClasspathFileUtils {
/**
* 在springboot的resources目錄下 獲取文件
*
* @param resPath
* @return
* @throws Exception
*/
public static File getFileFromSpringBootClassPath(String resPath) throws Exception {
ClassPathResource classPathResource = new ClassPathResource(resPath);
return classPathResource.getFile();
}
}
3.4 修改application.properties文件
# 配置用戶信息
fabric.user.name=admin
fabric.user.account=LH
fabric.user.affiliation=Org1
fabric.user.msp-id=Org1MSP
# 配置orderer信息
fabric.orderer.name=orderer.example.com
fabric.orderer.grpcs-addr=grpcs://orderer.example.com:7050
fabric.orderer.tlsca-cert=crypto-config/ordererOrganizations/example.com/tlsca/tlsca.example.com-cert.pem
fabric.orderer.ca-cert=crypto-config/ordererOrganizations/example.com/ca/ca.example.com-cert.pem
# 配置channel信息
fabric.channel.channel-name=mychannel1
fabric.channel.channel-config-tx-path=channel-artifacts/mychannel1.tx
# 配置org1-peer0的信息
fabric.org1-peer0.name=peer0.org1.example.com
fabric.org1-peer0.grpcs-addr=grpcs://peer0.org1.example.com:7051
fabric.org1-peer0.tlsca-cert=crypto-config/peerOrganizations/org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem
fabric.org1-peer0.users-admin-private-key=crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/keystore/2b33de36c48cc20ff8056c525db5ddfc3b3cbfe337984e867294de19fc3a770b_sk
fabric.org1-peer0.users-admin-cert=crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/admincerts/[email protected]
編寫配置文件對應的配置類
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Set;
@Getter
@Setter
@ConfigurationProperties(prefix = "fabric.user")
public class FabricUserProperties {
private String name;
private String account;
private String affiliation;
private String mspId;
private Set<String> roles;
}
@Getter
@Setter
@ConfigurationProperties(prefix = "fabric.channel")
public class FabricChannelProperties {
private String channelName;
private String channelConfigTxPath;
}
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Getter
@Setter
@ConfigurationProperties(prefix = "fabric.orderer")
public class FabricOrdererProperties {
private String name;
private String grpcsAddr;
private String tlscaCert;
private String caCert;
}
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Getter
@Setter
@ConfigurationProperties(prefix = "fabric.org1-peer0")
public class FabricOrg1Peer0Properties {
private String name;
private String grpcsAddr;
private String tlscaCert;
private String usersAdminPrivateKey;
private String usersAdminCert;
}
在啓動類上聲明使用的配置類
@EnableConfigurationProperties({
FabricUserProperties.class,
FabricOrdererProperties.class,
FabricChannelProperties.class,
FabricOrg1Peer0Properties.class
})
@SpringBootApplication
public class JavaFabricSdkApplication {
public static void main(String[] args) {
SpringApplication.run(JavaFabricSdkApplication.class, args);
}
}
3.5 編寫Fabric用戶信息實現Fabric User接口
import com.lhit.fabric.javafabricsdk.fabric.properties.FabricOrg1Peer0Properties;
import com.lhit.fabric.javafabricsdk.fabric.properties.FabricUserProperties;
import com.lhit.fabric.javafabricsdk.fabric.util.UserUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.hyperledger.fabric.sdk.Enrollment;
import org.hyperledger.fabric.sdk.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.security.Security;
import java.util.Set;
/**
* 在聯盟網絡中的用戶信息
*/
@Slf4j
@Getter
@Setter
@Component
public class FabricUserContext implements User {
@Autowired
private FabricUserProperties userProperties;
@Autowired
private FabricOrg1Peer0Properties org1Peer0Properties;
@PostConstruct
private void init() {
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>加載FabricUserContext");
// 指定下加密算法 否則節點通訊過程會報錯
Security.addProvider(new BouncyCastleProvider());
}
@Override
public String getName() {
return userProperties.getName();
}
@Override
public Set<String> getRoles() {
return userProperties.getRoles();
}
@Override
public String getAccount() {
return userProperties.getAccount();
}
@Override
public String getAffiliation() {
return userProperties.getAffiliation();
}
@Override
public Enrollment getEnrollment() {
try {
// 這裏用到了org1 peer0節點的 私鑰和證書。用於用戶enroll到節點中
Enrollment enrollment = UserUtils.getEnrollment(org1Peer0Properties.getUsersAdminPrivateKey(), org1Peer0Properties.getUsersAdminCert());
return enrollment;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
@Override
public String getMspId() {
return userProperties.getMspId();
}
}
爲了方便讀取證書信息封裝的工具類
import org.bouncycastle.crypto.CryptoException;
import org.hyperledger.fabric.sdk.Enrollment;
import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
/**
* 用戶工具類用於讀取證書和私鑰信息到java對象中
*/
public class UserUtils {
private static class CAEnrollment implements Enrollment {
private PrivateKey key;
private String ecert;
public CAEnrollment(PrivateKey key, String ecert) {
this.key = key;
this.ecert = ecert;
}
@Override
public PrivateKey getKey() {
return key;
}
@Override
public String getCert() {
return ecert;
}
}
/**
* @param keyPath 私鑰的地址 crypto-config/ordererOrganizations/example.com/ca/ca.example.com-cert.pem
* @param certPath 證書的地址 crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/admincerts/[email protected]
* @return enrollment 帶有用戶信息的對象
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CryptoException
* @throws InvalidKeySpecException
* @description 根據證書目錄和私鑰目錄讀取到enrollment裏面。
*/
public static Enrollment getEnrollment(String keyPath, String certPath) throws Exception {
PrivateKey key = null;
String certificate = null;
InputStream isKey = null;
BufferedReader brKey = null;
try {
isKey = new FileInputStream(ClasspathFileUtils.getFileFromSpringBootClassPath(keyPath));
brKey = new BufferedReader(new InputStreamReader(isKey));
StringBuilder keyBuilder = new StringBuilder();
for (String line = brKey.readLine(); line != null; line = brKey.readLine()) {
if (line.indexOf("PRIVATE") == -1) {
keyBuilder.append(line);
}
}
certificate = new String(Files.readAllBytes(Paths.get(ClasspathFileUtils.getFileFromSpringBootClassPath(certPath).getPath())));
byte[] encoded = DatatypeConverter.parseBase64Binary(keyBuilder.toString());
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
KeyFactory kf = KeyFactory.getInstance("ECDSA");
key = kf.generatePrivate(keySpec);
} finally {
isKey.close();
brKey.close();
}
return new CAEnrollment(key, certificate);
}
}
3.6 編寫FabricClient用於與網絡的交互
import com.lhit.fabric.javafabricsdk.fabric.properties.FabricChannelProperties;
import com.lhit.fabric.javafabricsdk.fabric.properties.FabricOrdererProperties;
import com.lhit.fabric.javafabricsdk.fabric.properties.FabricOrg1Peer0Properties;
import com.lhit.fabric.javafabricsdk.fabric.user.FabricUserContext;
import com.lhit.fabric.javafabricsdk.fabric.util.ClasspathFileUtils;
import lombok.extern.slf4j.Slf4j;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.util.Properties;
/**
* 創建Channel使用到的client
*/
@Slf4j
@Component
public class FabricClient {
@Autowired
private FabricUserContext userContext;
// Hyperledger Fabric Client 用於創建Channel
private HFClient hfClient;
@Autowired
private FabricChannelProperties channelProperties;
@Autowired
private FabricOrdererProperties ordererProperties;
@Autowired
private FabricOrg1Peer0Properties org1Peer0Properties;
private Orderer orderer;
private Peer org1Peer0;
@PostConstruct
private void init() throws Exception {
log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>加載FabricClient");
// 創建客戶端
hfClient = HFClient.createNewInstance();
// 指定加密算法
CryptoSuite cryptoSuite = CryptoSuite.Factory.getCryptoSuite();
hfClient.setCryptoSuite(cryptoSuite);
// 指定用戶身份
hfClient.setUserContext(userContext);
}
/**
* 創建channel
*
*/
public Channel createChannel() throws Exception {
// channel的配置信息
ChannelConfiguration channelConfiguration = new ChannelConfiguration(ClasspathFileUtils.getFileFromSpringBootClassPath(channelProperties.getChannelConfigTxPath()));
// 創建channel 需要的參數
// channelName channel的名稱
// orderer orderer節點對象
// channelConfiguration channel的配置信息
// channelConfigurationSignature 用戶簽名信息
Channel channel = hfClient.newChannel(channelProperties.getChannelName(), getOrderer(), channelConfiguration, hfClient.getChannelConfigurationSignature(channelConfiguration, hfClient.getUserContext()));
channel.initialize();
return channel;
}
/**
* 獲取orderer對象
*
* @return
* @throws Exception
*/
public Orderer getOrderer() throws Exception {
if (orderer == null) {
String path = ClasspathFileUtils.getFileFromSpringBootClassPath(ordererProperties.getTlscaCert()).getPath();
Properties properties = new Properties();
properties.setProperty("pemFile", path);
Orderer orderer = hfClient.newOrderer(ordererProperties.getName(), ordererProperties.getGrpcsAddr(), properties);
return orderer;
}
return orderer;
}
/**
* 獲取peer對象
*
* @return
* @throws Exception
*/
public Peer getOrg1Peer0() throws Exception {
if (org1Peer0 == null) {
String path = ClasspathFileUtils.getFileFromSpringBootClassPath(org1Peer0Properties.getTlscaCert()).getPath();
Properties properties = new Properties();
properties.setProperty("pemFile", path);
Peer peer = hfClient.newPeer(org1Peer0Properties.getName(), org1Peer0Properties.getGrpcsAddr(), properties);
return peer;
}
return org1Peer0;
}
/**
* 根據channel名稱獲取channel
*
* @param channelName
* @return
* @throws Exception
*/
public Channel getChannel(String channelName) throws Exception {
Channel channel = hfClient.getChannel(channelName);
if (channel == null) {
channel = hfClient.newChannel(channelName);
}
channel.addOrderer(getOrderer());
return channel;
}
}
3.7 編寫Service來提供與Fabric網絡交互的能力
import com.lhit.fabric.javafabricsdk.fabric.client.FabricClient;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.Peer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class FabricChannelService {
@Autowired
private FabricClient fabricClient;
/**
* 創建通道
*
* @throws Exception
*/
public Channel createChannel() throws Exception {
return fabricClient.createChannel();
}
/**
* 加入通道
*
* @throws Exception
*/
public void joinChannel(Channel channel,Peer peer) throws Exception {
channel.joinPeer(peer);
channel.initialize();
}
public Channel getChannel(String channelName) throws Exception {
return fabricClient.getChannel(channelName);
}
}
3.8 完整的目錄結構
3.9 編寫測試類 測試是否可以成功創建channel並將節點org1Peer0加入到channel中
@Slf4j
@SpringBootTest
class JavaFabricSdkApplicationTests {
@Autowired
private FabricChannelService channelService;
@Autowired
private FabricClient fabricClient;
@Test
void contextLoads() throws Exception {
log.info("開始創建channel");
Channel channel = channelService.createChannel();
log.info("org1peer0節點 加入channel");
channelService.joinChannel(channel,fabricClient.getOrg1Peer0());
log.info("完成創建channel並將org1peer0加入到channel中");
}
}
成功執行完畢後 ,如果沒有報錯 到docker容器中驗證org1 peer0 節點是否加入到了channel中
# 進入到cli容器中
docker exec -it cli bash
# cli默認的用戶身份就是 org1 peer0 admin用戶所以可以直接查看當前節點加入的channel
peer channel list
# 輸出 說明 成功的通過java sdk向fabric網絡中添加了channel和並將peer加入到了channel中
root@dbe359f6631b:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2020-04-01 07:28:46.769 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined:
mychannel
mychannel1
四、使用java sdk 安裝鏈碼到peer節點中
4.1 在FabricChannelService中添加安裝鏈碼的方法
/**
* 安裝鏈碼
*
* 注意:目錄 {chaincodeLoaction}/src/{chaincodePath}/
*
* 在當前項目中 就是
* chaincodeLoaction = chaincode
* chaincodePath = basic_info
*
* @param type 鏈碼語言GO JAVA NODE
* @param chaincodeName 鏈碼名稱
* @param version 鏈碼版本
* @param chaincodeLoaction 鏈碼路徑
* @param chaincodePath 鏈碼路徑
* @param peers 要安裝到哪些節點上(這些節點必須在同一個org內)
* @throws Exception
*/
public Collection<ProposalResponse> installChaincode(TransactionRequest.Type type, String chaincodeName, String version,String chaincodeLoaction ,String chaincodePath, List<Peer> peers) throws Exception {
// 初始化Install對象
InstallProposalRequest installProposalRequest = fabricClient.getHfClient().newInstallProposalRequest();
// 生成chaincodeId
ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).setVersion(version).build();
// 設置鏈碼語言類型
installProposalRequest.setChaincodeLanguage(type);
// 設置chaincodeId
installProposalRequest.setChaincodeID(chaincodeID);
// 指定鏈碼源文件
installProposalRequest.setChaincodeSourceLocation(ClasspathFileUtils.getFileFromSpringBootClassPath(chaincodeLoaction));
// 設置chanincodePata
installProposalRequest.setChaincodePath(chaincodePath);
return fabricClient.getHfClient().sendInstallProposal(installProposalRequest, peers);
}
編寫測試用例
@Test
void testInstallChaincodeToPeer() throws Exception {
Collection<ProposalResponse> basicinfo = channelService.installChaincode(TransactionRequest.Type.GO_LANG,
"basicinfo",
"1.0",
"chaincode",
"basic_info",
Lists.newArrayList(fabricClient.getOrg1Peer0()));
}
測試完成後在容器中查看Org1Peer0是否已經安裝了鏈碼
# 進入容器
docker exec -it cli bash
# 執行查看鏈碼安裝列表命令
peer chaincode list --installed
# 輸出
Get installed chaincodes on peer:
Name: basicinfo, Version: 1.0, Path: basic_info, Id: 124f873c5b5ffa6b57fa2dbb55c463bff7cae916ef297d2d78c6f345126714a2
Name: mycc, Version: 1.0, Path: github.com/chaincode/chaincode_example02/go/, Id: 333a19b11063d0ade7be691f9f22c04ad369baba15660f7ae9511fd1a6488209
五、使用java sdk實例化鏈碼
在service中添加實例化鏈碼的方法,在實例化鏈碼時需要制定背書策略。背書策略一般使用yaml文件來描述
# 背書策略描述文件
identities: # 指定參與背書的角色身份 因爲代碼中只是將鏈碼安裝實例化到了org1的peer0節點,所以先只寫user1
user1: {"role":{"name":"member","mspId":"Org1MSP"}}
# user2: {"role":{"name":"member","mspId":"Org2MSP"}}
policy: # 具體策略
1-of: # 以下聲明中的一個 就是 user1 背書即可
- signed-by: "user1"
# - signed-by: "user2"
# 2-of: # 以下聲明中的兩個 就是 user1 user2 都需要背書即可
# - signed-by: "user1"
# - signed-by: "user2"
# 1-of: # 以下聲明中的任意一個 就是 user1 user2 其中一個背書即可
# - signed-by: "user1"
# - signed-by: "user2"
把文件複製到resources目錄下
/**
* 實例化鏈碼
*
* @param type 鏈碼語言類型
* @param channelName channel名稱
* @param chaincodeName 鏈碼名稱
* @param version 版本
* @param orderer orderer節點信息
* @param peer 要安裝到那個peer
* @param funcName 初始化方法名 如果沒有可以填寫任意字符串
* @param args 初始化方法傳參 如果沒有 也要傳個 String[] args = {""}
* @throws Exception
*/
public void instantiateChaincode(TransactionRequest.Type type, String channelName, String chaincodeName, String version, Orderer orderer, Peer peer, String funcName, String[] args) throws Exception {
// 獲取到channel
Channel channel = getChannel(channelName);
// 指定channel中的 orderer和peer
channel.addPeer(peer);
channel.addOrderer(orderer);
// 初始化channel
channel.initialize();
//構造提案
InstantiateProposalRequest instantiateProposalRequest = fabricClient.getHfClient().newInstantiationProposalRequest();
// 設置語言
instantiateProposalRequest.setChaincodeLanguage(type);
// 生成chaincodeId
ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).setVersion(version).build();
instantiateProposalRequest.setChaincodeID(chaincodeID);
// 設置初始化方法和參數
instantiateProposalRequest.setFcn(funcName);
instantiateProposalRequest.setArgs(args);
// 設置背書策略
ChaincodeEndorsementPolicy chaincodeEndorsementPolicy = new ChaincodeEndorsementPolicy();
chaincodeEndorsementPolicy.fromYamlFile(ClasspathFileUtils.getFileFromSpringBootClassPath("endorsement_policy/my_endorsement_policy.yaml"));
// 背書策略設置到提案中
instantiateProposalRequest.setChaincodeEndorsementPolicy(chaincodeEndorsementPolicy);
// 合約實例化 用channel 提交提案
Collection<ProposalResponse> proposalResponses = channel.sendInstantiationProposal(instantiateProposalRequest);
for (ProposalResponse proposalRespons : proposalResponses) {
if (proposalRespons.getStatus().getStatus() != 200) {
throw new Exception("提案返回報錯");
}
}
// 提交 數據到鏈上
channel.sendTransaction(proposalResponses);
}
添加測試方法
@Test
void testInstantiateChaincode() throws Exception {
log.info("開始實例化鏈碼");
String[] args = {""};
channelService.instantiateChaincode(TransactionRequest.Type.GO_LANG,
channelProperties.getChannelName(),
"basicinfo",
"1.0",
fabricClient.getOrderer(),
fabricClient.getOrg1Peer0(),
"",
args
);
log.info("完成實例化鏈碼");
}
測試通過後檢查對應節點上已經實例化的鏈碼
# 進入cli容器
docker exec -it cli bash
# 查看當前節點已經實例化的鏈碼
peer chaincode list --instantiated -C mychannel1
# 輸出channel中已經實例化的鏈碼
Get instantiated chaincodes on channel mychannel1:
Name: basicinfo, Version: 1.0, Path: basic_info, Escc: escc, Vscc: vscc
六、更新鏈碼
在service中增加更新鏈碼的方法
/**
* 更新升級鏈碼
*
* @param type 鏈碼語言類型
* @param channelName channel名稱
* @param chaincodeName 鏈碼名稱
* @param version 版本
* @param orderer orderer節點信息
* @param peer 要安裝到那個peer
* @param funcName 初始化方法名 如果沒有可以填寫任意字符串
* @param args 初始化方法傳參 如果沒有 也要傳個 String[] args = {""}
* @throws Exception
*/
public void upgradeChaincode(TransactionRequest.Type type, String channelName, String chaincodeName, String version, Orderer orderer, Peer peer, String funcName, String[] args) throws Exception {
// 獲取到channel
Channel channel = getChannel(channelName);
// 指定channel中的 orderer和peer
channel.addPeer(peer);
channel.addOrderer(orderer);
// 初始化channel
channel.initialize();
//構造提案
UpgradeProposalRequest upgradeProposalRequest = fabricClient.getHfClient().newUpgradeProposalRequest();
// 設置語言
upgradeProposalRequest.setChaincodeLanguage(type);
// 生成chaincodeId
ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).setVersion(version).build();
upgradeProposalRequest.setChaincodeID(chaincodeID);
// 設置初始化方法和參數
upgradeProposalRequest.setFcn(funcName);
upgradeProposalRequest.setArgs(args);
// 修改背書策略
ChaincodeEndorsementPolicy chaincodeEndorsementPolicy = new ChaincodeEndorsementPolicy();
chaincodeEndorsementPolicy.fromYamlFile(ClasspathFileUtils.getFileFromSpringBootClassPath("endorsement_policy/my_endorsement_policy.yaml"));
// 背書策略設置到提案中
upgradeProposalRequest.setChaincodeEndorsementPolicy(chaincodeEndorsementPolicy);
// 合約實例化 用channel 提交提案
Collection<ProposalResponse> proposalResponses = channel.sendUpgradeProposal(upgradeProposalRequest);
for (ProposalResponse proposalRespons : proposalResponses) {
if (proposalRespons.getStatus().getStatus() != 200) {
throw new Exception("提案返回報錯");
}
}
// 提交 數據到鏈上
channel.sendTransaction(proposalResponses);
}
編寫測試方法
要想更新鏈碼到2.0版本 就需要先將2.0版的鏈碼安裝到peer上。
@Test
void testUpgradeChaincode() throws Exception {
log.info("開始更新鏈碼");
// 先安裝2.0合約
Collection<ProposalResponse> basicinfo = channelService.installChaincode(TransactionRequest.Type.GO_LANG,
"basicinfo",
"2.0",
"chaincode",
"basic_info",
Lists.newArrayList(fabricClient.getOrg1Peer0()));
System.out.println("end");
// 更新鏈碼到2.0
String[] args = {""};
channelService.upgradeChaincode(TransactionRequest.Type.GO_LANG,
channelProperties.getChannelName(),
"basicinfo",
"2.0",
fabricClient.getOrderer(),
fabricClient.getOrg1Peer0(),
"",
args
);
log.info("完成更新鏈碼");
}
執行完成後 查看channel中peer已經實例化的鏈碼
# 進入cli容器
docker exec -it cli bash
# 查看已經實例化的鏈碼
peer chaincode list --instantiated -C mychannel1
# 輸出
Get instantiated chaincodes on channel mychannel1:
Name: basicinfo, Version: 2.0, Path: basic_info, Escc: escc, Vscc: vscc
可以看到basicinfo鏈碼的version已經是2.0了
七、 調用鏈碼-invoke
在service中增加調用鏈碼的方法
/**
* 調用鏈碼invoke鏈碼
*
* @param type 鏈碼語言類型
* @param channelName channel名稱
* @param chaincodeName 鏈碼名稱
* @param version 版本
* @param orderer orderer節點信息
* @param peers 需要哪些peer背書
* @param funcName 調用方法名
* @param args 方法傳參
* @throws Exception
*/
public CompletableFuture<BlockEvent.TransactionEvent> invokeChaincode(TransactionRequest.Type type, String channelName, String chaincodeName, String version, Orderer orderer,List<Peer> peers, String funcName, String[] args) throws Exception {
// 獲取到channel
Channel channel = getChannel(channelName);
for (Peer peer : peers) {
channel.addPeer(peer);
}
channel.addOrderer(orderer);
// 初始化channel
channel.initialize();
// 構建交易提案
TransactionProposalRequest proposalRequest = fabricClient.getHfClient().newTransactionProposalRequest();
// 指定語言
proposalRequest.setChaincodeLanguage(TransactionRequest.Type.GO_LANG);
// 生成chaincodeId
ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).setVersion(version).build();
proposalRequest.setChaincodeID(chaincodeID);
// 設置初始化方法和參數
proposalRequest.setFcn(funcName);
proposalRequest.setArgs(args);
// 發送提案 peers要與背書規則相匹配,需要哪個peer背書 就將那個peer添加進去
Collection<ProposalResponse> proposalResponses = channel.sendTransactionProposal(proposalRequest);
// 合約實例化 用channel 提交提案
for (ProposalResponse proposalRespons : proposalResponses) {
if (proposalRespons.getStatus().getStatus() != 200) {
throw new Exception("提案返回報錯:" + proposalRespons.getMessage());
} else {
log.info("調用:{} 鏈碼方法成功",funcName);
}
}
// 發送數據到鏈上
return channel.sendTransaction(proposalResponses);
}
編寫測試方法
@Test
void testInvokeChaincode() throws Exception {
log.info("開始調用鏈碼方法");
// {"identity":"110115","mobile":"18910012222","name":"zhangsan"}
String[] args = {"110115", "{\"name\":\"zhangsan-5.0\",\"identity\":\"110115\",\"mobile\":\"18910012222\"}"};
CompletableFuture<BlockEvent.TransactionEvent> completableFuture = channelService.invokeChaincode(TransactionRequest.Type.GO_LANG,
channelProperties.getChannelName(),
"basicinfo",
"2.0",
fabricClient.getOrderer(),
Lists.newArrayList(fabricClient.getOrg1Peer0()),
"save",
args
);
log.info("完成鏈碼");
}
完成測試後 到cli容器中驗證下
# 進入cli容器
docker exec -it cli bash
# 查詢保存的數據
peer chaincode query -C mychannel4 -n basicinfo -c '{"Args":["query","110115"]}'
# 輸出
{"identity":"110115","mobile":"18910012222","name":"zhangsan-5.0"}
八 、使用sdk提供的查詢功能
在service中添加查詢方法
/**
* 調用鏈碼query鏈碼
*
* @param channelName channel名稱
* @param chaincodeName 鏈碼名稱
* @param funcName 調用方法名
* @param args 方法傳參
* @throws Exception
*/
public ProposalResponse queryChaincode(TransactionRequest.Type type,String channelName, String chaincodeName, List<Peer> peers, String funcName, String[] args) throws Exception {
// 獲取到channel
Channel channel = getChannel(channelName);
// 多個peer 可以防止單點故障
for (Peer peer : peers) {
channel.addPeer(peer);
}
// 初始化channel
channel.initialize();
// 構建交易提案
QueryByChaincodeRequest queryByChaincodeRequest = fabricClient.getHfClient().newQueryProposalRequest();
// 指定語言
queryByChaincodeRequest.setChaincodeLanguage(type);
// 生成chaincodeId
ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).build();
queryByChaincodeRequest.setChaincodeID(chaincodeID);
// 設置初始化方法和參數
queryByChaincodeRequest.setFcn(funcName);
queryByChaincodeRequest.setArgs(args);
// 調用查詢方法
Collection<ProposalResponse> proposalResponses = channel.queryByChaincode(queryByChaincodeRequest);
// 合約實例化 用channel 提交提案
for (ProposalResponse proposalRespons : proposalResponses) {
if (proposalRespons.getStatus().getStatus() != 200) {
log.info("提案返回報錯:" + proposalRespons.getMessage());
} else {
log.info("調用:{} 鏈碼方法成功返回數據:{}", funcName,proposalRespons.getProposalResponse().getPayload());
return proposalRespons;
}
}
// 如果沒有成功提案
return null;
}
編寫測試方法
@Test
void testQueryChaincode() throws Exception {
log.info("開始調用查詢鏈碼方法");
// {"identity":"110115","mobile":"18910012222","name":"zhangsan"}
String[] args = {"110115"};
ProposalResponse proposalResponse = channelService.queryChaincode(TransactionRequest.Type.GO_LANG,
channelProperties.getChannelName(),
"basicinfo",
Lists.newArrayList(fabricClient.getOrg1Peer0()),
"query",
args
);
byte[] chaincodeActionResponsePayload = proposalResponse.getChaincodeActionResponsePayload();
log.info("完成查詢鏈碼:"+ new String(chaincodeActionResponsePayload,"UTF-8") );
}
測試執行輸出
完成查詢鏈碼:{"identity":"110115","mobile":"18910012222","name":"zhangsan-5.0"}
說明已經成功從peer上查詢到了數據