Published on

fabric1.4.x java-sdk使用

Authors

参考链接

之前没写过java,第一用java做项目。 本文主要参考以下链接 https://www.cnblogs.com/cbkj-xd/p/12112311.html https://www.jianshu.com/p/bfb081a96337 http://blog.hubwiz.com/2019/04/23/fabric-sdk-java-hello-world/ https://blog.csdn.net/qq_27348837/article/details/95489720 https://developer.ibm.com/tutorials/hyperledger-fabric-java-sdk-for-tls-enabled-fabric-network/

sdk-github 可以看看里面的例子,不过封装的好深,我对java也不是很熟,看起来有点费劲。里面很多都是写在配置里,或者在一些类里封装的。


代码

先上代码 参考上面的帖子,需要要实现一个User的类,来实例化一个user对象。

  • LocalUser
public class LocalUser implements User {
    private boolean TLS_ENABLE = true;
    private String name;
    private String mspID;
    private Enrollment enrollment;

    LocalUser(String name, String mspID){
        this.name = name;
        this.mspID = mspID;
    }

    LocalUser(String name, String mspID, String keyFile, String certFile) throws Exception{
        this(name, mspID);
        this.enrollment = loadFromPemFile(keyFile, certFile);

        if (TLS_ENABLE == true) {


        }
    }

    private Enrollment loadFromPemFile(String keyFile,String certFile) throws Exception{
        byte[] sk = Files.readAllBytes(Paths.get(keyFile));     //载入私钥
        byte[] certPem = Files.readAllBytes(Paths.get(certFile));   //载入证书PEM文本
        CryptoPrimitives suite = new CryptoPrimitives();            //载入密码学套件
        PrivateKey privateKey = suite.bytesToPrivateKey(sk);    //将PEM文本转换为私钥对象
        return new X509Enrollment(privateKey,new String(certPem));  //创建并返回X509Enrollment对象
    }

    @Override
    public String getName(){return name;}

    @Override
    public String getMspId(){return mspID;}

    @Override public Enrollment getEnrollment() { return enrollment; }
    @Override public String getAccount() { return null; }
    @Override public String getAffiliation() { return null; }
    @Override public Set<String> getRoles() {return null;}

    static String getPEMStringFromPrivateKey(PrivateKey privateKey) throws IOException {
        StringWriter pemStrWriter = new StringWriter();
        PEMWriter pemWriter = new PEMWriter(pemStrWriter);

        pemWriter.writeObject(privateKey);

        pemWriter.close();

        return pemStrWriter.toString();
    }
}
  • 我把关键逻辑都封装在 ChainCode.java了
public class ChainCode {




    private static final String Crypto_Config_Path = "D:\\prj\\crypto-config\\";

    private static final String CA_Pemfile = Crypto_Config_Path+"peerOrganizations\\org1.example.com\\ca\\ca.org1.example.com-cert.pem";

    private String adminKeyFile = Crypto_Config_Path+"peerOrganizations\\" +
            "org1.example.com\\users\\Admin@org1.example.com\\msp\\keystore\\d1bc2f6f9dd1fdc6a90c32f203983870e7189131ca90493281f97bfe87ddcaa8_sk";
    private String adminCertFile = Crypto_Config_Path+"peerOrganizations\\org1.example.com\\users\\Admin@org1.example.com\\" +
            "msp\\signcerts\\Admin@org1.example.com-cert.pem";

    private HFClient client;
    private Channel channel;

    HFClient InitClient() throws  Exception{
        this.client = HFClient.createNewInstance();
        client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
        client.setUserContext(new LocalUser("admin", "Org1MSP", adminKeyFile, adminCertFile));



        return client;
    }


    void CreateChannelIns() throws  Exception{
        this.channel = client.newChannel("contractchannel");

        Properties proper;

        proper = loadTLSFile("peerOrganizations\\org1.example.com\\peers\\peer0.org1.example.com\\msp\\tlscacerts\\tlsca.org1.example.com-cert.pem", "peer0.org1.example.com");
        Peer peer = client.newPeer("peer0.org1.example.com","grpcs://192.169.0.86:7051", proper);
        channel.addPeer(peer);

        proper = loadTLSFile("ordererOrganizations\\example.com\\orderers\\orderer1.example.com\\msp\\tlscacerts\\tlsca.example.com-cert.pem", "orderer1.example.com");
        Orderer orderer = client.newOrderer("orderer1.example.com","grpcs://192.169.0.86:7050", proper);
        channel.addOrderer(orderer);
        channel.initialize();
    }

    /**
     * 为Fabric网络中节点配置TLS根证书
     *
     * @param rootTLSCert 根证书路径
     * @param hostName    节点域名
     * @return
     * @throws IOException
     */
    private static Properties loadTLSFile(String rootTLSCert, String hostName) throws IOException {
        Properties properties = new Properties();
        properties.put("pemBytes", Files.readAllBytes(Paths.get( Crypto_Config_Path + rootTLSCert)));
        properties.setProperty("sslProvider", "openSSL");
        properties.setProperty("negotiationType", "TLS");
        properties.setProperty("trustServerCertificate", "true");
        properties.setProperty("hostnameOverride", hostName);
        return properties;
    }

    void Query() throws Exception{
        QueryByChaincodeRequest req = this.client.newQueryProposalRequest();
        ChaincodeID cid = ChaincodeID.newBuilder().setName("contract").build();
        req.setChaincodeID(cid);
        req.setFcn("query");
        req.setArgs("a");
//        req.setChaincodeEndorsementPolicy();
        ProposalResponse[] rsp = this.channel.queryByChaincode(req).toArray(new ProposalResponse[0]);
        System.out.format("rsp message => %s\n",rsp[0].getProposalResponse().getResponse().getPayload().toStringUtf8());
    }

}
public class App 
{
    public static void main( String[] args ) {
        ChainCode c = new ChainCode();

        try {
            c.InitClient();
            c.CreateChannelIns();
            c.Query();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

注意事项

网络环境

fabric 1.4.2 java-fabric-sdk 2.0.0 release 未开启fabric-ca服务 开启TLS

踩坑
HFClient

因为开启了tls,我刚开始看javasdk里面的ene2endit这个示例,以为要用HFCAClient这个类初始化,后面又搜了一圈,发现不用。第一个坑点。只需要HFClient

User证书

初始化User的时候,传入Admin的私钥,和证书pem。为什么要admin的?这个应该跟policy配置有关,在configtx.yaml中,具体我还没研究过。回头再看

开启TLS必须配置 peer和orderer证书
这么多证书看着有点乱。我还没完全理解各个证书的关系和作用。 大概区分一下 msp是用来鉴权的,直白的说就是确定你是不是你,主要用来确认身份用的。 tls是加密通讯的证书,跟区块链的身份鉴权是两个东西,别混淆了(我暂时是这么理解的)。 需注意tls/ca.crt 跟 msp/tlscacerts/tlsca.org1.xxx.com-cert.pem 内容是一样的。 用于tls通讯的根证书。所以在传入根证书的时候,两个任意一个都可以。
properties.put("pemBytes", Files.readAllBytes(Paths.get( Crypto_Config_Path + rootTLSCert)));
properties.put("pemFile", Paths.get( Crypto_Config_Path + rootTLSCert));

上面的代码,二选一都可以,不一定要传入字符串。

报错处理
org.hyperledger.fabric.sdk.exception.ProposalException: 
org.hyperledger.fabric.sdk.exception.TransactionException: 
org.hyperledger.fabric.sdk.exception.ProposalException:
 getConfigBlock for channel contractchannel failed with peer peer0.org1.example.com.  Status FAILURE, details:
 Channel Channel{id: 1, name: contractchannel} Sending proposal with transaction: 
d795243d5dc706d8d7b2baafe7cdcf66343cd83d93aeb23c3363124724ea54de to Peer{ id: 2, name: peer0.org1.example.com, channelName:
 contractchannel, url: grpc://192.169.0.86:7051} failed because of:
 gRPC failure=Status{code=INTERNAL, description=http2 exception, cause=io.netty.handler.codec.http2.Http2Exception:
 First received frame was not SETTINGS. Hex dump for first 5 bytes: 1503010002

百度谷歌了一圈,确认不了是什么问题,但是隐约觉得是tls的配置问题。 查看peer0的日志,发现了问题所在。

 docker logs 23ef8ad2f248 -t --since="2020-04-01" --tail=50

查看peer0最后50条日志。

2020-04-01T03:41:03.038774384Z 2020-04-01 03:41:03.038 UTC [core.comm]
 ServerHandshake ->
 ERRO 10f8 TLS handshake failed with error tls: first record does not look like a TLS handshake
 {"server": "PeerServer", "remote address": "192.169.0.145:62931"}

不是TLS握手,说明tls有问题。 其实是个很小的问题,特别容易忽略,在看别人代码的时候,我就注意到有个地方很奇怪。

Peer peer = client.newPeer("peer0.org1.example","grpcs://192.169.0.86:7051", proper);

注意url,必须写grpcs,而不是grpc! 之前我就注意到,有点例子里面写的是grpc,有的写的是grpcs。加上s,完美解决。