前言
上一节只是搭建了本地IDEA的SDK环境,结合服务器上的环境跑通了,但是具体的细节还是很多不理解,因此尝试动手封装api,开始一步一步研究。
这一节主要是实现SDK的重要API,包括cli端创建channle,cli端加入channle,查询channle,创建order,创建peer,peer加入到channel,安装合约,实例化合约,调用合约,升级合约,查询合约等
项目搭建
创建springboot工程,引入相关依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--sdk-->
<dependency>
<groupId>org.hyperledger.fabric-sdk-java</groupId>
<artifactId>fabric-sdk-java</artifactId>
<version>1.4.6</version>
</dependency>
<!--日志-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.6.1</version>
</dependency>
<!--commons-->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8</version>
</dependency>
</dependencies>
配置文件log4j.properties
log4j.rootLogger=info, ServerDailyRollingFile, stdout
log4j.appender.ServerDailyRollingFile=org.apache.log4j.DailyRollingFileAppender
log4j.appender.ServerDailyRollingFile.DatePattern='.'yyyy-MM-dd
log4j.appender.ServerDailyRollingFile.File=logs/notify-subscription.log
log4j.appender.ServerDailyRollingFile.layout=org.apache.log4j.PatternLayout
log4j.appender.ServerDailyRollingFile.layout.ConversionPattern=%d - %m%n
log4j.appender.ServerDailyRollingFile.Append=true
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %p [%c] %m%n
配置文件application.properties
#peer1私钥的路径:peerOrganization-org1-users-Admin-msp-keystore
keyFolderPath=D:\Golang\fabric\code\sdkdemo\src\main\resources\crypto-config\peerOrganizations\
\org1.example.com\users\Admin@org1.example.com\msp\keystore
#peer1私钥的值:就是上面路径的值 xxx_sk
keyFileName=9238e39aa136e1c6f24750b9643b07a736f97712f97ba4e59f42d371aa11ce47_sk
#peer2私钥的路径:peerOrganization-org2-users-Admin-msp-keystore
keyFolderPath2=D:\Golang\fabric\code\sdkdemo\src\main\resources\crypto-config\peerOrganizations\
\org2.example.com\users\Admin@org2.example.com\msp\keystore
#peer2私钥的值:就是上面路径的值 xxx_sk
keyFileName2=a3373f32c8b6e884726f3faac3705026ceec67a83f2cd053edfc67c8671c9794_sk
#peer1证书的路径:peerOrganization-org1-users-Admin-msp-admincerts
#1.4.0以后的版本该目录下没文件,可以拷贝同目录下的signcerts文件夹下的文件
certFolderPath=D:\Golang\fabric\code\sdkdemo\src\main\resources\crypto-config\peerOrganizations\
\org1.example.com\users\Admin@org1.example.com\msp\admincerts
#peer1证书的值:就是上面路径的值 xxx_pem
certFileName=Admin@org1.example.com-cert.pem
#peer2证书的路径:peerOrganization-org2-users-Admin-msp-admincerts
#1.4.0以后的版本该目录下没文件,可以拷贝同目录下的signcerts文件夹下的文件
certFolderPath2=D:\Golang\fabric\code\sdkdemo\src\main\resources\crypto-config\peerOrganizations\
\org2.example.com\users\Admin@org2.example.com\msp\admincerts
#peer2证书的值:就是上面路径的值 xxx_pem
certFileName2=Admin@org2.example.com-cert.pem
#order的tls的证书路径:orderOrganization-tlsca-xxx.pem
tlsOrderFilePath=D:\Golang\fabric\code\sdkdemo\src\main\resources\crypto-config\ordererOrganizations\
\example.com\tlsca\tlsca.example.com-cert.pem
#命令创建channel时的tx文件路径
txfilePath =D:\Golang\fabric\code\sdkdemo\src\main\resources\test.tx
#peer的org1的tls的证书路径:peerOrganization-org1-peers-peer0-msp-tlscacerts-com-xxx.pem
tlsPeerFilePath=D:\Golang\fabric\code\sdkdemo\src\main\resources\crypto-config\peerOrganizations\
\org1.example.com\peers\peer0.org1.example.com\msp\tlscacerts\tlsca.org1.example.com-cert.pem
#peer的org2的tls的证书路径:peerOrganization-org1-peers-peer0-msp-tlscacerts-com-xxx.pem
tlsPeerFilePathAddition=D:\Golang\fabric\code\sdkdemo\src\main\resources\crypto-config\peerOrganizations\
\org2.example.com\tlsca\tlsca.org2.example.com-cert.pem
#order节点信息
orderName=orderer.example.com
orderGrpc=grpcs://orderer.example.com:7050
#peer节点信息
peer0Name=peer0.org1.example.com
peer0Grpc=grpcs://peer0.org1.example.com:7051
peer1Name=peer1.org1.example.com
peer1Grpc=grpcs://peer1.org1.example.com:8051
peer0Name2=peer0.org2.example.com
peer0Grpc2=grpcs://peer0.org2.example.com:9051
peer1Name2=peer1.org2.example.com
peer1Grpc2=grpcs://peer1.org2.example.com:10051
#合约相关信息
chainCodeName=basicInfo
chainCodeLocation=D:\Golang\fabric\code\chaincode
endorsementPolicy=D:\Golang\fabric\code\chaincode\src\basicInfo\chaincodeendorsementpolicy.yaml
chainCodeVersion=1.0
##合约升级
newVersion=2.0
chainCodePath=basicInfo
#channel
channelName=test
拷贝相关证书文件
启动网络之后,将
/opt/gopath/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network目录下的crypto-config文件拷贝到工程的resources目录下
注意:其中在1.4.6版本peerOrganization-org1(2)-users-Admin-msp-admincerts目录下的文件没有,但是可以拷贝同个目录下的signcerts的文件
生成tx文件
#cd /opt/gopath/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network
#../bin/configtxgen -profile TwoOrgsChannel -outputCreateChannelTx channel-artifacts/test.tx -channelID test
将生成的test.tx文件拷贝到idea工程的resources目录下
域名映射
编辑C:\Windows\System32\drivers\etc\host
添加下面内容
192.168.xx.xx peer0.org1.example.com peer1.org1.example.com peer0.org2.example.com peer1.org2.example.com orderer.example.com
启动网络方式
#更改世界状态数据库是couchdb启动
#访问方式:ip:5984/_utils
#首次启动
cd /opt/gopath/src/github.com/hyperledger/fabric/scripts/fabric-samples/first-network
./byfn.sh up -s couchdb
#后续启动,启动2个org,4个peer,1个cli,4个couchdb,1个kafka
docker-compose -f docker-compose-cli.yaml -f docker-compose-couch.yaml -f docker-compose-kafka.yaml start
SDK封装
Utils工具类
作用:该工具类用于读取证书和私钥信息到java对象中
属性:包含一个静态内部类和一个静态方法
- 静态内部类:
CAEnrollment,实现了Enrollment接口,覆写getKey()和getCert()方法- 静态方法:
getEnrollment,根据证书目录和私钥目录读取到enrollment里面。
/**
* @description 用户工具类用于读取证书和私钥信息到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;
}
}
/**
* @description 根据证书目录和私钥目录读取到enrollment里面。
* @param keyFolderPath 私钥的目录
* @param keyFileName 私钥的文件名
* @param certFolderPath 证书的目录
* @param certFileName 证书的文件名
* @return enrollment 带有用户信息的对象
* @throws IOException
* @throws NoSuchAlgorithmException
* @throws CryptoException
* @throws InvalidKeySpecException
*/
public static Enrollment getEnrollment(String keyFolderPath, String keyFileName, String certFolderPath, String certFileName)
throws Exception {
PrivateKey key = null;
String certificate = null;
InputStream isKey = null;
BufferedReader brKey = null;
try {
isKey = new FileInputStream(keyFolderPath + File.separator + keyFileName);
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(certFolderPath, certFileName)));
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);
}
}
UserContext类
实现
sdk中User的接口,并重写全部方法,为了安全,还需一个静态加密方法Security.addProvider()
@Data
@AllArgsConstructor
@NoArgsConstructor
/**
* @description 用户对象
*/
public class UserContext implements User, Serializable{
private String name;
private Set<String> roles;
private String account;
private String affiliation;
private Enrollment enrollment;
private String mspId;
static{
Security.addProvider(new BouncyCastleProvider());
}
}
Path类
该类主要是定义各种证书及其路径
/**
* 各种证书及其路径的类
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class Path {
/**
* peer私钥的路径
* peerOrganization-org1(2)-users-Admin-msp-keystore
*/
@Value("${keyFolderPath}")
public String keyFolderPath;
@Value("${keyFolderPath2}")
public String keyFolderPath2;
/**
* peer私钥的值
* 就是上面路径的值 xxx_sk
*/
@Value("${keyFileName}")
public String keyFileName;
@Value("${keyFileName2}")
public String keyFileName2;
/**
* peer证书的路径
* peerOrganization-org1(2)-users-Admin-msp-admincerts
*/
@Value("${certFolderPath}")
public String certFolderPath;
@Value("${certFolderPath2}")
public String certFolderPath2;
/**
* peer证书的值
* 就是上面路径的值 xxx_pem
*/
@Value("${certFileName}")
public String certFileName;
@Value("${certFileName2}")
public String certFileName2;
/**
* order的tls的证书路径
* orderOrganization-tlsca-xxx.pem
*/
@Value("${tlsOrderFilePath}")
public String tlsOrderFilePath;
/**
* 命令创建channel时的tx文件路径
*/
@Value("${txfilePath}")
public String txfilePath;
/**
* peer的tls的证书路径
* peerOrganization-org1-peers-peer0-msp-tlscacerts-com-xxx.pem
*/
@Value("${tlsPeerFilePath}")
public String tlsPeerFilePath;
@Value("${tlsPeerFilePathAddition}")
public String tlsPeerFilePathAddition;
/**
* order节点信息
*/
@Value("${orderName}")
public String orderName;
@Value("${orderGrpc}")
public String orderGrpc;
/**
* peer节点信息
*/
@Value("${peer0Name}")
public String peer0Name;
@Value("${peer0Grpc}")
public String peer0Grpc;
@Value("${peer1Name}")
public String peer1Name;
@Value("${peer1Grpc}")
public String peer1Grpc;
@Value("${peer0Name2}")
public String peer0Name2;
@Value("${peer0Grpc2}")
public String peer0Grpc2;
@Value("${peer1Name2}")
public String peer1Name2;
@Value("${peer1Grpc2}")
public String peer1Grpc2;
/**
* 合约相关
*/
@Value("${chainCodeLocation}")
public String chainCodeLocation;
@Value("${endorsementPolicy}")
public String endorsementPolicy;
@Value("${chainCodeName}")
public String chainCodeName;
@Value("${chainCodeVersion}")
public String chainCodeVersion;
@Value("${newVersion}")
public String newVersion;
@Value("${chainCodePath}")
public String chainCodePath;
/**
* channel相关
*/
@Value("${channelName}")
public String channelName;
}
FabricClient类
该类包括多个方法,包括
cli端创建channle,cli端加入channle,查询channle,创建order,创建peer,peer加入到channel,安装合约,实例化合约,调用合约,升级合约,查询合约等方法
private static final Logger log = LoggerFactory.getLogger(FabricClient.class);
//创建客户端,类似cli
private final HFClient hfClient;
@Autowired
private Path path;
public FabricClient(UserContext userContext) throws Exception {
//创建一个新的客户端实例
hfClient=HFClient.createNewInstance();
//设置加密算法
CryptoSuite cryptoSuite=CryptoSuite.Factory.getCryptoSuite();
//设置凭证工具
hfClient.setCryptoSuite(cryptoSuite);
//设置客户端用户
hfClient.setUserContext(userContext);
}
创建通道
入参:通道名字,
order节点,channel配置文件路径(也就是xxx.tx文件路径)
出参:Channel
/**
* @description 创建channel
* @param channelName channel的名字
* @param order order的信息
* @param txPath 创建channel所需的tx文件
* @return Channel
* @throws Exception 异常
*/
public Channel createChannel(String channelName, Orderer order,String txPath) throws Exception{
//通道配置文件的包装器
ChannelConfiguration cf=new ChannelConfiguration(new File(txPath));
//获取通道配置文件的签名,需要传入通道配置文件的包装器和用户
byte[] cfs = hfClient.getChannelConfigurationSignature(cf, hfClient.getUserContext());
//若只输入通道名称,则在当前客户端下加入一个已经配置好了的通道,若有指定orderer节点和设置配置文件及签名,则是创建一个新的通道
return hfClient.newChannel(channelName,order,cf,cfs);
}
已有通道加入客户端
入参:通道名字
出参:Channel
/**
* @description 已有的通道加入cli
* @param channelName 通道名字
* @return channel
* @throws Exception 异常
*/
public Channel newChannel(String channelName) throws Exception{
//若只输入通道名称,则在当前客户端下加入一个已经配置好了的通道
return hfClient.newChannel(channelName);
}
获取通道
入参:通道名字
出参:Channel
/**
* @description 获取channel
* @param channelName 通道名字
* @return channel
* @throws Exception 异常
*/
public Channel getChannel(String channelName) throws Exception{
//通过通道名称查询通道
return hfClient.getChannel(channelName);
}
创建order
入参:
orderer的名称,grpc(保证节点相互之间通信),tls路径(保证grpc调用生效)
出参:Orderer
/**
*
* @param name order名字
* @param grpcs grpcs
* @param tlsOrderPath tls路径
* @return Orderer
*@throws Exception 异常
*/
public Orderer newOrder(String name,String grpcUrl,String tlsOrderPath) throws Exception{
Properties properties=new Properties();
properties.setProperty("pemFile",tlsOrderPath);
//创建一个orderer节点
return hfClient.newOrderer(name, grpcUrl, properties);
}
获取order
/**
* 获取order节点
* @param channel
*/
public void getOrderers(Channel channel){
Collection<Orderer> orderers = channel.getOrderers();
for (Orderer orderer : orderers) {
log.info("channel里order的信息"+orderer);
}
}
创建peer
和创建
order的方法类似
入参:peer的名称,grpc(保证节点相互之间通信),tls路径(保证grpc调用生效)
出参:peer
/**
* @description 获取peer节点
* @param name peer
* @param grpcUrl grpcs
* @param tlsPeerPath tls路径
* @return Peer
* @throws Exception 异常
*/
public Peer newPeer(String name, String grpcUrl, String tlsPeerPath) throws Exception {
Properties properties = new Properties();
properties.setProperty("pemFile",tlsPeerPath);
//创建一个peer节点
return hfClient.newPeer(name,grpcUrl,properties);
}
获取peer节点
/**
* 获取peer节点
* @param channel
*/
public void getPeers(Channel channel){
log.info("channel里peer的信息"+channel.getPeers());
}
peer加入到Channel
/**
* @description peer节点,加入到channel里
* @param channelName 通道名字
* @param peer peer节点
* @throws Exception 异常
*/
public void joinPeer(String channelName,Peer peer) throws Exception{
//获取channel
Channel channel = getChannel(channelName);
//peer加入channel
channel.joinPeer(peer);
}
至此就可以结合下面的ChannelController类测试通道创建
安装合约
/**
* @description 安装合约
* @param lang 合约开发语言
* @param chainCodeName 合约名称
* @param chainCodeVersion 合约版本
* @param chainCodeLocation 合约的目录路径
* @param chainCodePath 合约的文件夹
* @param peers 安装的peers 节点
* @throws Exception 异常
*/
public void installChainCode(TransactionRequest.Type lang, String chainCodeName, String chainCodeVersion,String chainCodeLocation,String chainCodePath, List<Peer> peers) throws Exception {
//创建安装提案
InstallProposalRequest installProposalRequest = hfClient.newInstallProposalRequest();
ChaincodeID.Builder builder = ChaincodeID.newBuilder().setName(chainCodeName).setVersion(chainCodeVersion);
installProposalRequest.setChaincodeLanguage(lang);
installProposalRequest.setChaincodeID(builder.build());
installProposalRequest.setChaincodeSourceLocation(new File(chainCodeLocation));
installProposalRequest.setChaincodePath(chainCodePath);
//向节点发送安装智能合约提案
Collection<ProposalResponse> responses = hfClient.sendInstallProposal(installProposalRequest,peers);
//遍历提案结果
for(ProposalResponse response:responses){
if(response.getStatus().getStatus()==200){
log.info("{} installed success",response.getPeer().getName());
}else{
log.error("{} installed fail",response.getMessage());
}
}
}
至此就可以结合下面的InstallChainCodeController类测试合约安装
实例化合约
/**
* @description 合约的实例化
* @param channelName 通道名字
* @param lang 编码语音
* @param chaincodeName 链码名字
* @param chaincodeVersion 链码版本
* @param order orderer
* @param peer peer
* @param funcName 合约实例化执行的函数
* @param args 合约实例化执行的参数
* @throws Exception 异常
*/
public void initChainCode(String channelName, TransactionRequest.Type lang, String chaincodeName, String chaincodeVersion, Orderer order, List<Peer> peers, String funcName, String args[]) throws Exception {
//加入channel
Channel channel = newChannel(channelName);
for(Peer p : peers) {
channel.addPeer(p);
}
channel.addOrderer(order);
//初始化通道,启动通道
channel.initialize();
//创建实例化提案
InstantiateProposalRequest instantiateProposalRequest = hfClient.newInstantiationProposalRequest();
instantiateProposalRequest.setArgs(args);
instantiateProposalRequest.setFcn(funcName);
instantiateProposalRequest.setChaincodeLanguage(lang);
//指定背书策略
ChaincodeEndorsementPolicy chaincodeEndorsementPolicy = new ChaincodeEndorsementPolicy();
chaincodeEndorsementPolicy.fromYamlFile(new File("src\main\resources\endorPolicy\chaincodeendorsementpolicy.yaml"));
//chaincodeEndorsementPolicy.fromYamlFile(new File(path.endorsementPolicy));
instantiateProposalRequest.setChaincodeEndorsementPolicy(chaincodeEndorsementPolicy);
ChaincodeID.Builder builder = ChaincodeID.newBuilder().setName(chaincodeName).setVersion(chaincodeVersion);
instantiateProposalRequest.setChaincodeID(builder.build());
//实例化智能合约,若有指定节点,该提案会被发送到指定的节点
Collection<ProposalResponse> responses = channel.sendInstantiationProposal(instantiateProposalRequest);
//遍历提案结果
for(ProposalResponse response:responses){
if(response.getStatus().getStatus()==200){
log.info("{} init sucess",response.getPeer().getName());
}else{
log.error("{} init fail",response.getMessage());
}
}
//用当前客户端的用户将提案结果发送到当前通道的一个orderer节点,可以指定用户和orderer节点,由orderer节点排序和生成新的区块
channel.sendTransaction(responses);
}
至此就可以结合下面的InitChainCodeController类测试合约实例化
调用合约
/**
* @description 合约的调用
* @param channelName 通道名字
* @param lang 编码语音
* @param chaincodeName 链码名字
* @param order orderer
* @param peers peer
* @param funcName 合约调用执行的函数名称
* @param args 合约调用执行的参数
* @throws Exception 异常
*/
public void invoke(String channelName, TransactionRequest.Type lang, String chaincodeName, Orderer order, List<Peer> peers, String funcName, String args[]) throws Exception {
//加入channel
Channel channel = newChannel(channelName);
channel.addOrderer(order);
for(Peer p : peers) {
channel.addPeer(p);
}
channel.initialize();
//创建交易提案
TransactionProposalRequest transactionProposalRequest = hfClient.newTransactionProposalRequest();
transactionProposalRequest.setChaincodeLanguage(lang);
transactionProposalRequest.setArgs(args);
transactionProposalRequest.setFcn(funcName);
ChaincodeID.Builder builder = ChaincodeID.newBuilder().setName(chaincodeName);
transactionProposalRequest.setChaincodeID(builder.build());
//发送交易提案,若有指定节点,该提案会被发送到指定的节点
Collection<ProposalResponse> responses = channel.sendTransactionProposal(transactionProposalRequest,peers);
////遍历交易提案结果
for(ProposalResponse response:responses){
if(response.getStatus().getStatus()==200){
log.info("{} invoke proposal {} sucess",response.getPeer().getName(),funcName);
}else{
String[] logArgs = {response.getMessage(),funcName,response.getPeer().getName()};
log.error("{} invoke proposal {} fail on {}",logArgs);
}
}
//用当前客户端的用户将提案结果发送到当前通道的一个orderer节点,可以指定用户和orderer节点,由orderer节点排序和生成新的区块
channel.sendTransaction(responses);
}
至此就可以结合下面的InvokeChainCodeController类测试合约升级
升级合约
/**
* @description 合约的升级
* @param channelName 通道名字
* @param lang 编码语音
* @param chaincodeName 链码名字
* @param chaincodeVersion 链码版本
* @param order orderer
* @param peer peer
* @param funcName 合约实例化执行的函数
* @param args 合约实例化执行的参数
* @throws Exception 异常
*/
public void invoke(String channelName, TransactionRequest.Type lang, String chaincodeName, Orderer order, List<Peer> peers, String funcName, String args[]) throws Exception {
//加入channel
Channel channel = newChannel(channelName);
channel.addOrderer(order);
for(Peer p : peers) {
channel.addPeer(p);
}
channel.initialize();
//创建交易提案
TransactionProposalRequest transactionProposalRequest = hfClient.newTransactionProposalRequest();
transactionProposalRequest.setChaincodeLanguage(lang);
transactionProposalRequest.setArgs(args);
transactionProposalRequest.setFcn(funcName);
ChaincodeID.Builder builder = ChaincodeID.newBuilder().setName(chaincodeName);
transactionProposalRequest.setChaincodeID(builder.build());
//发送交易提案,若有指定节点,该提案会被发送到指定的节点
Collection<ProposalResponse> responses = channel.sendTransactionProposal(transactionProposalRequest,peers);
////遍历交易提案结果
for(ProposalResponse response:responses){
if(response.getStatus().getStatus()==200){
log.info("{} invoke proposal {} sucess",response.getPeer().getName(),funcName);
}else{
String[] logArgs = {response.getMessage(),funcName,response.getPeer().getName()};
log.error("{} invoke proposal {} fail on {}",logArgs);
}
}
//用当前客户端的用户将提案结果发送到当前通道的一个orderer节点,可以指定用户和orderer节点,由orderer节点排序和生成新的区块
channel.sendTransaction(responses);
}
至此就可以结合下面的UpdateChainCodeController类测试合约升级
查询合约
/**
* @description 合约的查询
* @param peers peer
* @param channelName 通道名字
* @param lang 编码语音
* @param chaincodeName 链码名字
* @param funcName 合约调用执行的函数名称
* @param args 合约调用执行的参数
* @throws Exception 异常
*/
public Map<Object, String> queryChainCode(List<Peer> peers, String channelName, TransactionRequest.Type lang, String chaincodeName, String funcName, String args[]) throws Exception {
Channel channel = getChannel(channelName);
for(Peer p : peers) {
channel.addPeer(p);
}
channel.initialize();
HashMap<Object, String> map = new HashMap();
QueryByChaincodeRequest queryByChaincodeRequest = hfClient.newQueryProposalRequest();
ChaincodeID.Builder builder = ChaincodeID.newBuilder().setName(chaincodeName);
queryByChaincodeRequest.setChaincodeID(builder.build());
queryByChaincodeRequest.setArgs(args);
queryByChaincodeRequest.setFcn(funcName);
queryByChaincodeRequest.setChaincodeLanguage(lang);
Collection<ProposalResponse> responses = channel.queryByChaincode(queryByChaincodeRequest);
for (ProposalResponse response : responses) {
if (response.getStatus().getStatus() == 200) {
log.info("data is {}", response.getProposalResponse().getResponse().getPayload());
map.put(response.getStatus().getStatus(),new String(response.getProposalResponse().getResponse().getPayload().toByteArray()));
} else {
log.error("data get error {}", response.getMessage());
map.put(response.getStatus().getStatus(),response.getMessage());
}
return map;
}
map.put("code","404");
return map;
}
至此就可以结合下面的QueryChainCodeController类测试合约升级
Controller类测试
ChannelController类测试
该类主要是对上面
FabricClient类的通道创建方法进行测试
等价于以下命令#docker exec -it cli bash #ORDERER_CA=/opt/gopath/src/github.com/hyperledger/fabric/peer/crypto/ordererOrganizations/example.com/orderers/orderer.example.com/msp/tlscacerts/tlsca.example.com-cert.pem #peer channel create -o orderer.example.com:7050 -c test -f ./channel-artifacts/mychannel.tx --tls --cafile $ORDERER_CA #peer channel join -b test.block
代码编写
@Controller
@RequestMapping("/sdk")
public class ChannelController {
private static final Logger log = LoggerFactory.getLogger(ChannelController.class);
@Autowired
private Path path;
@RequestMapping("/createChannel")
@ResponseBody
public String createChannel() throws Exception {
UserContext userContext=new UserContext();
userContext.setName("admin");
userContext.setAffiliation("Org1");
userContext.setMspId("Org1MSP");
userContext.setAccount("hongcb");
//使用工具类生成enrollment
Enrollment enrollment = UserUtils.getEnrollment(path.keyFolderPath,path.keyFileName,path.certFolderPath,path.certFileName);
userContext.setEnrollment(enrollment);
FabricClient fabricClient=new FabricClient(userContext);
//创建channel
//1、创建order
Orderer order = fabricClient.newOrder(path.orderName, path.orderGrpc, path.tlsOrderFilePath);
log.info("order信息:"+order);
//2、创建peer
Peer peer = fabricClient.newPeer(path.peer0Name, path.peer0Grpc, path.tlsPeerFilePath);
Peer peer1 = fabricClient.newPeer(path.peer1Name, path.peer1Grpc, path.tlsPeerFilePath);
log.info("peer0Org1信息:"+peer);
log.info("peer1org1信息:"+peer1);
//3、创建channel
Channel channel = fabricClient.createChannel(path.channelName, order, path.txfilePath);
log.info("channel的名字=="+channel.getName());
//获取orders信息
fabricClient.getOrderers(channel);
//4、peer加入通道
fabricClient.joinPeer(path.channelName,peer);
fabricClient.joinPeer(path.channelName,peer1);
log.info("peer节点开始加入通道");
//获取peers信息
fabricClient.getPeers(channel);
boolean initialized = channel.isInitialized();
log.info("是否初始化:"+initialized);
return channel.toString();
}
@RequestMapping("/createChannel2")
@ResponseBody
public String createChannel2(String name) throws Exception {
UserContext userContext=new UserContext();
userContext.setName("admin");
userContext.setAffiliation("Org2");
userContext.setMspId("Org2MSP");
userContext.setAccount("hongcb");
//使用工具类生成enrollment
Enrollment enrollment = UserUtils.getEnrollment(path.keyFolderPath2,path.keyFileName2,path.certFolderPath2,path.certFileName2);
userContext.setEnrollment(enrollment);
FabricClient fabricClient=new FabricClient(userContext);
//1、得到order
Orderer order = fabricClient.newOrder(path.orderName, path.orderGrpc, path.tlsOrderFilePath);
log.info("order信息:"+order);
//获取已有channel
Channel channel = fabricClient.newChannel(path.channelName);
channel.addOrderer(order);
//2、得到peer
Peer peer3 = fabricClient.newPeer(path.peer0Name2, path.peer0Grpc2, path.tlsPeerFilePathAddition);
Peer peer4 = fabricClient.newPeer(path.peer1Name2, path.peer1Grpc2, path.tlsPeerFilePathAddition);
log.info("peer0Org2信息:"+peer3);
log.info("peer1org2信息:"+peer4);
//3、peer加入通道
fabricClient.joinPeer(path.channelName,peer3);
fabricClient.joinPeer(path.channelName,peer4);
log.info("peer节点开始加入通道");
//获取peers信息
fabricClient.getPeers(channel);
// log.info("加入之后,channel里的peer节点信息=="+channel.getPeers());
return channel.toString();
}
}
控制台输出
浏览器输入http://localhost:8080/sdk/createChannel,控制台如下打印:
order信息:Orderer{id: 1, channelName: , name:orderer.example.com, url: grpcs://orderer.example.com:7050}
peer0Org1信息:Peer{ id: 2, name: peer0.org1.example.com, channelName: null, url: grpcs://peer0.org1.example.com:7051}
peer1org1信息:Peer{ id: 3, name: peer1.org1.example.com, channelName: null, url: grpcs://peer1.org1.example.com:8051}
channel的名字==test
channel里order的信息Orderer{id: 1, channelName: test, name:orderer.example.com, url: grpcs://orderer.example.com:7050}
Channel{id: 5, name: test} joining Peer{ id: 2, name: peer0.org1.example.com, channelName: null, url: grpcs://peer0.org1.example.com:7051}
Peer Peer{ id: 2, name: peer0.org1.example.com, channelName: test, url: grpcs://peer0.org1.example.com:7051} joined into channel Channel{id: 5, name: test}
Channel{id: 5, name: test} joining Peer{ id: 3, name: peer1.org1.example.com, channelName: null, url: grpcs://peer1.org1.example.com:8051}
Peer Peer{ id: 3, name: peer1.org1.example.com, channelName: test, url: grpcs://peer1.org1.example.com:8051} joined into channel Channel{id: 5, name: test}
peer节点开始加入通道
channel里peer的信息[Peer{ id: 2, name: peer0.org1.example.com, channelName: test, url: grpcs://peer0.org1.example.com:7051}, Peer{ id: 3, name: peer1.org1.example.com, channelName: test, url: grpcs://peer1.org1.example.com:8051}]
是否初始化:false
浏览器输入http://localhost:8080/sdk/createChannel2,控制台打印和上面类似:
进入cli容器,查看当前的channel,可以看到test通道了
docker exec -it cli /bin/bash
peer channel list
InstallChainCodeController类测试
该类主要是对上面
FabricClient类的合约安装方法进行测试
等价于命令peer chaincode install -n mycc -p 链码位置
代码编写
@Controller
@RequestMapping("/sdk")
public class InstallChainCodeController {
private static final Logger log = LoggerFactory.getLogger(ChannelController.class);
@Autowired
private Path path;
@RequestMapping("/chainCodeInstall")
@ResponseBody
public String chainCodeInstall() throws Exception {
UserContext userContext=new UserContext();
userContext.setName("admin");
userContext.setAffiliation("Org1");
userContext.setMspId("Org1MSP");
userContext.setAccount("hongcb");
//使用工具类生成enrollment
Enrollment enrollment = UserUtils.getEnrollment(path.keyFolderPath,path.keyFileName,path.certFolderPath,path.certFileName);
userContext.setEnrollment(enrollment);
FabricClient fabricClient=new FabricClient(userContext);
//获取peer集合
Peer peer0 = fabricClient.newPeer(path.peer0Name, path.peer0Grpc, path.tlsPeerFilePath);
Peer peer1 = fabricClient.newPeer(path.peer1Name, path.peer1Grpc, path.tlsPeerFilePath);
List<Peer> peers = new ArrayList<>();
peers.add(peer0);
peers.add(peer1);
//在peer中安装合约
fabricClient.installChainCode(TransactionRequest.Type.GO_LANG,path.chainCodeName,
path.chainCodeVersion,path.chainCodeLocation,
path.chainCodePath,peers);
return "install ok 1";
}
@RequestMapping("/chainCodeInstall2")
@ResponseBody
public String chainCodeInstall2() throws Exception {
UserContext userContext=new UserContext();
userContext.setName("admin");
userContext.setAffiliation("Org2");
userContext.setMspId("Org2MSP");
userContext.setAccount("hongcb");
//使用工具类生成enrollment
Enrollment enrollment = UserUtils.getEnrollment(path.keyFolderPath2,path.keyFileName2,path.certFolderPath2,path.certFileName2);
userContext.setEnrollment(enrollment);
FabricClient fabricClient=new FabricClient(userContext);
//获取peer
Peer peer0 = fabricClient.newPeer(path.peer0Name2, path.peer0Grpc2, path.tlsPeerFilePathAddition);
Peer peer1 = fabricClient.newPeer(path.peer1Name2, path.peer1Grpc2, path.tlsPeerFilePathAddition);
List<Peer> peers = new ArrayList<>();
peers.add(peer0);
peers.add(peer1);
//在peer中安装合约
fabricClient.installChainCode(TransactionRequest.Type.GO_LANG,path.chainCodeName,
path.newVersion,path.chainCodeLocation,
path.chainCodePath,peers);
return "install ok 2";
}
}
控制台输出
浏览器输入http://localhost:8080/sdk/chainCodeInstall,控制台如下打印:
Installing 'basicInfo::basicInfo::2.0' language Go chaincode from directory: 'D:\Golang\fabric\code\chaincode\src\basicInfo' with source location: 'src\basicInfo'. chaincodePath:'basicInfo'
peer0.org1.example.com 安装 成功
peer1.org1.example.com 安装 成功
进入cli容器,查看当前的chaincode的安装方法
docker exec -it cli /bin/bash
peer chaincode list --installed
InitChainCodeController类测试
该类主要是对上面
FabricClient类的合约实例化方法进行测试
等价于命令peer chaincode instantiate -o orderer.example.com:7050 --tls --cafile $ORDERER_CA -C mychannel -n test -v 1.0 -c '{"Args":[]}' -P "OR ('Org1MSP.peer')"
代码编写
@Controller
@RequestMapping("/sdk")
public class InitChainCodeController {
private static final Logger log = LoggerFactory.getLogger(ChannelController.class);
@Autowired
private Path path;
@RequestMapping("/init")
@ResponseBody
public String initChainCode() throws Exception {
UserContext userContext=new UserContext();
userContext.setName("admin");
userContext.setAffiliation("Org1");
userContext.setMspId("Org1MSP");
userContext.setAccount("hongcb");
//使用工具类生成enrollment
Enrollment enrollment = UserUtils.getEnrollment(path.keyFolderPath,path.keyFileName,path.certFolderPath,path.certFileName);
userContext.setEnrollment(enrollment);
FabricClient fabricClient=new FabricClient(userContext);
Peer peer0 = fabricClient.newPeer(path.peer0Name, path.peer0Grpc, path.tlsPeerFilePath);
Peer peer1 = fabricClient.newPeer(path.peer1Name, path.peer1Grpc, path.tlsPeerFilePath);
List<Peer> peers = new ArrayList<>();
peers.add(peer0);
peers.add(peer1);
Orderer order = fabricClient.newOrder(path.orderName, path.orderGrpc, path.tlsOrderFilePath);
String initArgs[] = {""};
fabricClient.initChainCode(path.channelName, TransactionRequest.Type.GO_LANG,
path.chainCodeName,path.chainCodeVersion,order,peers,"init",initArgs);
return "init";
}
}
控制台输出
浏览器输入http://localhost:8080/sdk/init,控制台如下打印:
Channel Channel{id: 3, name: test} eventThread started shutdown: false thread: null
peer0.org1.example.com init sucess
peer1.org1.example.com init sucess
进入cli容器,查看当前chaincode的实例化情况
docker exec -it cli /bin/bash
peer chaincode list --instantiated -C test
InvokeChainCodeController类测试
该类主要是对上面
FabricClient类的合约调用方法进行测试
调用之前需要先同时安装链码在peer0和peer1,上一步安装合约测试已经将合约在peer0中了,因此只需要更改一下路径在执行一下安装合约方法即可。再执行调用合约的方法
代码编写
@Controller
@RequestMapping("/sdk")
public class InvokeChainCodeController {
private static final Logger log = LoggerFactory.getLogger(ChannelController.class);
@Autowired
private Path path;
@RequestMapping("/invoke")
@ResponseBody
public String chainCodeInvoke() throws Exception {
UserContext userContext = new UserContext();
userContext.setAffiliation("Org1");
userContext.setMspId("Org1MSP");
userContext.setAccount("hongcb");
userContext.setName("admin");
Enrollment enrollment = UserUtils.getEnrollment(path.keyFolderPath,path.keyFileName,path.certFolderPath,path.certFileName);
userContext.setEnrollment(enrollment);
FabricClient fabricClient = new FabricClient(userContext);
Peer peer0 = fabricClient.newPeer(path.peer0Name,path.peer0Grpc,path.tlsPeerFilePath);
Peer peer1 = fabricClient.newPeer(path.peer1Name,path.peer1Grpc,path.tlsPeerFilePath);
List<Peer> peers = new ArrayList<>();
peers.add(peer0);
peers.add(peer1);
Orderer order = fabricClient.newOrder(path.orderName, path.orderGrpc, path.tlsOrderFilePath);
String[] initArgs = {"110114","{"name":"zhangsan","identity":"110114","mobile":"18910012222"}"};
fabricClient.invoke(path.channelName, TransactionRequest.Type.GO_LANG,path.chainCodeName,order,peers,"save",initArgs);
return "invoke ok";
}
}
控制台输出
浏览器输入http://localhost:8080/sdk/chainCodeInstall2,控制台如下打印:
Installing 'basicInfo::basicInfo::3.0' language Go chaincode from directory: 'D:\Golang\fabric\code\chaincode\src\basicInfo' with source location: 'src\basicInfo'. chaincodePath:'basicInfo'
peer0.org2.example.com installed sucess
peer1.org2.example.com installed sucess
输入http://localhost:8080/sdk/createChannel2,控制台如下打印
order信息:Orderer{id: 1, channelName: , name:orderer.example.com, url: grpcs://orderer.example.com:7050}
peer信息:Peer{ id: 4, name: peer0.org2.example.com, channelName: null, url: grpcs://peer0.org2.example.com:9051}
=================
Channel{id: 2, name: test} joining Peer{ id: 4, name: peer0.org2.example.com, channelName: null, url: grpcs://peer0.org2.example.com:9051}.
=================
Peer Peer{ id: 4, name: peer0.org2.example.com, channelName: test, url: grpcs://peer0.org2.example.com:9051} joined into channel Channel{id: 2, name: test}
=================
channel信息,peer加入后Channel{id: 2, name: test}
在输入http://localhost:8080/sdk/invoke,控制台如下打印
peer0.org1.example.com invoke proposal save sucess
peer0.org2.example.com invoke proposal save sucess
进入cli容器,查看当前chaincode的调用情况
docker exec -it cli /bin/bash
peer chaincode query -C test -n basicInfo -c '{"Args":["query","110114"]}'
未调用前:无数据
调用后:有数据
UpdateChainCodeController类测试
该类主要是对上面
FabricClient类的合约升级方法进行测试
代码编写
@Controller
@RequestMapping("/sdk")
public class UpdateChainCodeController {
private static final Logger log = LoggerFactory.getLogger(ChannelController.class);
@Autowired
private Path path;
@RequestMapping("/update")
@ResponseBody
public String updateChainCode() throws Exception {
UserContext userContext=new UserContext();
userContext.setName("admin");
userContext.setAffiliation("Org1");
userContext.setMspId("Org1MSP");
userContext.setAccount("hongcb");
//使用工具类生成enrollment
Enrollment enrollment = UserUtils.getEnrollment(path.keyFolderPath,path.keyFileName,path.certFolderPath,path.certFileName);
userContext.setEnrollment(enrollment);
FabricClient fabricClient=new FabricClient(userContext);
Peer peer = fabricClient.getPeer(path.peer0Name, path.peer0Grpc, path.tlsPeerFilePath);
Orderer order = fabricClient.getOrder(path.orderName, path.orderGrpc, path.tlsOrderFilePath);
String initArgs[] = {""};
fabricClient.upgradeChainCode(path.channelName, TransactionRequest.Type.GO_LANG,path.chainCodeName,path.newVersion,order,peer,"init",initArgs);
return "update";
}
}
控制台输出
测试前需要先修改版本为3.0,再运行安装合约的方法,再运行升级合约的方法
浏览器输入`http://localhost:8080/sdk/update,控制台如下打印:
Channel Channel{id: 3, name: test} eventThread started shutdown: false thread: null
peer0.org1.example.com upgrade sucess
进入cli容器,查看当前chaincode的实例化情况
docker exec -it cli /bin/bash
peer chaincode list --instantiated -C test
未运行升级方法之前,版本为2.0
运行升级方法之后,版本为3.0
QueryChainCodeController类测试
该类主要是对上面
FabricClient类的合约查询方法进行测试
可以使用peer0节点也可以使用peer1节点查询
(查询合约无需orderer,上传才需要)
代码编写
@Controller
@RequestMapping("/sdk")
public class QueryChainCodeController {
private static final Logger log = LoggerFactory.getLogger(ChannelController.class);
@Autowired
private Path path;
@RequestMapping("/query")
@ResponseBody
public String queryChainCode(String name) throws Exception {
UserContext userContext = new UserContext();
userContext.setAffiliation("Org2");
userContext.setMspId("Org2MSP");
userContext.setAccount("hongcb");
userContext.setName("admin");
Enrollment enrollment = UserUtils.getEnrollment(path.keyFolderPath2,path.keyFileName2,path.certFolderPath2,path.certFileName2);
userContext.setEnrollment(enrollment);
FabricClient fabricClient = new FabricClient(userContext);
Peer peer0 = fabricClient.getPeer(path.peer0Name,path.peer0Grpc,path.tlsPeerFilePath);
Peer peer1 = fabricClient.getPeer(path.peer0Name2,path.peer0Grpc2,path.tlsPeerFilePathAddition);
List<Peer> peers = new ArrayList<>();
peers.add(peer0);
peers.add(peer1);
String[] initArgs = {"110114"};
Map<Object, String> map = fabricClient.queryChainCode(peers, path.channelName, TransactionRequest.Type.GO_LANG, path.chainCodeName, "query", initArgs);
log.info("查询到的信息:"+map);
return "query ok";
}
}
控制台输出
浏览器输入http://localhost:8080/sdk/query,控制台如下打印: