(七)HyperledgerFarbic1.4- Fabric的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/Admin@org1.example.com/msp/keystore/2b33de36c48cc20ff8056c525db5ddfc3b3cbfe337984e867294de19fc3a770b_sk fabric.org1-peer0.users-admin-cert=crypto-config/peerOrganizations/org1.example.com/users/Admin@org1.example.com/msp/admincerts/Admin@org1.example.com-cert.pem
编写配置文件对应的配置类
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/Admin@org1.example.com/msp/admincerts/Admin@org1.example.com-cert.pem * @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上查询到了数据