Fabric 1.4源码解读 4:交易背书流程解读
流程
在 快速入门Fabric核心概念和框架 这篇文章中,介绍了 Fabric 的很多概念,其中也包含了交易、提案(Proposal)和链码。同时也介绍了,交易的执行流程,链码的调用流程等。
本文聚焦介绍交易流程的一个环节:交易背书,以下的3幅图,在快速入门Fabric核心概念和框架 中都有介绍,有必要的话,去读一下上下文信息。
交易宏观流程
交易的详细流程请阅读 交易流程,了解交易流程的几大环节。
链码调用流程
上图,展示了客户端、Peer,以及链码容器 3大主体在交易流程中的背书过程,请关注一下Peer中的 Handler,它负责和链码容器交互。
提案背书流程
上图,从接近源码的层面,展示了交易背书过程。其中Fabric、Shim 是 Peer 中的模块,ChainCode 代表链码容器,Endorser Chaincode 代表 Peer 对交易提案和模拟执行结果进行背书。
如果了解过Chaincode,你会知道 Shim 是链码容器和 Peer 交互所依赖的模块。
最后推荐一份保华大佬整理的 Peer 提案背书过程,是读源码前,必读的资料。虽然精简,但把重要的核心流程都串联起来了。
源码分析
Proposal定义
客户端发送被背书节点的是 SignedProposal
,它包含了签名和Proposal,这是它在proposal.proto
中的组成简介,:
1 | SignedProposal |
proposal.proto
这个文件还简要介绍了Client和背书节点之间通信的消息类型和过程。
Proposal
1 | type SignedProposal struct { |
1 | type Proposal struct { |
Header
1 | type Header struct { |
gRPC定义
1 | // EndorserClient is the client API for Endorser service. |
SDK发送Proposal
Peer接收Proposal
Peer处理Proposal主流程
主要是把背书节点的背书工作聚合一下:
- Proposal预处理
- 获取交易执行模拟器,模拟执行Proposal
- 如果模拟执行成功,调用ESCC对Proposal和结果进行背书,如果模拟执行失败直接返回背书失败的响应
1 | func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) { |
preProcess 检查和获取信息
1 | // preProcess checks the tx proposal headers, uniqueness and ACL |
背书节点模拟执行交易
获取模拟器
1 | // Support contains functions that the endorser requires to execute its tasks |
模拟执行
endorser部分
1 | if acquireTxSimulator(chainID, vr.hdrExt.ChaincodeId) { |
调用chaincode模块模拟执行交易,获取交易执行的公开和私密数据读写集,以及交易执行产生的事件,并把结果返回给上层进行背书。
其中还包含了私密数据的处理,会把它取出来,然后通过Gossip传播给在私密数据中的Peer节点。
1 | // SimulateProposal simulates the proposal by calling the chaincode |
callChaincode
调用chaincode模块执行链码。在前面的流程中,还没有区分系统链码SCC和用户链码UCC,SCC和UCC都会通过Execute
函数被传递给chaincode模块而执行。
如果是调用lscc
部署或升级UCC,会调用ExecuteLegacyInit
执行链码容器的初始化。
最后返回链码模拟执行结果和事件。
1 | // call specified chaincode (system or user) |
Support
接口实际集合了众多背书节点需要的外部模块功能,比如链码、系统链码、ACL等。
1 | // Support contains functions that the endorser requires to execute its tasks |
Execute
就是调用ChaincodeSupport.Execute
实现的。
1 | // Execute a proposal and return the chaincode response |
chaincode部分
通过上面的接口,跨入chaincode模块的大门。
1 | func (cs *ChaincodeSupport) Execute(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, input *pb.ChaincodeInput) (*pb.Response, *pb.ChaincodeEvent, error) { |
获取链码执行环境
Launch
可以获取链码执行环境,即用户链码容器,如果已实例化的链码,在当前背书节点上,链码容器未启动,则启动链码容器,Launch
会返回一个跟链码容器交互Handler。
某个 Peer 上可以部署多个链码容器,Peer 为了和这些链码容器交互/通信,给每个链码容器都创建了一个 Handler,Handler 携带了 Peer 和链码容器交互的资源。
1 | // Launch starts executing chaincode if it is not already running. This method |
链码容器的启动过程,不是本文的重点,所以不继续深入Launch的调用细节。
模拟执行交易
execute
封装出执行交易的消息,然后使用 Handler 执行交易。
1 | // execute executes a transaction and waits for it to complete until a timeout value. |
注意以下这个参数 txParams *ccprovider.TransactionParams
其类型定义如下,它包含了一条交易执行过程中的信息和资源,所以交易传递的过程中,一直有这个参数。
1 | // TransactionParams are parameters which are tied to a particular transaction |
Handler 执行交易的过程如下,创建交易执行的上下文 Context,因为链码容器在执行交易的时候,会和 Peer 之间进行多次通信,进行数据的读写,上下文可以让数据读写获取到正确的信息。
之后 Handler 把消息发送给链码容器,并等待链码容器发来包含执行结果的消息,或者执行超时,默认执行时间是 30s。
1 | func (h *Handler) Execute(txParams *ccprovider.TransactionParams, cccid *ccprovider.CCContext, msg *pb.ChaincodeMessage, timeout time.Duration) (*pb.ChaincodeMessage, error) { |
处理链码容器模拟响应
链码容器执行的响应会向上传递,直到 ChaincodeSupport.Execute
,它调用 processChaincodeExecutionResult
把链码容器返回的响应,转化为交易模拟执行的 Response,而 Response 最终会返回给Endorser,大家可去调用流程上翻。
1 | func processChaincodeExecutionResult(txid, ccName string, resp *pb.ChaincodeMessage, err error) (*pb.Response, *pb.ChaincodeEvent, error) { |
释放模拟器资源
回想一下,在 Endorser.SimulateProposal
中,它获取了 交易模拟执行器 TXSimulator
,这里面可是有很多资源的,如果不及时释放,在高 TPS 下,Peer压力上大,资源泄露,性能低下等问题会爆发出来。
txParams.TXSimulator.Done()
用来释放资源,上文提到 TxSimulator
包含了 QueryExecutor
, lockBasedQueryExecutor
实现了 QueryExecutor
,也就是说,主要是释放查询操作相关的资源。
从源码可以看到会释放读写锁以及迭代器资源,如果不及时释放,后果果然不堪。
1 | // Done implements method in interface `ledger.QueryExecutor` |
ESCC处理模拟执行结果
上文提到,模拟执行的 Response 会最终回到 Endorser,Endorser 会调用 ESCC 对结果进行背书,最终生成 ProposalResponse
,我们看一下这个过程。
1 | func (e *Endorser) ProcessProposal(ctx context.Context, signedProp *pb.SignedProposal) (*pb.ProposalResponse, error) { |
Endorser.endorseProposal
背书链码实现了可插拔,可以使用不同的ESCC,系统链码和用户链码的背书过程是不同的。
1 | // endorse the proposal by calling the ESCC |
背书插件实现下面的接口即可。
1 | // Plugin endorses a proposal response |
使用插件背书,需获取插件实例,然后组装响应Payload,它包含了交易执行的多种结果,然后对Payload以及签名的Proposal背书。
1 | // EndorseWithPlugin endorses the response with a plugin |
系统提供的默认背书插件如下,本质是对交易执行结果和Proposal签名人信息进行签名。
1 | // Endorse signs the given payload(ProposalResponsePayload bytes), and optionally mutates it. |
发送Response
ProcessProposal
会把 ProposalResponse 作为返回值,剩下的就交给 gRPC,发送给请求方了。
总结
本文从宏观和源码层面,解读了交易提案背书涉及的数据结构,以及其主要背书流程,核心可以主要包含以下几步:
- 检查Proposal
- 为交易创建模拟器,并调用模拟器模拟执行交易,生成执行结果
- 背书模块对执行结果和Proposal身份信息背书(签名),然后生成背书响应发送给客户端
关于背书流程,本文未涉及的环节有:
- Proposal中各字段,层层递进的含义
- 模拟执行交易,是链码执行函数,并和Peer交互的过程,以及模拟执行的各种资源
- 2种插件化ESCC的实现
后面的章节,会对相关源码实现做进一步分析。