区块链笔记(5)-sdk的封装

104 阅读11分钟

前言

上一节只是搭建了本地IDEASDK环境,结合服务器上的环境跑通了,但是具体的细节还是很多不理解,因此尝试动手封装api,开始一步一步研究。

这一节主要是实现SDK的重要API,包括cli端创建channlecli端加入channle,查询channle,创建order,创建peerpeer加入到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

实现sdkUser的接口,并重写全部方法,为了安全,还需一个静态加密方法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端创建channlecli端加入channle,查询channle,创建order,创建peerpeer加入到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

image-20210410225510441

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

image-20210411203948905

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

image-20210411212558804

InvokeChainCodeController类测试

该类主要是对上面FabricClient类的合约调用方法进行测试
调用之前需要先同时安装链码在peer0peer1,上一步安装合约测试已经将合约在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"]}'

未调用前:无数据

image-20210412120911567

调用后:有数据
image-20210412121003441

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

image-20210411232037889

运行升级方法之后,版本为3.0

image-20210411232123510

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,控制台如下打印:
image-20210412142611737