Fabric End2EndIT

/*
 *  Copyright 2016, 2017 DTCC, Fujitsu Australia Software Technology, IBM - All Rights Reserved.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *    http://www.apache.org/licenses/LICENSE-2.0
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package org.hyperledger.fabric.sdkintegration;

import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.nio.file.Paths;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.TimeUnit;

import org.apache.commons.codec.binary.Hex;
import org.hyperledger.fabric.protos.ledger.rwset.kvrwset.KvRwset;
import org.hyperledger.fabric.sdk.BlockEvent;
import org.hyperledger.fabric.sdk.BlockInfo;
import org.hyperledger.fabric.sdk.BlockchainInfo;
import org.hyperledger.fabric.sdk.ChaincodeEndorsementPolicy;
import org.hyperledger.fabric.sdk.ChaincodeID;
import org.hyperledger.fabric.sdk.Channel;
import org.hyperledger.fabric.sdk.ChannelConfiguration;
import org.hyperledger.fabric.sdk.EventHub;
import org.hyperledger.fabric.sdk.HFClient;
import org.hyperledger.fabric.sdk.InstallProposalRequest;
import org.hyperledger.fabric.sdk.InstantiateProposalRequest;
import org.hyperledger.fabric.sdk.Orderer;
import org.hyperledger.fabric.sdk.Peer;
import org.hyperledger.fabric.sdk.ProposalResponse;
import org.hyperledger.fabric.sdk.QueryByChaincodeRequest;
import org.hyperledger.fabric.sdk.SDKUtils;
import org.hyperledger.fabric.sdk.TestConfigHelper;
import org.hyperledger.fabric.sdk.TransactionInfo;
import org.hyperledger.fabric.sdk.TransactionProposalRequest;
import org.hyperledger.fabric.sdk.TxReadWriteSetInfo;
import org.hyperledger.fabric.sdk.exception.InvalidArgumentException;
import org.hyperledger.fabric.sdk.exception.InvalidProtocolBufferRuntimeException;
import org.hyperledger.fabric.sdk.exception.ProposalException;
import org.hyperledger.fabric.sdk.exception.TransactionEventException;
import org.hyperledger.fabric.sdk.security.CryptoSuite;
import org.hyperledger.fabric.sdk.testutils.TestConfig;
import org.hyperledger.fabric_ca.sdk.HFCAClient;
import org.hyperledger.fabric_ca.sdk.RegistrationRequest;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import static java.lang.String.format;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.hyperledger.fabric.sdk.BlockInfo.EnvelopeType.TRANSACTION_ENVELOPE;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

/**
 * Test end to end scenario
 */
public class End2endIT {

    private static final TestConfig testConfig = TestConfig.getConfig();
    private static final String TEST_ADMIN_NAME = "admin";
    private static final String TESTUSER_1_NAME = "user1";
    private static final String TEST_FIXTURES_PATH = "src/test/fixture";

    private static final String CHAIN_CODE_NAME = "example_cc_go";
    private static final String CHAIN_CODE_PATH = "github.com/example_cc";
    private static final String CHAIN_CODE_VERSION = "1";

    private static final String FOO_CHANNEL_NAME = "foo";
    private static final String BAR_CHANNEL_NAME = "bar";

    String testTxID = null;  // save the CC invoke TxID and use in queries

    private final TestConfigHelper configHelper = new TestConfigHelper();

    private Collection testSampleOrgs;

    @Before
    public void checkConfig() throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException, MalformedURLException {
        out("\n\n\nRUNNING: End2endIT.\n");
        configHelper.clearConfig();
        configHelper.customizeConfig();

        testSampleOrgs = testConfig.getIntegrationTestsSampleOrgs();
        //Set up hfca for each sample org

        for (SampleOrg sampleOrg : testSampleOrgs) {
            sampleOrg.setCAClient(HFCAClient.createNewInstance(sampleOrg.getCALocation(), sampleOrg.getCAProperties()));
        }
    }

    @After
    public void clearConfig() {
        try {
            configHelper.clearConfig();
        } catch (Exception e) {
        }
    }

    /**
     * 安装链码环境
     * 主要流程:
     *  1、创建通道
     *  2、更新锚点
     *  3、安装链码
     *  4、初始化链码
     *  5、执行链码操作
     * */
    @Test
    public void setup() {

        try {

            ////////////////////////////
            // Setup client

            //Create instance of client.
            //创建一个客户端
            HFClient client = HFClient.createNewInstance();
            //生成并设置加密套件
            client.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());

            // client.setMemberServices(peerOrg1FabricCA);

            ////////////////////////////
            //Set up USERS

            //Persistence is not part of SDK. Sample file store is for demonstration purposes only!
            //   MUST be replaced with more robust application implementation  (Database, LDAP)
            //加载本地临时目录临时生成的客户端测试文件
            File sampleStoreFile = new File(System.getProperty("java.io.tmpdir") + "/HFCSampletest.properties");
            if (sampleStoreFile.exists()) { //For testing start fresh
                sampleStoreFile.delete();
            }
            //通过文件生成配置对象
            final SampleStore sampleStore = new SampleStore(sampleStoreFile);
            //  sampleStoreFile.deleteOnExit();

            //SampleUser can be any implementation that implements org.hyperledger.fabric.sdk.User Interface

            ////////////////////////////
            // get users for all orgs
            //获得所有组织的所有用户
            for (SampleOrg sampleOrg : testSampleOrgs) {

                //通过组织信息获得CA客户端
                HFCAClient ca = sampleOrg.getCAClient();
                //获得组织名称
                final String orgName = sampleOrg.getName();
                //获得组织证书id
                final String mspid = sampleOrg.getMSPID();
                //设置CA客户端证书套件
                ca.setCryptoSuite(CryptoSuite.Factory.getCryptoSuite());
                //获得组织管理员(admin)
                SampleUser admin = sampleStore.getMember(TEST_ADMIN_NAME, orgName);
                //如果未进行登记
                if (!admin.isEnrolled()) {
                    //Preregistered admin only needs to be enrolled with Fabric caClient.
                    //预注册管理员只需要在fabric CA客户端进行登记
                    admin.setEnrollment(ca.enroll(admin.getName(), "adminpw"));
                    admin.setMspId(mspid);
                }
                //设置管理员身份
                sampleOrg.setAdmin(admin); // The admin of this org --

                //获得普通用户
                SampleUser user = sampleStore.getMember(TESTUSER_1_NAME, sampleOrg.getName());
                //如果未注册
                if (!user.isRegistered()) {  // users need to be registered AND enrolled
                    //生成注册请求
                    RegistrationRequest rr = new RegistrationRequest(user.getName(), "org1.department1");
                    //设置用户密码(注册请求发出去后,CA会返回一个密码)
                    user.setEnrollmentSecret(ca.register(rr, admin));
                }
                //如果未登记
                if (!user.isEnrolled()) {
                    //进行登记操作
                    user.setEnrollment(ca.enroll(user.getName(), user.getEnrollmentSecret()));
                    user.setMspId(mspid);
                }
                //组织内添加用户
                sampleOrg.addUser(user); //Remember user belongs to this Org

                //获得示例组织名称
                final String sampleOrgName = sampleOrg.getName();
                //获得组织的命名域
                final String sampleOrgDomainName = sampleOrg.getDomainName();

                // src/test/fixture/sdkintegration/e2e-2Orgs/channel/crypto-config/peerOrganizations/org1.example.com/users/[email protected]/msp/keystore

                //使用证书文件生成用户信息
                SampleUser peerOrgAdmin = sampleStore.getMember(sampleOrgName + "Admin", sampleOrgName, sampleOrg.getMSPID(),
                        findFileSk(Paths.get(testConfig.getTestChannlePath(), "crypto-config/peerOrganizations/",
                                sampleOrgDomainName, format("/users/Admin@%s/msp/keystore", sampleOrgDomainName)).toFile()),
                        Paths.get(testConfig.getTestChannlePath(), "crypto-config/peerOrganizations/", sampleOrgDomainName,
                                format("/users/Admin@%s/msp/signcerts/Admin@%s-cert.pem", sampleOrgDomainName, sampleOrgDomainName)).toFile());

                //设置节点管理员(一个特殊的用户,可以创建通道,加入节点以及安装链码)
                sampleOrg.setPeerAdmin(peerOrgAdmin); //A special user that can create channels, join peers and install chaincode

            }

            ////////////////////////////
            //Construct and run the channels
            //构建及运行通道

            //获得组织配置信息
            SampleOrg sampleOrg = testConfig.getIntegrationTestsSampleOrg("peerOrg1");
            //构建信道(信道名称,客户端【cli】,组织)
            Channel fooChannel = constructChannel(FOO_CHANNEL_NAME, client, sampleOrg);
            //启动信道
            runChannel(client, fooChannel, true, sampleOrg, 0);

            //停止channel(停止客户端,不停止环境)
            fooChannel.shutdown(true); // Force foo channel to shutdown clean up resources.
            out("\n");

            //根据组织名称获取组织对象
            sampleOrg = testConfig.getIntegrationTestsSampleOrg("peerOrg2");
            //构建通道(bar)
            Channel barChannel = constructChannel(BAR_CHANNEL_NAME, client, sampleOrg);
            //启动通道
            runChannel(client, barChannel, true, sampleOrg, 100); //run a newly constructed bar channel with different b value!
            //let bar channel just shutdown so we have both scenarios.

            out("\nTraverse the blocks for chain %s ", barChannel.getName());
            blockWalker(barChannel);
            out("That's all folks!");

        } catch (Exception e) {
            e.printStackTrace();

            fail(e.getMessage());
        }

    }

    //CHECKSTYLE.OFF: Method length is 320 lines (max allowed is 150).
    //启动信道
    void runChannel(HFClient client, Channel channel, boolean installChaincode, SampleOrg sampleOrg, int delta) {

        try {
            //获得信道名称
            final String channelName = channel.getName();
            //检查信道名称是否正确
            boolean isFooChain = FOO_CHANNEL_NAME.equals(channelName);

            out("Running channel %s", channelName);

            //信道设置交易等待时间
            channel.setTransactionWaitTime(testConfig.getTransactionWaitTime());
            //设置部署等待时间
            channel.setDeployWaitTime(testConfig.getDeployWaitTime());

            //获取信道内节点集合
            Collection channelPeers = channel.getPeers();
            //获取信道内排序节点集合
            Collection orderers = channel.getOrderers();
            //声明链码标识
            final ChaincodeID chaincodeID;
            //提案结果(请求结果)
            Collection responses;
            //成功结果集合
            Collection successful = new LinkedList<>();
            //失败结果集合
            Collection failed = new LinkedList<>();

            //生成链码标识对象
            chaincodeID = ChaincodeID.newBuilder().setName(CHAIN_CODE_NAME)
                    .setVersion(CHAIN_CODE_VERSION)
                    .setPath(CHAIN_CODE_PATH).build();

            //如果需要安装链码
            if (installChaincode) {
                ////////////////////////////
                // Install Proposal Request
                // 发送安装请求

                //设置客户端用户内容(当前组织的节点管理员)
                client.setUserContext(sampleOrg.getPeerAdmin());

                out("Creating install proposal");

                //构建安装提案请求
                InstallProposalRequest installProposalRequest = client.newInstallProposalRequest();
                //设置链码标识
                installProposalRequest.setChaincodeID(chaincodeID);

                //如果是foo链码
                if (isFooChain) {
                    // on foo chain install from directory.

                    ////For GO language and serving just a single user, chaincodeSource is mostly likely the users GOPATH
                    //设置链码安装路径(由代码跑的位置来看,可以将链码放在本地进行安装,牛逼)
                    installProposalRequest.setChaincodeSourceLocation(new File(TEST_FIXTURES_PATH + "/sdkintegration/gocc/sample1"));
                } else {
                    // On bar chain install from an input stream.
                    //从输入流安装bar链码
                    installProposalRequest.setChaincodeInputStream(Util.generateTarGzInputStream(
                            (Paths.get(TEST_FIXTURES_PATH, "/sdkintegration/gocc/sample1", "src", CHAIN_CODE_PATH).toFile()),
                            Paths.get("src", CHAIN_CODE_PATH).toString()));

                }
                //设置链码版本
                installProposalRequest.setChaincodeVersion(CHAIN_CODE_VERSION);

                out("Sending install proposal");

                ////////////////////////////
                // only a client from the same org as the peer can issue an install request
                // 只有来自同一个组织节点的客户端才能发起安装请求
                //安装提案数量?
                int numInstallProposal = 0;
                //    Set orgs = orgPeers.keySet();
                //   for (SampleOrg org : testSampleOrgs) {

                //获得组织内节点集合
                Set peersFromOrg = sampleOrg.getPeers();
                //应该发送的天数量
                numInstallProposal = numInstallProposal + peersFromOrg.size();
                //发送安装提案,获得响应集合
                responses = client.sendInstallProposal(installProposalRequest, peersFromOrg);

                //遍历返回结果集合
                for (ProposalResponse response : responses) {
                    //如果提案成功
                    if (response.getStatus() == ProposalResponse.Status.SUCCESS) {
                        //将该结果纳入成功的集合
                        out("Successful install proposal response Txid: %s from peer %s", response.getTransactionID(), response.getPeer().getName());
                        successful.add(response);
                    } else {
                        //加入失败集合
                        failed.add(response);
                    }
                }

                //检查返回结果集是否拥有一致的读写集
                SDKUtils.getProposalConsistencySets(responses);
                //   }
                out("Received %d install proposal responses. Successful+verified: %d . Failed: %d", numInstallProposal, successful.size(), failed.size());

                if (failed.size() > 0) {
                    //如果有失败的节点,取出错误信息,并返回异常:没有足够的背书节点
                    ProposalResponse first = failed.iterator().next();
                    fail("Not enough endorsers for install :" + successful.size() + ".  " + first.getMessage());
                }
            }

            //   client.setUserContext(sampleOrg.getUser(TEST_ADMIN_NAME));
            //  final ChaincodeID chaincodeID = firstInstallProposalResponse.getChaincodeID();
            // Note installing chaincode does not require transaction no need to
            // send to Orderers

            ///////////////
            //// Instantiate chaincode.
            //构建实例化提案请求
            InstantiateProposalRequest instantiateProposalRequest = client.newInstantiationProposalRequest();
            //设置请求等待时间
            instantiateProposalRequest.setProposalWaitTime(testConfig.getProposalWaitTime());
            //设置链码标识
            instantiateProposalRequest.setChaincodeID(chaincodeID);
            //设置实例化方法名称
            instantiateProposalRequest.setFcn("init");
            //设置实例化参数列表
            instantiateProposalRequest.setArgs(new String[] {"a", "500", "b", "" + (200 + delta)});
            Mapbyte[]> tm = new HashMap<>();
            tm.put("HyperLedgerFabric", "InstantiateProposalRequest:JavaSDK".getBytes(UTF_8));
            tm.put("method", "InstantiateProposalRequest".getBytes(UTF_8));
            //设置提案请求参数
            instantiateProposalRequest.setTransientMap(tm);

            /*
              policy OR(Org1MSP.member, Org2MSP.member) meaning 1 signature from someone in either Org1 or Org2
              See README.md Chaincode endorsement policies section for more details.
              指定背书策略:Org1或者Org2其中一个进行背书签名即可
            */
            //生成背书策略
            ChaincodeEndorsementPolicy chaincodeEndorsementPolicy = new ChaincodeEndorsementPolicy();
            //从配置文件中加载背书策略(这里的文件貌似是通用的,意思是不是一个节点上的链码背书策略都是相同的?)
            chaincodeEndorsementPolicy.fromYamlFile(new File(TEST_FIXTURES_PATH + "/sdkintegration/chaincodeendorsementpolicy.yaml"));
            //在实例化请求中加载该背书策略
            instantiateProposalRequest.setChaincodeEndorsementPolicy(chaincodeEndorsementPolicy);

            out("Sending instantiateProposalRequest to all peers with arguments: a and b set to 100 and %s respectively", "" + (200 + delta));
            //清除之前的返回结果集存储
            successful.clear();
            failed.clear();

            //如果链码是foo
            if (isFooChain) {  //Send responses both ways with specifying peers and by using those on the channel.
                //发送实例化提案(向所有信道内的节点)
                responses = channel.sendInstantiationProposal(instantiateProposalRequest, channel.getPeers());
            } else {
                //发送实例化提案(信道内所有节点)
                responses = channel.sendInstantiationProposal(instantiateProposalRequest);

            }

            //遍历响应结果集(将响应结果分类存储)
            for (ProposalResponse response : responses) {
                if (response.isVerified() && response.getStatus() == ProposalResponse.Status.SUCCESS) {
                    successful.add(response);
                    out("Succesful instantiate proposal response Txid: %s from peer %s", response.getTransactionID(), response.getPeer().getName());
                } else {
                    failed.add(response);
                }
            }
            out("Received %d instantiate proposal responses. Successful+verified: %d . Failed: %d", responses.size(), successful.size(), failed.size());
            //如果有失败提案,则返回失败信息,以及帮助提示
            if (failed.size() > 0) {
                ProposalResponse first = failed.iterator().next();
                fail("Not enough endorsers for instantiate :" + successful.size() + "endorser failed with " + first.getMessage() + ". Was verified:" + first.isVerified());
            }

            //如果以上都成功了,则继续进行一下后续操作

            ///////////////
            /// Send instantiate transaction to orderer
            out("Sending instantiateTransaction to orderer with a and b set to 100 and %s respectively", "" + (200 + delta));
            //向排序节点发送交易信息
            channel.sendTransaction(successful, orderers).thenApply(transactionEvent -> {

                waitOnFabric(0);

                assertTrue(transactionEvent.isValid()); // must be valid to be here.
                out("Finished instantiate transaction with transaction id %s", transactionEvent.getTransactionID());

                try {
                    successful.clear();
                    failed.clear();

                    client.setUserContext(sampleOrg.getUser(TESTUSER_1_NAME));

                    ///////////////
                    /// Send transaction proposal to all peers
                    //发送交易提案至所有节点

                    //生成交易提案
                    TransactionProposalRequest transactionProposalRequest = client.newTransactionProposalRequest();
                    //设置链码标识
                    transactionProposalRequest.setChaincodeID(chaincodeID);
                    //设置调用链码方法
                    transactionProposalRequest.setFcn("invoke");
                    //设置提案等待时间
                    transactionProposalRequest.setProposalWaitTime(testConfig.getProposalWaitTime());
                    //设置参数
                    transactionProposalRequest.setArgs(new String[] {"move", "a", "b", "100"});

                    Mapbyte[]> tm2 = new HashMap<>();
                    tm2.put("HyperLedgerFabric", "TransactionProposalRequest:JavaSDK".getBytes(UTF_8));
                    tm2.put("method", "TransactionProposalRequest".getBytes(UTF_8));
                    tm2.put("result", ":)".getBytes(UTF_8));  /// This should be returned see chaincode.
                    //设置交易参数
                    transactionProposalRequest.setTransientMap(tm2);

                    out("sending transactionProposal to all peers with arguments: move(a,b,100)");

                    //发送交易提案,并获得响应集合
                    Collection transactionPropResp = channel.sendTransactionProposal(transactionProposalRequest, channel.getPeers());
                    //遍历响应并归类
                    for (ProposalResponse response : transactionPropResp) {
                        if (response.getStatus() == ProposalResponse.Status.SUCCESS) {
                            out("Successful transaction proposal response Txid: %s from peer %s", response.getTransactionID(), response.getPeer().getName());
                            successful.add(response);
                        } else {
                            failed.add(response);
                        }
                    }

                    // Check that all the proposals are consistent with each other. We should have only one set
                    // where all the proposals above are consistent.
                    //从返回结果中提取拥有一致提案结果的集合(校验所有背书节点的背书结果是否一致)
                    Collection> proposalConsistencySets = SDKUtils.getProposalConsistencySets(transactionPropResp);
                    if (proposalConsistencySets.size() != 1) {
                        //提案结果只能有一个
                        fail(format("Expected only one set of consistent proposal responses but got %d", proposalConsistencySets.size()));
                    }


                    out("Received %d transaction proposal responses. Successful+verified: %d . Failed: %d",
                            transactionPropResp.size(), successful.size(), failed.size());
                    if (failed.size() > 0) {
                        //如果有背书失败情况,则退出(3f+1)
                        ProposalResponse firstTransactionProposalResponse = failed.iterator().next();
                        fail("Not enough endorsers for invoke(move a,b,100):" + failed.size() + " endorser error: " +
                                firstTransactionProposalResponse.getMessage() +
                                ". Was verified: " + firstTransactionProposalResponse.isVerified());
                    }
                    out("Successfully received transaction proposal responses.");

                    //提取提案结果的第一个解(此处所有解应该相同,如果不同,在前面的校验已经处理过了,不会走到这一步)
                    ProposalResponse resp = transactionPropResp.iterator().next();
                    //提取提案结果中的返回数据
                    byte[] x = resp.getChaincodeActionResponsePayload(); // This is the data returned by the chaincode.
                    //将结果解析成字符串
                    String resultAsString = null;
                    if (x != null) {
                        resultAsString = new String(x, "UTF-8");
                    }
                    assertEquals(":)", resultAsString);

                    assertEquals(200, resp.getChaincodeActionResponseStatus()); //Chaincode's status.

                    //获取事务读写集信息
                    TxReadWriteSetInfo readWriteSetInfo = resp.getChaincodeActionResponseReadWriteSetInfo();
                    //See blockwalker below how to transverse this
                    //判断非空
                    assertNotNull(readWriteSetInfo);
                    assertTrue(readWriteSetInfo.getNsRwsetCount() > 0);

                    //从返回信息中获取链码标识
                    ChaincodeID cid = resp.getChaincodeID();
                    assertNotNull(cid);
                    assertEquals(CHAIN_CODE_PATH, cid.getPath());
                    assertEquals(CHAIN_CODE_NAME, cid.getName());
                    assertEquals(CHAIN_CODE_VERSION, cid.getVersion());


                    ////////////////////////////
                    // Send Transaction Transaction to orderer
                    out("Sending chaincode transaction(move a,b,100) to orderer.");
                    //向排序节点发送交易请求
                    return channel.sendTransaction(successful).get(testConfig.getTransactionWaitTime(), TimeUnit.SECONDS);

                } catch (Exception e) {
                    out("Caught an exception while invoking chaincode");
                    e.printStackTrace();
                    fail("Failed invoking chaincode with error : " + e.getMessage());
                }

                return null;

            }).thenApply(transactionEvent -> {
                try {

                    waitOnFabric(0);

                    assertTrue(transactionEvent.isValid()); // must be valid to be here.
                    out("Finished transaction with transaction id %s", transactionEvent.getTransactionID());
                    testTxID = transactionEvent.getTransactionID(); // used in the channel queries later

                    ////////////////////////////
                    // Send Query Proposal to all peers
                    //发送查询请求至所有节点
                    String expect = "" + (300 + delta);
                    out("Now query chaincode for the value of b.");
                    //开始构建请求对象
                    QueryByChaincodeRequest queryByChaincodeRequest = client.newQueryProposalRequest();
                    queryByChaincodeRequest.setArgs(new String[] {"query", "b"});
                    queryByChaincodeRequest.setFcn("invoke");
                    queryByChaincodeRequest.setChaincodeID(chaincodeID);

                    Mapbyte[]> tm2 = new HashMap<>();
                    tm2.put("HyperLedgerFabric", "QueryByChaincodeRequest:JavaSDK".getBytes(UTF_8));
                    tm2.put("method", "QueryByChaincodeRequest".getBytes(UTF_8));
                    queryByChaincodeRequest.setTransientMap(tm2);

                    Collection queryProposals = channel.queryByChaincode(queryByChaincodeRequest, channel.getPeers());
                    for (ProposalResponse proposalResponse : queryProposals) {
                        if (!proposalResponse.isVerified() || proposalResponse.getStatus() != ProposalResponse.Status.SUCCESS) {
                            fail("Failed query proposal from peer " + proposalResponse.getPeer().getName() + " status: " + proposalResponse.getStatus() +
                                    ". Messages: " + proposalResponse.getMessage()
                                    + ". Was verified : " + proposalResponse.isVerified());
                        } else {
                            String payload = proposalResponse.getProposalResponse().getResponse().getPayload().toStringUtf8();
                            out("Query payload of b from peer %s returned %s", proposalResponse.getPeer().getName(), payload);
                            assertEquals(payload, expect);
                        }
                    }

                    return null;
                } catch (Exception e) {
                    out("Caught exception while running query");
                    e.printStackTrace();
                    fail("Failed during chaincode query with error : " + e.getMessage());
                }

                return null;
            }).exceptionally(e -> {
                if (e instanceof TransactionEventException) {
                    BlockEvent.TransactionEvent te = ((TransactionEventException) e).getTransactionEvent();
                    if (te != null) {
                        fail(format("Transaction with txid %s failed. %s", te.getTransactionID(), e.getMessage()));
                    }
                }
                fail(format("Test failed with %s exception %s", e.getClass().getName(), e.getMessage()));

                return null;
            }).get(testConfig.getTransactionWaitTime(), TimeUnit.SECONDS);

            // Channel queries

            // We can only send channel queries to peers that are in the same org as the SDK user context
            // Get the peers from the current org being used and pick one randomly to send the queries to.
            //获得组织内所有的已经使用的节点
            Set peerSet = sampleOrg.getPeers();
            //  Peer queryPeer = peerSet.iterator().next();
            //   out("Using peer %s for channel queries", queryPeer.getName());

            //获得链所在区块信息
            BlockchainInfo channelInfo = channel.queryBlockchainInfo();
            out("Channel info for : " + channelName);
            out("Channel height: " + channelInfo.getHeight());
            //获得当前块的hash
            String chainCurrentHash = Hex.encodeHexString(channelInfo.getCurrentBlockHash());
            //获得上一个块的hash
            String chainPreviousHash = Hex.encodeHexString(channelInfo.getPreviousBlockHash());
            out("Chain current block hash: " + chainCurrentHash);
            out("Chainl previous block hash: " + chainPreviousHash);

            // Query by block number. Should return latest block, i.e. block number 2
            //获得最后一个快信息(根据区块编号,区块高度-1)
            BlockInfo returnedBlock = channel.queryBlockByNumber(channelInfo.getHeight() - 1);
            //获得上一个块的hash
            String previousHash = Hex.encodeHexString(returnedBlock.getPreviousHash());

            out("queryBlockByNumber returned correct block with blockNumber " + returnedBlock.getBlockNumber()
                    + " \n previous_hash " + previousHash);
            //判断链上登记的最后区块与查询的返回区块编号是否一致
            assertEquals(channelInfo.getHeight() - 1, returnedBlock.getBlockNumber());
            //判断两个查询的区块是否相等
            assertEquals(chainPreviousHash, previousHash);

            // Query by block hash. Using latest block's previous hash so should return block number 1
            //
            byte[] hashQuery = returnedBlock.getPreviousHash();
            //根据区块hash,查询返回区块信息
            returnedBlock = channel.queryBlockByHash(hashQuery);
            out("queryBlockByHash returned block with blockNumber " + returnedBlock.getBlockNumber());
            assertEquals(channelInfo.getHeight() - 2, returnedBlock.getBlockNumber());

            // Query block by TxID. Since it's the last TxID, should be block 2
            //通过交易ID,查询区块信息
            returnedBlock = channel.queryBlockByTransactionID(testTxID);
            out("queryBlockByTxID returned block with blockNumber " + returnedBlock.getBlockNumber());
            assertEquals(channelInfo.getHeight() - 1, returnedBlock.getBlockNumber());

            // query transaction by ID
            //根据交易ID查询交易信息
            TransactionInfo txInfo = channel.queryTransactionByID(testTxID);
            out("QueryTransactionByID returned TransactionInfo: txID " + txInfo.getTransactionID()
                    + "\n     validation code " + txInfo.getValidationCode().getNumber());

            out("Running for Channel %s done", channelName);

        } catch (Exception e) {
            out("Caught an exception running channel %s", channel.getName());
            e.printStackTrace();
            fail("Test failed with error : " + e.getMessage());
        }
    }
    //CHECKSTYLE.ON: Method length is 320 lines (max allowed is 150).
    //构建通道(通道名称,客户端,组织配置信息)
    private Channel constructChannel(String name, HFClient client, SampleOrg sampleOrg) throws Exception {
        ////////////////////////////
        //Construct the channel
        //

        out("Constructing channel %s", name);

        //Only peer Admin org
        //只支持节点管理员操作
        client.setUserContext(sampleOrg.getPeerAdmin());

        Collection orderers = new LinkedList<>();
        //循环读取排序服务器
        for (String orderName : sampleOrg.getOrdererNames()) {
            //根据排序节点名称,获得其配置信息
            Properties ordererProperties = testConfig.getOrdererProperties(orderName);

            //example of setting keepAlive to avoid timeouts on inactive http2 connections.
            // Under 5 minutes would require changes to server side to accept faster ping rates.
            //设置保持连接时长:5分钟
            ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveTime", new Object[] {5L, TimeUnit.MINUTES});
            //设置连接超时时长:8秒钟
            ordererProperties.put("grpc.NettyChannelBuilderOption.keepAliveTimeout", new Object[] {8L, TimeUnit.SECONDS});

            //在新建的集合中增加排序节点
            orderers.add(client.newOrderer(orderName, sampleOrg.getOrdererLocation(orderName),
                    ordererProperties));
        }

        //Just pick the first orderer in the list to create the channel.
        //选择第一个排序节点进行创建通道
        Orderer anOrderer = orderers.iterator().next();
        //从排序节点集合中移除已经在使用的节点????
        orderers.remove(anOrderer);

        //通过读取channel.tx文件,生成信道配置对象
        ChannelConfiguration channelConfiguration = new ChannelConfiguration(new File(TEST_FIXTURES_PATH + "/sdkintegration/e2e-2Orgs/channel/" + name + ".tx"));

        //Create channel that has only one signer that is this orgs peer admin. If channel creation policy needed more signature they would need to be added too.
        //创建信道对象,只有一个签名,就是组织节点管理员。如果创建信道策略需要更多的签名,那么他们必须添加
        Channel newChannel = client.newChannel(name, anOrderer, channelConfiguration, client.getChannelConfigurationSignature(channelConfiguration, sampleOrg.getPeerAdmin()));

        out("Created channel %s", name);

        //获得组织内节点信息
        for (String peerName : sampleOrg.getPeerNames()) {
            //获取节点位置
            String peerLocation = sampleOrg.getPeerLocation(peerName);

            //获得节点属性
            Properties peerProperties = testConfig.getPeerProperties(peerName); //test properties for peer.. if any.
            if (peerProperties == null) {
                peerProperties = new Properties();
            }
            //Example of setting specific options on grpc's NettyChannelBuilder
            //设置节点最大消息传输量:9000000,9M?
            peerProperties.put("grpc.NettyChannelBuilderOption.maxInboundMessageSize", 9000000);

            //通过客户端生成节点对象,保持节点连接??
            Peer peer = client.newPeer(peerName, peerLocation, peerProperties);
            //在新的信道中加入生成的节点
            newChannel.joinPeer(peer);
            out("Peer %s joined channel %s", peerName, name);
            //将新生成的节点对象加入组织配置信息中
            sampleOrg.addPeer(peer);
        }

        //加载剩下的排序节点(这里已经排除了已经占用了的排序节点)
        for (Orderer orderer : orderers) { //add remaining orderers if any.
            newChannel.addOrderer(orderer);
        }

        //获得组织内事件记录节点集合
        for (String eventHubName : sampleOrg.getEventHubNames()) {

            //从配置中获取事件记录属性
            final Properties eventHubProperties = testConfig.getEventHubProperties(eventHubName);

            //增加保持连接事件以及连接超时时间设置
            eventHubProperties.put("grpc.NettyChannelBuilderOption.keepAliveTime", new Object[] {5L, TimeUnit.MINUTES});
            eventHubProperties.put("grpc.NettyChannelBuilderOption.keepAliveTimeout", new Object[] {8L, TimeUnit.SECONDS});

            //创建事件记录对象
            EventHub eventHub = client.newEventHub(eventHubName, sampleOrg.getEventHubLocation(eventHubName),
                    eventHubProperties);
            //将事件记录对象加入到信道中
            newChannel.addEventHub(eventHub);
        }

        //信道实例化
        newChannel.initialize();

        out("Finished initialization channel %s", name);

        return newChannel;

    }

    static void out(String format, Object... args) {

        System.err.flush();
        System.out.flush();

        System.out.println(format(format, args));
        System.err.flush();
        System.out.flush();

    }

    private void waitOnFabric(int additional) {
        // wait a few seconds for the peers to catch up with each other via the gossip network.
        // Another way would be to wait on all the peers event hubs for the event containing the transaction TxID
//        try {
//            out("Wait %d milliseconds for peers to sync with each other", gossipWaitTime + additional);
//            TimeUnit.MILLISECONDS.sleep(gossipWaitTime + additional);
//        } catch (InterruptedException e) {
//            fail("should not have jumped out of sleep mode. No other threads should be running");
//        }
    }

    /**
     * 根据文件加载秘钥
     * */
    File findFileSk(File directory) {

        File[] matches = directory.listFiles((dir, name) -> name.endsWith("_sk"));

        if (null == matches) {
            throw new RuntimeException(format("Matches returned null does %s directory exist?", directory.getAbsoluteFile().getName()));
        }

        if (matches.length != 1) {
            throw new RuntimeException(format("Expected in %s only 1 sk file but found %d", directory.getAbsoluteFile().getName(), matches.length));
        }

        return matches[0];

    }

    private static final Map TX_EXPECTED;

    static {
        TX_EXPECTED = new HashMap<>();
        TX_EXPECTED.put("readset1", "Missing readset for channel bar block 1");
        TX_EXPECTED.put("writeset1", "Missing writeset for channel bar block 1");
    }

    void blockWalker(Channel channel) throws InvalidArgumentException, ProposalException, IOException {
        try {
            //获得链块信息
            BlockchainInfo channelInfo = channel.queryBlockchainInfo();

            //从后往前加载块信息
            for (long current = channelInfo.getHeight() - 1; current > -1; --current) {
                //获得当前查找到的快信息
                BlockInfo returnedBlock = channel.queryBlockByNumber(current);
                //此处为什么块不用当前循环的值,而是要自己去取呢?
                final long blockNumber = returnedBlock.getBlockNumber();

                out("current block number %d has data hash: %s", blockNumber, Hex.encodeHexString(returnedBlock.getDataHash()));
                out("current block number %d has previous hash id: %s", blockNumber, Hex.encodeHexString(returnedBlock.getPreviousHash()));
                out("current block number %d has calculated block hash is %s", blockNumber, Hex.encodeHexString(SDKUtils.calculateBlockHash(blockNumber, returnedBlock.getPreviousHash(), returnedBlock.getDataHash())));

                //获取数据量?
                final int envelopCount = returnedBlock.getEnvelopCount();
                //为什么比较 1 ? 难道这个块里面的数据量就是 1 ?
                assertEquals(1, envelopCount);

                out("current block number %d has %d envelope count:", blockNumber, returnedBlock.getEnvelopCount());
                int i = 0;
                for (BlockInfo.EnvelopeInfo envelopeInfo : returnedBlock.getEnvelopeInfos()) {
                    ++i;

                    out("  Transaction number %d has transaction id: %s", i, envelopeInfo.getTransactionID());
                    final String channelId = envelopeInfo.getChannelId();
                    assertTrue("foo".equals(channelId) || "bar".equals(channelId));

                    out("  Transaction number %d has channel id: %s", i, channelId);
                    out("  Transaction number %d has epoch: %d", i, envelopeInfo.getEpoch());
                    out("  Transaction number %d has transaction timestamp: %tB %, i, envelopeInfo.getTimestamp());
                    out("  Transaction number %d has type id: %s", i, "" + envelopeInfo.getType());

                    if (envelopeInfo.getType() == TRANSACTION_ENVELOPE) {
                        BlockInfo.TransactionEnvelopeInfo transactionEnvelopeInfo = (BlockInfo.TransactionEnvelopeInfo) envelopeInfo;

                        out("  Transaction number %d has %d actions", i, transactionEnvelopeInfo.getTransactionActionInfoCount());
                        assertEquals(1, transactionEnvelopeInfo.getTransactionActionInfoCount()); // for now there is only 1 action per transaction.
                        out("  Transaction number %d isValid %b", i, transactionEnvelopeInfo.isValid());
                        assertEquals(transactionEnvelopeInfo.isValid(), true);
                        out("  Transaction number %d validation code %d", i, transactionEnvelopeInfo.getValidationCode());
                        assertEquals(0, transactionEnvelopeInfo.getValidationCode());

                        int j = 0;
                        for (BlockInfo.TransactionEnvelopeInfo.TransactionActionInfo transactionActionInfo : transactionEnvelopeInfo.getTransactionActionInfos()) {
                            ++j;
                            out("   Transaction action %d has response status %d", j, transactionActionInfo.getResponseStatus());
                            assertEquals(200, transactionActionInfo.getResponseStatus());
                            out("   Transaction action %d has response message bytes as string: %s", j,
                                    printableString(new String(transactionActionInfo.getResponseMessageBytes(), "UTF-8")));
                            out("   Transaction action %d has %d endorsements", j, transactionActionInfo.getEndorsementsCount());
                            assertEquals(2, transactionActionInfo.getEndorsementsCount());

                            for (int n = 0; n < transactionActionInfo.getEndorsementsCount(); ++n) {
                                BlockInfo.EndorserInfo endorserInfo = transactionActionInfo.getEndorsementInfo(n);
                                out("Endorser %d signature: %s", n, Hex.encodeHexString(endorserInfo.getSignature()));
                                out("Endorser %d endorser: %s", n, new String(endorserInfo.getEndorser(), "UTF-8"));
                            }
                            out("   Transaction action %d has %d chaincode input arguments", j, transactionActionInfo.getChaincodeInputArgsCount());
                            for (int z = 0; z < transactionActionInfo.getChaincodeInputArgsCount(); ++z) {
                                out("     Transaction action %d has chaincode input argument %d is: %s", j, z,
                                        printableString(new String(transactionActionInfo.getChaincodeInputArgs(z), "UTF-8")));
                            }

                            out("   Transaction action %d proposal response status: %d", j,
                                    transactionActionInfo.getProposalResponseStatus());
                            out("   Transaction action %d proposal response payload: %s", j,
                                    printableString(new String(transactionActionInfo.getProposalResponsePayload())));

                            TxReadWriteSetInfo rwsetInfo = transactionActionInfo.getTxReadWriteSet();
                            if (null != rwsetInfo) {
                                out("   Transaction action %d has %d name space read write sets", j, rwsetInfo.getNsRwsetCount());

                                for (TxReadWriteSetInfo.NsRwsetInfo nsRwsetInfo : rwsetInfo.getNsRwsetInfos()) {
                                    final String namespace = nsRwsetInfo.getNaamespace();
                                    KvRwset.KVRWSet rws = nsRwsetInfo.getRwset();

                                    int rs = -1;
                                    for (KvRwset.KVRead readList : rws.getReadsList()) {
                                        rs++;

                                        out("     Namespace %s read set %d key %s  version [%d:%d]", namespace, rs, readList.getKey(),
                                                readList.getVersion().getBlockNum(), readList.getVersion().getTxNum());

                                        if ("bar".equals(channelId) && blockNumber == 2) {
                                            if ("example_cc_go".equals(namespace)) {
                                                if (rs == 0) {
                                                    assertEquals("a", readList.getKey());
                                                    assertEquals(1, readList.getVersion().getBlockNum());
                                                    assertEquals(0, readList.getVersion().getTxNum());
                                                } else if (rs == 1) {
                                                    assertEquals("b", readList.getKey());
                                                    assertEquals(1, readList.getVersion().getBlockNum());
                                                    assertEquals(0, readList.getVersion().getTxNum());
                                                } else {
                                                    fail(format("unexpected readset %d", rs));
                                                }

                                                TX_EXPECTED.remove("readset1");
                                            }
                                        }
                                    }

                                    rs = -1;
                                    for (KvRwset.KVWrite writeList : rws.getWritesList()) {
                                        rs++;
                                        String valAsString = printableString(new String(writeList.getValue().toByteArray(), "UTF-8"));

                                        out("     Namespace %s write set %d key %s has value '%s' ", namespace, rs,
                                                writeList.getKey(),
                                                valAsString);

                                        if ("bar".equals(channelId) && blockNumber == 2) {
                                            if (rs == 0) {
                                                assertEquals("a", writeList.getKey());
                                                assertEquals("400", valAsString);
                                            } else if (rs == 1) {
                                                assertEquals("b", writeList.getKey());
                                                assertEquals("400", valAsString);
                                            } else {
                                                fail(format("unexpected writeset %d", rs));
                                            }

                                            TX_EXPECTED.remove("writeset1");
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
            if (!TX_EXPECTED.isEmpty()) {
                fail(TX_EXPECTED.get(0));
            }
        } catch (InvalidProtocolBufferRuntimeException e) {
            throw e.getCause();
        }
    }

    static String printableString(final String string) {
        int maxLogStringLength = 64;
        if (string == null || string.length() == 0) {
            return string;
        }

        String ret = string.replaceAll("[^\\p{Print}]", "?");

        ret = ret.substring(0, Math.min(ret.length(), maxLogStringLength)) + (ret.length() > maxLogStringLength ? "..." : "");

        return ret;

    }

}

你可能感兴趣的:(fabric)