(七)HyperledgerFarbic1.4- Fabric的SDK使用

一、SDK提供的功能

在之前的操作中 無論是創建channel還是 鏈碼 安裝實例化 合約的查詢 調用 在CA中註冊用戶,都是通過命令行的形式去操作的。
sdk提供了一系列的功能,可以通過sdk去實現之前使用命令行操作的效果。
現階段fabric提供的比較穩定的sdk有:Node、java、go
如果是創建一個區塊鏈管理平臺。就更需要SDK來獲取到區塊鏈中的信息。

總之,業務系統想要與fabric工程交互就應該使用sdk。

二、使用java-fabric-sdk

創建一個springboot項目pom中引用對應版本的sdk

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

三、使用sdk創建channel 並將peer加入到channel中

創建channel 需要使用channel配置文件中指定在Channel中的org的Admin用戶。
創建channel需要生成創世區塊,這一步需要有orderer 的配置信息

所以 使用sdk創建channel要準備一下的配置信息

  • channel中任意一個org的 admin用戶信息
  • orderer節點的信息

3.1 啓動first-network

使用fabric-samples 提供的byfn.sh腳本啓動first-network

./byfn.sh uo -s couchdb

啓動完成後會在first-network目錄下生成兩個目錄

crypto-config : 存放了orderer的證書和peer的證書
channel-artifacts:存放了channel的配置和peer錨節點的配置以及channel的創世區塊

3.2 生成channel的配置文件

使用first-network中提供的configtx.yaml配置文件,配置文件中有關於channel的一個配置。

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

按照這個配置文件的描述,將會生成一個SampleConsortium聯盟,聯盟中包含Org1和Org2兩個組織
使用configtxgen工具 生成channel的配置文件

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

以上命令中的幾個參數

  -profile 使用配置文件中Profiles下的那個配置項,這裏用到的Profiles.TwoOrgsChannel
  -outputCreateChannelTx 執行最終生成的channel的tx配置文件的位置
  -channelID 使用這個configtx的channel的channelId
  -configPath 指定包含configtx.yaml配置文件的目錄(如果命令執行的目錄就包含configtx.yaml則不需要使用該參數)

執行完命令後在指定的目錄下會出現mychannel1.tx配置文件。

3.3 創建一個springboot項目

通過maven引入fabric-sdk-java

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

將上一步生成的channel1.tx和聯盟中的證書都複製到springboot的resources目錄下
在這裏插入圖片描述
爲了方便從resources目錄下讀取文件編寫一個utils類

import org.springframework.core.io.ClassPathResource;

import java.io.File;

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

3.4 修改application.properties文件

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

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

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

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

編寫配置文件對應的配置類
在這裏插入圖片描述

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

在啓動類上聲明使用的配置類


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

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 完整的目錄結構

在這裏插入圖片描述

3.9 編寫測試類 測試是否可以成功創建channel並將節點org1Peer0加入到channel中

@Slf4j
@SpringBootTest
class JavaFabricSdkApplicationTests {


    @Autowired
    private FabricChannelService channelService;

    @Autowired
    private FabricClient fabricClient;

    @Test
    void contextLoads() throws Exception {

        log.info("開始創建channel");
        Channel channel = channelService.createChannel();

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

        log.info("完成創建channel並將org1peer0加入到channel中");
    }

}

成功執行完畢後 ,如果沒有報錯 到docker容器中驗證org1 peer0 節點是否加入到了channel中

# 進入到cli容器中
docker exec -it cli bash
# cli默認的用戶身份就是 org1 peer0 admin用戶所以可以直接查看當前節點加入的channel
peer channel list
# 輸出 說明 成功的通過java sdk向fabric網絡中添加了channel和並將peer加入到了channel中
root@dbe359f6631b:/opt/gopath/src/github.com/hyperledger/fabric/peer# peer channel list
2020-04-01 07:28:46.769 UTC [channelCmd] InitCmdFactory -> INFO 001 Endorser and orderer connections initialized
Channels peers has joined: 
mychannel
mychannel1

四、使用java sdk 安裝鏈碼到peer節點中

4.1 在FabricChannelService中添加安裝鏈碼的方法

 /**
     * 安裝鏈碼
     *
     * 注意:目錄 {chaincodeLoaction}/src/{chaincodePath}/
     *
     * 在當前項目中 就是
     * chaincodeLoaction = chaincode
     * chaincodePath = basic_info
     *
     * @param type          鏈碼語言GO JAVA NODE
     * @param chaincodeName 鏈碼名稱
     * @param version       鏈碼版本
     * @param chaincodeLoaction 鏈碼路徑
     * @param chaincodePath 鏈碼路徑
     * @param peers         要安裝到哪些節點上(這些節點必須在同一個org內)
     * @throws Exception
     */
    public Collection<ProposalResponse> installChaincode(TransactionRequest.Type type, String chaincodeName, String version,String chaincodeLoaction ,String chaincodePath, List<Peer> peers) throws Exception {
        // 初始化Install對象
        InstallProposalRequest installProposalRequest = fabricClient.getHfClient().newInstallProposalRequest();
        // 生成chaincodeId
        ChaincodeID chaincodeID = ChaincodeID.newBuilder().setName(chaincodeName).setVersion(version).build();
        // 設置鏈碼語言類型
        installProposalRequest.setChaincodeLanguage(type);
        // 設置chaincodeId
        installProposalRequest.setChaincodeID(chaincodeID);
        // 指定鏈碼源文件
        installProposalRequest.setChaincodeSourceLocation(ClasspathFileUtils.getFileFromSpringBootClassPath(chaincodeLoaction));
        // 設置chanincodePata
        installProposalRequest.setChaincodePath(chaincodePath);
        return fabricClient.getHfClient().sendInstallProposal(installProposalRequest, peers);
    }

編寫測試用例

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

測試完成後在容器中查看Org1Peer0是否已經安裝了鏈碼

# 進入容器
docker exec -it cli bash

# 執行查看鏈碼安裝列表命令
peer chaincode list --installed

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

五、使用java sdk實例化鏈碼

在service中添加實例化鏈碼的方法,在實例化鏈碼時需要制定背書策略。背書策略一般使用yaml文件來描述

# 背書策略描述文件
identities: # 指定參與背書的角色身份 因爲代碼中只是將鏈碼安裝實例化到了org1的peer0節點,所以先只寫user1
  user1: {"role":{"name":"member","mspId":"Org1MSP"}}
#  user2: {"role":{"name":"member","mspId":"Org2MSP"}}
policy: # 具體策略
  1-of: # 以下聲明中的一個 就是 user1 背書即可
    - signed-by: "user1"
#    - signed-by: "user2"
#  2-of: # 以下聲明中的兩個 就是 user1 user2 都需要背書即可
#    - signed-by: "user1"
#    - signed-by: "user2"
#  1-of: # 以下聲明中的任意一個 就是 user1 user2 其中一個背書即可
#    - signed-by: "user1"
#    - signed-by: "user2"

把文件複製到resources目錄下
在這裏插入圖片描述

  /**
     * 實例化鏈碼
     *
     * @param type          鏈碼語言類型
     * @param channelName   channel名稱
     * @param chaincodeName 鏈碼名稱
     * @param version       版本
     * @param orderer       orderer節點信息
     * @param peer          要安裝到那個peer
     * @param funcName      初始化方法名 如果沒有可以填寫任意字符串
     * @param args          初始化方法傳參 如果沒有 也要傳個 String[] args = {""}
     * @throws Exception
     */
    public void instantiateChaincode(TransactionRequest.Type type, String channelName, String chaincodeName, String version, Orderer orderer, Peer peer, String funcName, String[] args) throws Exception {

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

        //構造提案
        InstantiateProposalRequest instantiateProposalRequest = fabricClient.getHfClient().newInstantiationProposalRequest();
        // 設置語言
        instantiateProposalRequest.setChaincodeLanguage(type);

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

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

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

        // 提交 數據到鏈上
        channel.sendTransaction(proposalResponses);

    }

添加測試方法

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

測試通過後檢查對應節點上已經實例化的鏈碼

# 進入cli容器
docker exec -it cli bash
# 查看當前節點已經實例化的鏈碼
peer chaincode list --instantiated -C mychannel1
# 輸出channel中已經實例化的鏈碼
Get instantiated chaincodes on channel mychannel1:
Name: basicinfo, Version: 1.0, Path: basic_info, Escc: escc, Vscc: vscc

六、更新鏈碼

在service中增加更新鏈碼的方法

/**
     * 更新升級鏈碼
     *
     * @param type          鏈碼語言類型
     * @param channelName   channel名稱
     * @param chaincodeName 鏈碼名稱
     * @param version       版本
     * @param orderer       orderer節點信息
     * @param peer          要安裝到那個peer
     * @param funcName      初始化方法名 如果沒有可以填寫任意字符串
     * @param args          初始化方法傳參 如果沒有 也要傳個 String[] args = {""}
     * @throws Exception
     */
    public void upgradeChaincode(TransactionRequest.Type type, String channelName, String chaincodeName, String version, Orderer orderer, Peer peer, String funcName, String[] args) throws Exception {

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

        //構造提案
        UpgradeProposalRequest upgradeProposalRequest = fabricClient.getHfClient().newUpgradeProposalRequest();
        // 設置語言
        upgradeProposalRequest.setChaincodeLanguage(type);

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

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


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

        // 提交 數據到鏈上
        channel.sendTransaction(proposalResponses);
    }

編寫測試方法
要想更新鏈碼到2.0版本 就需要先將2.0版的鏈碼安裝到peer上。

 @Test
    void testUpgradeChaincode() throws Exception {
        log.info("開始更新鏈碼");

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

        System.out.println("end");

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

執行完成後 查看channel中peer已經實例化的鏈碼

# 進入cli容器
docker exec -it cli bash
# 查看已經實例化的鏈碼
peer chaincode list --instantiated -C mychannel1
# 輸出
Get instantiated chaincodes on channel mychannel1:
Name: basicinfo, Version: 2.0, Path: basic_info, Escc: escc, Vscc: vscc

可以看到basicinfo鏈碼的version已經是2.0了

七、 調用鏈碼-invoke

在service中增加調用鏈碼的方法

 /**
     * 調用鏈碼invoke鏈碼
     *
     * @param type          鏈碼語言類型
     * @param channelName   channel名稱
     * @param chaincodeName 鏈碼名稱
     * @param version       版本
     * @param orderer       orderer節點信息
     * @param peers          需要哪些peer背書
     * @param funcName      調用方法名
     * @param args          方法傳參
     * @throws Exception
     */
    public CompletableFuture<BlockEvent.TransactionEvent> invokeChaincode(TransactionRequest.Type type, String channelName, String chaincodeName, String version, Orderer orderer,List<Peer>  peers, String funcName, String[] args) throws Exception {

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

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

        // 發送提案 peers要與背書規則相匹配,需要哪個peer背書 就將那個peer添加進去
        Collection<ProposalResponse> proposalResponses = channel.sendTransactionProposal(proposalRequest);
        // 合約實例化 用channel 提交提案
        for (ProposalResponse proposalRespons : proposalResponses) {
            if (proposalRespons.getStatus().getStatus() != 200) {
                throw new Exception("提案返回報錯:" + proposalRespons.getMessage());
            } else {
                log.info("調用:{} 鏈碼方法成功",funcName);
            }
        }

        // 發送數據到鏈上
        return channel.sendTransaction(proposalResponses);

    }

編寫測試方法

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

完成測試後 到cli容器中驗證下

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

# 查詢保存的數據
peer chaincode query -C mychannel4 -n basicinfo  -c '{"Args":["query","110115"]}'
# 輸出
{"identity":"110115","mobile":"18910012222","name":"zhangsan-5.0"}

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

在service中添加查詢方法


    /**
     * 調用鏈碼query鏈碼
     *
     * @param channelName   channel名稱
     * @param chaincodeName 鏈碼名稱
     * @param funcName      調用方法名
     * @param args          方法傳參
     * @throws Exception
     */
    public ProposalResponse queryChaincode(TransactionRequest.Type type,String channelName, String chaincodeName, List<Peer> peers, String funcName, String[] args) throws Exception {
        // 獲取到channel
        Channel channel = getChannel(channelName);
        // 多個peer 可以防止單點故障
        for (Peer peer : peers) {
            channel.addPeer(peer);
        }
        // 初始化channel
        channel.initialize();


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

        // 設置初始化方法和參數
        queryByChaincodeRequest.setFcn(funcName);
        queryByChaincodeRequest.setArgs(args);

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


        // 合約實例化 用channel 提交提案
        for (ProposalResponse proposalRespons : proposalResponses) {
            if (proposalRespons.getStatus().getStatus() != 200) {
                log.info("提案返回報錯:" + proposalRespons.getMessage());
            } else {
                log.info("調用:{} 鏈碼方法成功返回數據:{}", funcName,proposalRespons.getProposalResponse().getPayload());
                return proposalRespons;
            }
        }

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

編寫測試方法

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

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

測試執行輸出

完成查詢鏈碼:{"identity":"110115","mobile":"18910012222","name":"zhangsan-5.0"}

說明已經成功從peer上查詢到了數據

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章