[toc]
在之前的操作中 无论是创建 channel 还是 链码 安装实例化 合约的查询 调用 在 CA 中注册用户,都是通过命令行的形式去操作的。 sdk 提供了一系列的功能,可以通过 sdk 去实现之前使用命令行操作的效果。 现阶段 fabric 提供的比较稳定的 sdk 有:Node、java、go 如果是创建一个区块链管理平台。就更需要 SDK 来获取到区块链中的信息。
总之,业务系统想要与 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>
创建 channel 需要使用 channel 配置文件中指定在 Channel 中的 org 的 Admin 用户。 创建 channel 需要生成创世区块,这一步需要有 orderer 的配置信息
所以 使用 sdk 创建 channel 要准备一下的配置信息
- channel 中任意一个 org 的 admin 用户信息
- orderer 节点的信息
使用 fabric-samples 提供的 byfn.sh 脚本启动 first-network
./byfn.sh uo -s couchdb
启动完成后会在 first-network 目录下生成两个目录
crypto-config : 存放了orderer的证书和peer的证书
channel-artifacts:存放了channel的配置和peer锚节点的配置以及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 配置文件。
通过 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();
}
}
# 配置用户信息
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);
}
}
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);
}
}
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;
}
}
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);
}
}
@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
/**
* 安装链码
*
* 注意:目录 {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
在 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 了
在 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"}
在 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 上查询到了数据