Skip to content

Latest commit

 

History

History
1193 lines (886 loc) · 36.7 KB

File metadata and controls

1193 lines (886 loc) · 36.7 KB

fabric-sdk 作用

[toc]

一、SDK 提供的功能

在之前的操作中 无论是创建 channel 还是 链码 安装实例化 合约的查询 调用 在 CA 中注册用户,都是通过命令行的形式去操作的。 sdk 提供了一系列的功能,可以通过 sdk 去实现之前使用命令行操作的效果。 现阶段 fabric 提供的比较稳定的 sdk 有:Node、java、go 如果是创建一个区块链管理平台。就更需要 SDK 来获取到区块链中的信息。

总之,业务系统想要与 fabric 工程交互就应该使用 sdk。

二、使用 java-fabric-sdk

创建一个 springboot 项目 pom 中引用对应版本的 sdk

<!--fabric java sdk -->
<dependency>
   <groupId>org.hyperledger.fabric-sdk-java</groupId>
   <artifactId>fabric-sdk-java</artifactId>
   <version>1.4.6</version>
</dependency>

三、使用 sdk 创建 channel 并将 peer 加入到 channel 中

创建 channel 需要使用 channel 配置文件中指定在 Channel 中的 org 的 Admin 用户。 创建 channel 需要生成创世区块,这一步需要有 orderer 的配置信息

所以 使用 sdk 创建 channel 要准备一下的配置信息

  • channel 中任意一个 org 的 admin 用户信息
  • orderer 节点的信息

3.1 启动 first-network

使用 fabric-samples 提供的 byfn.sh 脚本启动 first-network

./byfn.sh uo -s couchdb

启动完成后会在 first-network 目录下生成两个目录

crypto-config : 存放了orderer的证书和peer的证书
channel-artifacts:存放了channel的配置和peer锚节点的配置以及channel的创世区块

3.2 生成 channel 的配置文件

使用 first-network 中提供的 configtx.yaml 配置文件, 配置文件中有关于 channel 的一个配置。

TwoOrgsChannel:
        Consortium: SampleConsortium
        <<: *ChannelDefaults
        Application:
            <<: *ApplicationDefaults
            Organizations:
                - *Org1
                - *Org2
            Capabilities:
                <<: *ApplicationCapabilities

按照这个配置文件的描述,将会生成一个 SampleConsortium 联盟,联盟中包含 Org1 和 Org2 两个组织 使用 configtxgen 工具 生成 channel 的配置文件

configtxgen -profile TwoOrgsChannel -outputCreateChannelTx channel-artifacts/mychannel1.tx -channelID mychannel1

以上命令中的几个参数

-profile 使用配置文件中Profiles下的那个配置项,这里用到的Profiles.TwoOrgsChannel
  -outputCreateChannelTx 执行最终生成的channel的tx配置文件的位置
  -channelID 使用这个configtx的channel的channelId
  -configPath 指定包含configtx.yaml配置文件的目录(如果命令执行的目录就包含configtx.yaml则不需要使用该参数)

执行完命令后在指定的目录下会出现 mychannel1.tx 配置文件。

3.3 创建一个 springboot 项目

通过 maven 引入 fabric-sdk-java

<!--fabric java sdk -->
<dependency>
    <groupId>org.hyperledger.fabric-sdk-java</groupId>
    <artifactId>fabric-sdk-java</artifactId>
    <version>1.4.6</version>
</dependency>

将上一步生成的 channel1.tx 和联盟中的证书都复制到 springboot 的 resources 目录下

img 为了方便从 resources 目录下读取文件编写一个 utils 类

import org.springframework.core.io.ClassPathResource;

import java.io.File;

public class ClasspathFileUtils {
    /**
     * 在springboot的resources目录下 获取文件
     *
     * @param resPath
     * @return
     * @throws Exception
     */
    public static File getFileFromSpringBootClassPath(String resPath) throws Exception {
        ClassPathResource classPathResource = new ClassPathResource(resPath);
        return classPathResource.getFile();
    }
}

3.4 修改 application.properties 文件

# 配置用户信息
fabric.user.name=admin
fabric.user.account=LH
fabric.user.affiliation=Org1
fabric.user.msp-id=Org1MSP

# 配置orderer信息
fabric.orderer.name=orderer.example.com
fabric.orderer.grpcs-addr=grpcs://orderer.example.com:7050
fabric.orderer.tlsca-cert=crypto-config/ordererOrganizations/example.com/tlsca/tlsca.example.com-cert.pem
fabric.orderer.ca-cert=crypto-config/ordererOrganizations/example.com/ca/ca.example.com-cert.pem

# 配置channel信息
fabric.channel.channel-name=mychannel1
fabric.channel.channel-config-tx-path=channel-artifacts/mychannel1.tx

# 配置org1-peer0的信息
fabric.org1-peer0.name=peer0.org1.example.com
fabric.org1-peer0.grpcs-addr=grpcs://peer0.org1.example.com:7051
fabric.org1-peer0.tlsca-cert=crypto-config/peerOrganizations/org1.example.com/msp/tlscacerts/tlsca.org1.example.com-cert.pem
fabric.org1-peer0.users-admin-private-key=crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/keystore/2b33de36c48cc20ff8056c525db5ddfc3b3cbfe337984e867294de19fc3a770b_sk
fabric.org1-peer0.users-admin-cert=crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/admincerts/[email protected]

编写配置文件对应的配置类

img

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Set;
@Getter
@Setter
@ConfigurationProperties(prefix = "fabric.user")
public class FabricUserProperties {
    private String name;
    private String account;
    private String affiliation;
    private String mspId;
    private Set<String> roles;
}
@Getter
@Setter
@ConfigurationProperties(prefix = "fabric.channel")
public class FabricChannelProperties {
    private String channelName;
    private String channelConfigTxPath;
}
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Getter
@Setter
@ConfigurationProperties(prefix = "fabric.orderer")
public class FabricOrdererProperties {
    private String name;
    private String grpcsAddr;
    private String tlscaCert;
    private String caCert;
}
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
@Getter
@Setter
@ConfigurationProperties(prefix = "fabric.org1-peer0")
public class FabricOrg1Peer0Properties {
    private String name;
    private String grpcsAddr;
    private String tlscaCert;
    private String usersAdminPrivateKey;
    private String usersAdminCert;
}

在启动类上声明使用的配置类

@EnableConfigurationProperties({
        FabricUserProperties.class,
        FabricOrdererProperties.class,
        FabricChannelProperties.class,
        FabricOrg1Peer0Properties.class
})
@SpringBootApplication
public class JavaFabricSdkApplication {
    public static void main(String[] args) {
        SpringApplication.run(JavaFabricSdkApplication.class, args);
    }
}

3.5 编写 Fabric 用户信息实现 Fabric User 接口

import com.lhit.fabric.javafabricsdk.fabric.properties.FabricOrg1Peer0Properties;
import com.lhit.fabric.javafabricsdk.fabric.properties.FabricUserProperties;
import com.lhit.fabric.javafabricsdk.fabric.util.UserUtils;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.hyperledger.fabric.sdk.Enrollment;
import org.hyperledger.fabric.sdk.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
import java.security.Security;
import java.util.Set;

/**
 * 在联盟网络中的用户信息
 */
@Slf4j
@Getter
@Setter
@Component
public class FabricUserContext implements User {

    @Autowired
    private FabricUserProperties userProperties;

    @Autowired
    private FabricOrg1Peer0Properties org1Peer0Properties;

    @PostConstruct
    private void init() {
        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>加载FabricUserContext");
        // 指定下加密算法 否则节点通讯过程会报错
        Security.addProvider(new BouncyCastleProvider());
    }

    @Override
    public String getName() {
        return userProperties.getName();
    }

    @Override
    public Set<String> getRoles() {
        return userProperties.getRoles();
    }

    @Override
    public String getAccount() {
        return userProperties.getAccount();
    }

    @Override
    public String getAffiliation() {
        return userProperties.getAffiliation();
    }

    @Override
    public Enrollment getEnrollment() {
        try {
        	// 这里用到了org1 peer0节点的 私钥和证书。用于用户enroll到节点中
            Enrollment enrollment = UserUtils.getEnrollment(org1Peer0Properties.getUsersAdminPrivateKey(), org1Peer0Properties.getUsersAdminCert());
            return enrollment;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    @Override
    public String getMspId() {
        return userProperties.getMspId();
    }
}

为了方便读取证书信息封装的工具类

import org.bouncycastle.crypto.CryptoException;
import org.hyperledger.fabric.sdk.Enrollment;
import javax.xml.bind.DatatypeConverter;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;

/**
 * 用户工具类用于读取证书和私钥信息到java对象中
 */
public class UserUtils {

    private static class CAEnrollment implements Enrollment {

        private PrivateKey key;
        private String ecert;

        public CAEnrollment(PrivateKey key, String ecert) {
            this.key = key;
            this.ecert = ecert;
        }

        @Override
        public PrivateKey getKey() {
            return key;
        }

        @Override
        public String getCert() {
            return ecert;
        }
    }

    /**
     * @param keyPath  私钥的地址 crypto-config/ordererOrganizations/example.com/ca/ca.example.com-cert.pem
     * @param certPath 证书的地址 crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/admincerts/[email protected]
     * @return enrollment 带有用户信息的对象
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws CryptoException
     * @throws InvalidKeySpecException
     * @description 根据证书目录和私钥目录读取到enrollment里面。
     */
    public static Enrollment getEnrollment(String keyPath, String certPath) throws Exception {
        PrivateKey key = null;
        String certificate = null;
        InputStream isKey = null;
        BufferedReader brKey = null;
        try {
            isKey = new FileInputStream(ClasspathFileUtils.getFileFromSpringBootClassPath(keyPath));
            brKey = new BufferedReader(new InputStreamReader(isKey));
            StringBuilder keyBuilder = new StringBuilder();
            for (String line = brKey.readLine(); line != null; line = brKey.readLine()) {
                if (line.indexOf("PRIVATE") == -1) {
                    keyBuilder.append(line);
                }
            }
            certificate = new String(Files.readAllBytes(Paths.get(ClasspathFileUtils.getFileFromSpringBootClassPath(certPath).getPath())));
            byte[] encoded = DatatypeConverter.parseBase64Binary(keyBuilder.toString());
            PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(encoded);
            KeyFactory kf = KeyFactory.getInstance("ECDSA");
            key = kf.generatePrivate(keySpec);
        } finally {
            isKey.close();
            brKey.close();
        }
        return new CAEnrollment(key, certificate);
    }
}

3.6 编写 FabricClient 用于与网络的交互

import com.lhit.fabric.javafabricsdk.fabric.properties.FabricChannelProperties;
import com.lhit.fabric.javafabricsdk.fabric.properties.FabricOrdererProperties;
import com.lhit.fabric.javafabricsdk.fabric.properties.FabricOrg1Peer0Properties;
import com.lhit.fabric.javafabricsdk.fabric.user.FabricUserContext;
import com.lhit.fabric.javafabricsdk.fabric.util.ClasspathFileUtils;
import lombok.extern.slf4j.Slf4j;
import org.hyperledger.fabric.sdk.*;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import java.util.Properties;

/**
 * 创建Channel使用到的client
 */
@Slf4j
@Component
public class FabricClient {

    @Autowired
    private FabricUserContext userContext;

    // Hyperledger Fabric Client 用于创建Channel
    private HFClient hfClient;

    @Autowired
    private FabricChannelProperties channelProperties;

    @Autowired
    private FabricOrdererProperties ordererProperties;

    @Autowired
    private FabricOrg1Peer0Properties org1Peer0Properties;

    private Orderer orderer;

    private Peer org1Peer0;

    @PostConstruct
    private void init() throws Exception {
        log.info(">>>>>>>>>>>>>>>>>>>>>>>>>>加载FabricClient");
        // 创建客户端
        hfClient = HFClient.createNewInstance();
        // 指定加密算法
        CryptoSuite cryptoSuite = CryptoSuite.Factory.getCryptoSuite();
        hfClient.setCryptoSuite(cryptoSuite);
        // 指定用户身份
        hfClient.setUserContext(userContext);
    }

    /**
     * 创建channel
     *
     */
    public Channel createChannel() throws Exception {

        // channel的配置信息
        ChannelConfiguration channelConfiguration = new ChannelConfiguration(ClasspathFileUtils.getFileFromSpringBootClassPath(channelProperties.getChannelConfigTxPath()));
        // 创建channel 需要的参数
        // channelName channel的名称
        // orderer orderer节点对象
        // channelConfiguration channel的配置信息
        // channelConfigurationSignature 用户签名信息
        Channel channel = hfClient.newChannel(channelProperties.getChannelName(), getOrderer(), channelConfiguration, hfClient.getChannelConfigurationSignature(channelConfiguration, hfClient.getUserContext()));
        channel.initialize();
        return channel;
    }

    /**
     * 获取orderer对象
     *
     * @return
     * @throws Exception
     */
    public Orderer getOrderer() throws Exception {
        if (orderer == null) {
            String path = ClasspathFileUtils.getFileFromSpringBootClassPath(ordererProperties.getTlscaCert()).getPath();
            Properties properties = new Properties();
            properties.setProperty("pemFile", path);
            Orderer orderer = hfClient.newOrderer(ordererProperties.getName(), ordererProperties.getGrpcsAddr(), properties);
            return orderer;
        }
        return orderer;
    }
    
    /**
     * 获取peer对象
     *
     * @return
     * @throws Exception
     */
    public Peer getOrg1Peer0() throws Exception {
        if (org1Peer0 == null) {
            String path = ClasspathFileUtils.getFileFromSpringBootClassPath(org1Peer0Properties.getTlscaCert()).getPath();
            Properties properties = new Properties();
            properties.setProperty("pemFile", path);
            Peer peer = hfClient.newPeer(org1Peer0Properties.getName(), org1Peer0Properties.getGrpcsAddr(), properties);
            return peer;
        }
        return org1Peer0;
    }

    /**
     * 根据channel名称获取channel
     *
     * @param channelName
     * @return
     * @throws Exception
     */
    public Channel getChannel(String channelName) throws Exception {
        Channel channel = hfClient.getChannel(channelName);
        if (channel == null) {
            channel = hfClient.newChannel(channelName);
        }
        channel.addOrderer(getOrderer());
        return channel;
    }
}

3.7 编写 Service 来提供与 Fabric 网络交互的能力

import com.lhit.fabric.javafabricsdk.fabric.client.FabricClient;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.Peer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class FabricChannelService {

    @Autowired
    private FabricClient fabricClient;


    /**
     * 创建通道
     *
     * @throws Exception
     */
    public Channel createChannel() throws Exception {
        return fabricClient.createChannel();
    }

    /**
     * 加入通道
     *
     * @throws Exception
     */
    public void joinChannel(Channel channel,Peer peer) throws Exception {
        channel.joinPeer(peer);
        channel.initialize();
    }

    public Channel getChannel(String channelName) throws Exception {
        return fabricClient.getChannel(channelName);
    }
}

3.8 完整的目录结构

img

3.9 编写测试类 测试是否可以成功创建 channel 并将节点 org1Peer0 加入到 channel 中

@Slf4j
@SpringBootTest
class JavaFabricSdkApplicationTests {


    @Autowired
    private FabricChannelService channelService;

    @Autowired
    private FabricClient fabricClient;

    @Test
    void contextLoads() throws Exception {

        log.info("开始创建channel");
        Channel channel = channelService.createChannel();

        log.info("org1peer0节点 加入channel");
        channelService.joinChannel(channel,fabricClient.getOrg1Peer0());

        log.info("完成创建channel并将org1peer0加入到channel中");
    }

}

成功执行完毕后 ,如果没有报错 到 docker 容器中验证 org1 peer0 节点是否加入到了 channel 中

# 进入到cli容器中
docker exec -it cli bash
# cli默认的用户身份就是 org1 peer0 admin用户所以可以直接查看当前节点加入的channel
peer channel list
# 输出 说明 成功的通过java sdk向fabric网络中添加了channel和并将peer加入到了channel中
root@dbe359f6631b:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2020-04-01 07:28:46.769 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined: 
mychannel
mychannel1

四、使用 java sdk 安装链码到 peer 节点中

4.1 在 FabricChannelService 中添加安装链码的方法

/**
     * 安装链码
     *
     * 注意:目录 {chaincodeLoaction}/src/{chaincodePath}/
     *
     * 在当前项目中 就是
     * chaincodeLoaction = chaincode
     * chaincodePath = basic_info
     *
     * @param type          链码语言GO JAVA NODE
     * @param chaincodeName 链码名称
     * @param version       链码版本
     * @param chaincodeLoaction 链码路径
     * @param chaincodePath 链码路径
     * @param peers         要安装到哪些节点上(这些节点必须在同一个org内)
     * @throws Exception
     */
    public Collection<ProposalResponse> installChaincode(TransactionRequest.Type type, String chaincodeName, String version,String chaincodeLoaction ,String chaincodePath, List<Peer> peers) throws Exception {
        // 初始化Install对象
        InstallProposalRequest installProposalRequest = fabricClient.getHfClient().newInstallProposalRequest();
        // 生成chaincodeId
        ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).setVersion(version).build();
        // 设置链码语言类型
        installProposalRequest.setChaincodeLanguage(type);
        // 设置chaincodeId
        installProposalRequest.setChaincodeID(chaincodeID);
        // 指定链码源文件
        installProposalRequest.setChaincodeSourceLocation(ClasspathFileUtils.getFileFromSpringBootClassPath(chaincodeLoaction));
        // 设置chanincodePata
        installProposalRequest.setChaincodePath(chaincodePath);
        return fabricClient.getHfClient().sendInstallProposal(installProposalRequest, peers);
    }

编写测试用例

@Test
    void testInstallChaincodeToPeer() throws Exception {
        Collection<ProposalResponse> basicinfo = channelService.installChaincode(TransactionRequest.Type.GO_LANG,
                "basicinfo",
                "1.0",
                "chaincode",
                "basic_info",
                Lists.newArrayList(fabricClient.getOrg1Peer0()));
    }

测试完成后在容器中查看 Org1Peer0 是否已经安装了链码

# 进入容器
docker exec -it cli bash

# 执行查看链码安装列表命令
peer chaincode list --installed

# 输出
Get installed chaincodes on peer:
Name: basicinfo, Version: 1.0, Path: basic_info, Id: 124f873c5b5ffa6b57fa2dbb55c463bff7cae916ef297d2d78c6f345126714a2
Name: mycc, Version: 1.0, Path: github.com/chaincode/chaincode_example02/go/, Id: 333a19b11063d0ade7be691f9f22c04ad369baba15660f7ae9511fd1a6488209

五、使用 java sdk 实例化链码

在 service 中添加实例化链码的方法, 在实例化链码时需要制定背书策略。背书策略一般使用 yaml 文件来描述

# 背书策略描述文件
identities: # 指定参与背书的角色身份 因为代码中只是将链码安装实例化到了org1的peer0节点,所以先只写user1
  user1: {"role":{"name":"member","mspId":"Org1MSP"}}
#  user2: {"role":{"name":"member","mspId":"Org2MSP"}}
policy: # 具体策略
  1-of: # 以下声明中的一个 就是 user1 背书即可
    - signed-by: "user1"
#    - signed-by: "user2"
#  2-of: # 以下声明中的两个 就是 user1 user2 都需要背书即可
#    - signed-by: "user1"
#    - signed-by: "user2"
#  1-of: # 以下声明中的任意一个 就是 user1 user2 其中一个背书即可
#    - signed-by: "user1"
#    - signed-by: "user2"

把文件复制到 resources 目录下

img

/**
     * 实例化链码
     *
     * @param type          链码语言类型
     * @param channelName   channel名称
     * @param chaincodeName 链码名称
     * @param version       版本
     * @param orderer       orderer节点信息
     * @param peer          要安装到那个peer
     * @param funcName      初始化方法名 如果没有可以填写任意字符串
     * @param args          初始化方法传参 如果没有 也要传个 String[] args = {""}
     * @throws Exception
     */
    public void instantiateChaincode(TransactionRequest.Type type, String channelName, String chaincodeName, String version, Orderer orderer, Peer peer, String funcName, String[] args) throws Exception {

        // 获取到channel
        Channel channel = getChannel(channelName);
        // 指定channel中的 orderer和peer
        channel.addPeer(peer);
        channel.addOrderer(orderer);
        // 初始化channel
        channel.initialize();

        //构造提案
        InstantiateProposalRequest instantiateProposalRequest = fabricClient.getHfClient().newInstantiationProposalRequest();
        // 设置语言
        instantiateProposalRequest.setChaincodeLanguage(type);

        // 生成chaincodeId
        ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).setVersion(version).build();
        instantiateProposalRequest.setChaincodeID(chaincodeID);
        // 设置初始化方法和参数
        instantiateProposalRequest.setFcn(funcName);
        instantiateProposalRequest.setArgs(args);

        // 设置背书策略
        ChaincodeEndorsementPolicy chaincodeEndorsementPolicy = new ChaincodeEndorsementPolicy();
        chaincodeEndorsementPolicy.fromYamlFile(ClasspathFileUtils.getFileFromSpringBootClassPath("endorsement_policy/my_endorsement_policy.yaml"));
        // 背书策略设置到提案中
        instantiateProposalRequest.setChaincodeEndorsementPolicy(chaincodeEndorsementPolicy);

        // 合约实例化 用channel 提交提案
        Collection<ProposalResponse> proposalResponses = channel.sendInstantiationProposal(instantiateProposalRequest);
        for (ProposalResponse proposalRespons : proposalResponses) {
            if (proposalRespons.getStatus().getStatus() != 200) {
                throw new Exception("提案返回报错");
            }
        }

        // 提交 数据到链上
        channel.sendTransaction(proposalResponses);

    }

添加测试方法

@Test
    void testInstantiateChaincode() throws Exception {
        log.info("开始实例化链码");
        String[] args = {""};
        channelService.instantiateChaincode(TransactionRequest.Type.GO_LANG,
                channelProperties.getChannelName(),
                "basicinfo",
                "1.0",
                fabricClient.getOrderer(),
                fabricClient.getOrg1Peer0(),
                "",
                args
        );
        log.info("完成实例化链码");
    }

测试通过后检查对应节点上已经实例化的链码

# 进入cli容器
docker exec -it cli bash
# 查看当前节点已经实例化的链码
peer chaincode list --instantiated -C mychannel1
# 输出channel中已经实例化的链码
Get instantiated chaincodes on channel mychannel1:
Name: basicinfo, Version: 1.0, Path: basic_info, Escc: escc, Vscc: vscc

六、更新链码

在 service 中增加更新链码的方法

/**
     * 更新升级链码
     *
     * @param type          链码语言类型
     * @param channelName   channel名称
     * @param chaincodeName 链码名称
     * @param version       版本
     * @param orderer       orderer节点信息
     * @param peer          要安装到那个peer
     * @param funcName      初始化方法名 如果没有可以填写任意字符串
     * @param args          初始化方法传参 如果没有 也要传个 String[] args = {""}
     * @throws Exception
     */
    public void upgradeChaincode(TransactionRequest.Type type, String channelName, String chaincodeName, String version, Orderer orderer, Peer peer, String funcName, String[] args) throws Exception {

        // 获取到channel
        Channel channel = getChannel(channelName);
        // 指定channel中的 orderer和peer
        channel.addPeer(peer);
        channel.addOrderer(orderer);
        // 初始化channel
        channel.initialize();

        //构造提案
        UpgradeProposalRequest upgradeProposalRequest = fabricClient.getHfClient().newUpgradeProposalRequest();
        // 设置语言
        upgradeProposalRequest.setChaincodeLanguage(type);

        // 生成chaincodeId
        ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).setVersion(version).build();
        upgradeProposalRequest.setChaincodeID(chaincodeID);
        // 设置初始化方法和参数
        upgradeProposalRequest.setFcn(funcName);
        upgradeProposalRequest.setArgs(args);

        // 修改背书策略
        ChaincodeEndorsementPolicy chaincodeEndorsementPolicy = new ChaincodeEndorsementPolicy();
        chaincodeEndorsementPolicy.fromYamlFile(ClasspathFileUtils.getFileFromSpringBootClassPath("endorsement_policy/my_endorsement_policy.yaml"));
        // 背书策略设置到提案中
        upgradeProposalRequest.setChaincodeEndorsementPolicy(chaincodeEndorsementPolicy);


        // 合约实例化 用channel 提交提案
        Collection<ProposalResponse> proposalResponses = channel.sendUpgradeProposal(upgradeProposalRequest);
        for (ProposalResponse proposalRespons : proposalResponses) {
            if (proposalRespons.getStatus().getStatus() != 200) {
                throw new Exception("提案返回报错");
            }
        }

        // 提交 数据到链上
        channel.sendTransaction(proposalResponses);
    }

编写测试方法 要想更新链码到 2.0 版本 就需要先将 2.0 版的链码安装到 peer 上。

@Test
    void testUpgradeChaincode() throws Exception {
        log.info("开始更新链码");

        // 先安装2.0合约
        Collection<ProposalResponse> basicinfo = channelService.installChaincode(TransactionRequest.Type.GO_LANG,
                "basicinfo",
                "2.0",
                "chaincode",
                "basic_info",
                Lists.newArrayList(fabricClient.getOrg1Peer0()));

        System.out.println("end");

        // 更新链码到2.0
        String[] args = {""};
        channelService.upgradeChaincode(TransactionRequest.Type.GO_LANG,
                channelProperties.getChannelName(),
                "basicinfo",
                "2.0",
                fabricClient.getOrderer(),
                fabricClient.getOrg1Peer0(),
                "",
                args
        );
        log.info("完成更新链码");
    }

执行完成后 查看 channel 中 peer 已经实例化的链码

# 进入cli容器
docker exec -it cli bash
# 查看已经实例化的链码
peer chaincode list --instantiated -C mychannel1
# 输出
Get instantiated chaincodes on channel mychannel1:
Name: basicinfo, Version: 2.0, Path: basic_info, Escc: escc, Vscc: vscc

可以看到 basicinfo 链码的 version 已经是 2.0 了

七、 调用链码 - invoke

在 service 中增加调用链码的方法

/**
     * 调用链码invoke链码
     *
     * @param type          链码语言类型
     * @param channelName   channel名称
     * @param chaincodeName 链码名称
     * @param version       版本
     * @param orderer       orderer节点信息
     * @param peers          需要哪些peer背书
     * @param funcName      调用方法名
     * @param args          方法传参
     * @throws Exception
     */
    public CompletableFuture<BlockEvent.TransactionEvent> invokeChaincode(TransactionRequest.Type type, String channelName, String chaincodeName, String version, Orderer orderer,List<Peer>  peers, String funcName, String[] args) throws Exception {

        // 获取到channel
        Channel channel = getChannel(channelName);
        for (Peer peer : peers) {
            channel.addPeer(peer);
        }
        channel.addOrderer(orderer);
        // 初始化channel
        channel.initialize();

        // 构建交易提案
        TransactionProposalRequest proposalRequest = fabricClient.getHfClient().newTransactionProposalRequest();
        // 指定语言
        proposalRequest.setChaincodeLanguage(TransactionRequest.Type.GO_LANG);
        // 生成chaincodeId
        ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).setVersion(version).build();
        proposalRequest.setChaincodeID(chaincodeID);
        // 设置初始化方法和参数
        proposalRequest.setFcn(funcName);
        proposalRequest.setArgs(args);

        // 发送提案 peers要与背书规则相匹配,需要哪个peer背书 就将那个peer添加进去
        Collection<ProposalResponse> proposalResponses = channel.sendTransactionProposal(proposalRequest);
        // 合约实例化 用channel 提交提案
        for (ProposalResponse proposalRespons : proposalResponses) {
            if (proposalRespons.getStatus().getStatus() != 200) {
                throw new Exception("提案返回报错:" + proposalRespons.getMessage());
            } else {
                log.info("调用:{} 链码方法成功",funcName);
            }
        }

        // 发送数据到链上
        return channel.sendTransaction(proposalResponses);

    }

编写测试方法

@Test
    void testInvokeChaincode() throws Exception {
        log.info("开始调用链码方法");
//        {"identity":"110115","mobile":"18910012222","name":"zhangsan"}
        String[] args = {"110115", "{\"name\":\"zhangsan-5.0\",\"identity\":\"110115\",\"mobile\":\"18910012222\"}"};
        CompletableFuture<BlockEvent.TransactionEvent> completableFuture = channelService.invokeChaincode(TransactionRequest.Type.GO_LANG,
                channelProperties.getChannelName(),
                "basicinfo",
                "2.0",
                fabricClient.getOrderer(),
                Lists.newArrayList(fabricClient.getOrg1Peer0()),
                "save",
                args
        );
        log.info("完成链码");
    }

完成测试后 到 cli 容器中验证下

# 进入cli容器
docker exec -it cli bash

# 查询保存的数据
peer chaincode query -C mychannel4 -n basicinfo  -c '{"Args":["query","110115"]}'
# 输出
{"identity":"110115","mobile":"18910012222","name":"zhangsan-5.0"}

八 、使用 sdk 提供的查询功能

在 service 中添加查询方法

/**
     * 调用链码query链码
     *
     * @param channelName   channel名称
     * @param chaincodeName 链码名称
     * @param funcName      调用方法名
     * @param args          方法传参
     * @throws Exception
     */
    public ProposalResponse queryChaincode(TransactionRequest.Type type,String channelName, String chaincodeName, List<Peer> peers, String funcName, String[] args) throws Exception {
        // 获取到channel
        Channel channel = getChannel(channelName);
        // 多个peer 可以防止单点故障
        for (Peer peer : peers) {
            channel.addPeer(peer);
        }
        // 初始化channel
        channel.initialize();


        // 构建交易提案
        QueryByChaincodeRequest queryByChaincodeRequest = fabricClient.getHfClient().newQueryProposalRequest();
        // 指定语言
        queryByChaincodeRequest.setChaincodeLanguage(type);
        // 生成chaincodeId
        ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).build();
        queryByChaincodeRequest.setChaincodeID(chaincodeID);

        // 设置初始化方法和参数
        queryByChaincodeRequest.setFcn(funcName);
        queryByChaincodeRequest.setArgs(args);

        // 调用查询方法
        Collection<ProposalResponse> proposalResponses = channel.queryByChaincode(queryByChaincodeRequest);


        // 合约实例化 用channel 提交提案
        for (ProposalResponse proposalRespons : proposalResponses) {
            if (proposalRespons.getStatus().getStatus() != 200) {
                log.info("提案返回报错:" + proposalRespons.getMessage());
            } else {
                log.info("调用:{} 链码方法成功返回数据:{}", funcName,proposalRespons.getProposalResponse().getPayload());
                return proposalRespons;
            }
        }

        // 如果没有成功提案
        return null;
    }

编写测试方法

@Test
    void testQueryChaincode() throws Exception {
        log.info("开始调用查询链码方法");
//        {"identity":"110115","mobile":"18910012222","name":"zhangsan"}
        String[] args = {"110115"};
        ProposalResponse proposalResponse = channelService.queryChaincode(TransactionRequest.Type.GO_LANG,
                channelProperties.getChannelName(),
                "basicinfo",
                Lists.newArrayList(fabricClient.getOrg1Peer0()),
                "query",
                args
        );

        byte[] chaincodeActionResponsePayload = proposalResponse.getChaincodeActionResponsePayload();
        log.info("完成查询链码:"+ new String(chaincodeActionResponsePayload,"UTF-8") );
    }

测试执行输出

完成查询链码:{"identity":"110115","mobile":"18910012222","name":"zhangsan-5.0"}

说明已经成功从 peer 上查询到了数据