一 checkConfig Before
1.1 private static final TestConfig testConfig = TestConfig.getConfig();
这里加载一个配置文件(test 路径 / src/test/java/org/hyperledger/fabric/sdk/testutils.properties, 文件不存在就加载代码中写死的默认配置),
配置文件需要设置 peer,orderer,ca,eventhub 的地址, 组织 mspid, 组织域名. 解析配置文件, 将信息加载到 sampleOrgs 中, 如果与 CA 通信
启用了 tls, 需要在 sampleOrg 中 (字段 caProperties) 保存与 CA 通信的 tls 证书位置(src/test/fixture/sdkintegration/e2e-2Orgs/$(FAB_CONFIG_GEN_VERS)/crypto-config/peerOrganizations/$(DNAME)/ca/ca.$(DNAME)-cert.pem), 用于之后为组织创建 CA 客户端,
当前样例中与 CA 通信默认没有启用 TLS.
**** 注意 sampleOrgs 是一个重要的变量, 保存了所有的配置信息, 并且之后加入组织的成员信息也保存到 sampleOrgs 中, sampleOrgs 集合中一
个典型组织结构如下:
1.2 为每个组织设置 CA 客户端
从 testConfig 中获取到 sampleOrgs 到 testSampleOrgs 中, 遍历每个组织并设置每个组织创建 CA 客户端, 用户之后访问 CA 服务器.
二 setup Test
setup 为测试主要流程实现, 包括创建 user,admin,peerAdmin, 为每个用户获取 CA 证书, 创建 channel, 安装链码, 实例化链码, 设置事件, 进行交易
几个部分.
2.1 为每个组织创建用户, 也保存在 sampleOrgs 中
组织的用户信息与常规配置不同, 在程序运行区间会有动态添加用户的需求, 并且程序在运行时, 需要保存用户的信息. 样例中为了简单, 将用户实例对象序列化到文件中, 所以这里第一步先去文件中恢复用户信息到 sampleOrg 中.
反序列化本地缓存的用户对象 (/tmp/HFCSampletest) 到 sampleStore 中
- sampleStore = new SampleStore(sampleStoreFile);
- enrollUsersSetup(sampleStore); //This enrolls users with fabric ca and setups sample store to get users later.
1) 先获取组织的 CA 客户端实例;
2) 设置 CA 实例的加密套件, 用于加解密和验证;
3) 创建用户, 并通过 ca.enroll(user, secret) 向 CA 服务端申请证书.
- keypair = cryptoSuite.keyGen(); // generate ECDSA keys: signing and encryption keys, 非对称加密生成私钥
- //url:ca 地址; body: 通过 kvpair 和 user 生成; 加上 user 和 secret 去申请证书, 最后从 response 中解析出证书
- String responseBody = httpPost(url + HFCA_ENROLL, body, new UsernamePasswordCredentials(user, secret));
这里需要实例化三种用户.
首先是 admin, 即登陆 ca 的管理员用户, 默认账号密码 "admin", "adminpw". 这个用户在 ca 服务器启动时就有, 先尝试从用户存储文件中获取, 没有的话直接从 CA 中获取 keypair 和证书即可. 所有的用户抽象为 sampleUser 的结构, 如下图, keypaire 和证书保存在 enrollment 中. 另外所有的用户都保存到 sampleOrg 中.
然后是普通用户, 普通用户可以有多个, 先尝试从序列化后的用户存储文件中获取, 没有的话需要先从 CA 注册, 再创建 keypair, 申请证书. 最后保存到 sampleOrg 字段 usermap 中
sampleOrg sampleorg: 组织集合, 包括 orderer 的地址(与 orderer 通信时使用的 tls 证书是启动 CA 后去申请的),peer 地址, user,admin,peerAdmin
Orderers 集合: tls 证书和 orderer 地址
HFCCient client: cryptoSuite 加密解密组件,
- channel(需要通过 tx 配置, 组织 envelope, 用 peerAdmin 签名后, 用 orderer 对象原子广播生成 channel),
- userContext(对最后要发送的 envelpe 做签名, 如在创建 channel 时就是用 peerAdmin 对象的密钥去做签名, 那就设置为 peerAdmin, 如果是发送一笔普通交易, 则可以用普通 user 做签名)
- channel fooChannel:
- runFabricTest
1 创建 HFClient, 一个 client 配一个 channel 实例
设置 client 的加密组件, 用于加密, 解密, 验证.
2 创建 channel
获取 org1 的实例 sampleOrg
创建 channel 实例, 传入 client 和 sampleOrg
Channel fooChannel = constructChannel(FOO_CHANNEL_NAME, client, sampleOrg);
1) 设置 userContext client.setUserContext(sampleOrg.getPeerAdmin());
2) 将 sampleOrg 下所有的 orderer 地址, 实例化 orderer 对象, 做成 orderers 集合
orderers.add(client.newOrderer(orderName, sampleOrg.getOrdererLocation(orderName), ordererProperties));
3) 选择集合中的第一个 orderer 去创建 channel
先读取[channel name].tx 配置文件到 ChannelConfiguration channelConfiguration 对象中
然后创建 channel, 依据 channel 创建策略去创建 channel, 这里只需要 peerAdmin 的签名, 如果策略需要更多, 则需要指定更多
- public Channel newChannel(String name, Orderer orderer, ChannelConfiguration channelConfiguration, byte[]... channelConfigurationSignatures)
- Channel newChannel = client.newChannel(name, anOrderer, channelConfiguration, client.getChannelConfigurationSignature(channelConfiguration, sampleOrg.getPeerAdmin()));
newChannnel 实现流程,[channelName].tx 实际就是一个 envelope 结构体, 先发序列化出来
反序列化 payload
反序列化 payload 的 header 中的 channelheader
校验下 header 的值, common.java 中定义了 type 的值: public enum HeaderType, 这里我们 tx 中应该为 CONFIG_UPDATE(2), 表示一笔更新 channel 配置的交易
接着反序列化 payload 的 data 域, configUpdateEnv
获取其中的 configUpdate 字段内容, 发送
从中会重新构建一个 envelope, 做签名后, 再用 orderer 对象发送(发送给 orderer 对象中保存的地址, 使用其 tls 证书)
private void sendUpdateChannel(byte[] configupdate, byte[][] signers, Orderer orderer) 这里就要创建 channel 了
1) 先构建一个交易上下文
TransactionContext transactionContext = getTransactionContext();
从中调用 new TransactionContext(this, userContext, client.getCryptoSuite()); 返回交易上下文对象
cryptoPrimititives: client.getCrytoSuite() 加密解密验证套件, userContext(即 2-1 中定义, 主要是使用其公私钥来给 envelope 做签名),channel 当前的 channel 对象, identity 身份认证信息
2)构建上下文对象后, 要构建 envelope, 会使用上下文对象来作签名. 重要: 上下文对象中包含 client 的 usercontext, 这里要区别开创建 channel 传进来的 signer,signer 是直接把序列化的证书 byte 存到了 envelope 中的 payload 中的 data 域(data 域为一个 configupdateenv 结构, 包含了 signatures 字段). 而上下文对象中的 client 的 usercontext 则是用来对 envelope 各个部分做签名(包括 payload 整体做签名(放到 envelope 的开始),payload 的 header,payload 的 data)
猜测: signer 可以自己设置任意用户, 任意多个签名 byte, 即指定 channel 设置的一个策略???????
2-1)创建 configupdateenv, 同上面的 configUpdateEnvelope 结构, 这里重新构建信封结构, 添加签名.
这里签名只有 peerAdmin 的签名. configupdateEnv 将作为 envelope payload 结构中 data 域
2-2)然后设置 envelope payload 结构的 header 域, header 域包括 channel header(包含 type, 是普通交易还是 channel 配置等等和交易 txid),signatureheader(签名头)
- final ByteString sigHeaderByteString = getSignatureHeaderAsByteString(transactionContext); //signatureheader 由交易上下文生成
- final ChannelHeader payloadChannelHeader = ProtoUtils.createChannelHeader(HeaderType.CONFIG_UPDATE,
- transactionContext.getTxID(), name, transactionContext.getEpoch(), transactionContext.getFabricTimestamp(), null, null); //channel header
- final Header payloadHeader = Header.newBuilder().setChannelHeader(payloadChannelHeader.toByteString())
- .setSignatureHeader(sigHeaderByteString).build(); // 设置 envelope payload 的 header
2-3)设置 envelope payload 的 data 域
- final ByteString payloadByteString = Payload.newBuilder()
- .setHeader(payloadHeader)
- .setData(configUpdateEnvBuilder.build().toByteString())
- .build().toByteString();
2-4)设置整个 envelope
- ByteString payloadSignature = transactionContext.signByteStrings(payloadByteString);
- Envelope payloadEnv = Envelope.newBuilder()
- .setSignature(payloadSignature)
- .setPayload(payloadByteString).build();
2-5)根据 orderer 中设置的 orderer 地址和 tls, 使用原子广播发送信封
BroadcastResponse trxResult = orderer.sendTransaction(payloadEnv);
至此 channel 创建完毕, 将创建的 channel 加入到去安居 channels 变量中
后续参见 End2endIT.java 844
- // Set peer to not be all roles but eventing.
- 1) newChannel.joinPeer(peer, createPeerOptions().setPeerRoles(PeerRole.NO_EVENT_SOURCE));
添加所有的 peer 到 channel 中, peer 对象的属性在 sampleOrg 中存储
- 2) for (Orderer orderer : orderers) {
- //add remaining orderers if any.
- newChannel.addOrderer(orderer);
- }
添加所有的 orderer 到 channel 中
3) 设置 eventhub, 其实是设置与 eventhub 服务端的连接, 后面去交易的时候才去设置监听的事件
3 回到 End2endIT.java 206 行, 在创建 channel 后安装实例化链码
sampleStore.saveChannel(fooChannel); 在 sampleStore 中本地序列化存储创建的 channel 对象
然后 runChannel runChannel(client, fooChannel, true, sampleOrg, 0);
3-1) 注册了 chaincode 事件
3-1) 第一步, 先安装链码, 所有的 peer 都要安装, 安装链码只需要封装 proposal 发送给 peers 就可以了
- Collection<Peer> peers = channel.getPeers();
- numInstallProposal = numInstallProposal + peers.size();
- responses = client.sendInstallProposal(installProposalRequest, peers);
3-2) 实例化链码
responses = channel.sendInstantiationProposal(instantiateProposalRequest, channel.getPeers()); // 封装 proposal 并发送给 peer
3-3) 发送交易, 实例化链码也当作一笔交易处理, 需要进行 orderer 排序, commiter 验证提交
- channel.sendTransaction(successful, createTransactionOptions() //Basically the default options but shows it's usage.
- .userContext(client.getUserContext()) //could be a different user context. this is the default.
- .shuffleOrders(false) // don't shuffle any orderers the default is true.
- .orderers(channel.getOrderers()) // specify the orderers we want to try this transaction. Fails once all Orderers are tried.
- .nOfEvents(nOfEvents) // The events to signal the completion of the interest in the transaction
- )
successful 是所有交易提案的结果集合
在 sendTransaction 中, 封装一个 envelope, 不同于 channel 创建中的 envelope, 这个 envelope 中 payload 封装为交易提案 response,response 为
多个背书节点的响应, 这里拿出来一个, ed 是所有背书的集合(读写集签名),proposalResponsePayload 为提案结果 payload, 共同作为 payload
- for (ProposalResponse sdkProposalResponse : proposalResponses) {
- ed.add(sdkProposalResponse.getProposalResponse().getEndorsement());
- if (proposal == null) {
- proposal = sdkProposalResponse.getProposal();
- proposalTransactionID = sdkProposalResponse.getTransactionID();
- proposalResponsePayload = sdkProposalResponse.getProposalResponse().getPayload();
- }
- }
- Payload transactionPayload = transactionBuilder
- .chaincodeProposal(proposal)
- .endorsements(ed)
- .proposalResponsePayload(proposalResponsePayload).build();
- Envelope transactionEnvelope = createTransactionEnvelope(transactionPayload, userContext)
resp = orderer.sendTransaction(transactionEnvelope); 3118 行, 发送信封, 这里会逐个 orderer 依次发送, 哪个返回 success, 就 break.
4 执行 chaincode
- client.setUserContext(sampleOrg.getUser(TESTUSER_1_NAME)); // 重要: 执行交易使用普通用户对 envelope 做签名即可
- ///////////////
- /// Send transaction proposal to all peers
- TransactionProposalRequest transactionProposalRequest = client.newTransactionProposalRequest();
- transactionProposalRequest.setChaincodeID(chaincodeID);
- transactionProposalRequest.setChaincodeLanguage(CHAIN_CODE_LANG);
- //transactionProposalRequest.setFcn("invoke");
- transactionProposalRequest.setFcn("move");
- transactionProposalRequest.setProposalWaitTime(testConfig.getProposalWaitTime());
- transactionProposalRequest.setArgs("a", "b", "100")
掠过交易提案过程, 这里获取到所有提案结果 successful 后, 设置并发送 envelope 给 orderer 634
- out("Sending chaincode transaction(move a,b,100) to orderer.");
- eturn channel.sendTransaction(successful).get(testConfig.getTransactionWaitTime(), TimeUnit.SECONDS);
执行到 sendTransaction channel.java 3079
return sendTransaction(proposalResponses, createTransactionOptions().orderers(orderers).userContext(userContext));
然后同实例化链码一样, 3179 行, 封装 envelope 并发送给 orderer
public CompletableFuture<TransactionEvent> sendTransaction(Collection<ProposalResponse> proposalResponses,TransactionOptions transactionOptions)
来源: http://www.bubuko.com/infodetail-2873143.html