hyperledger fabric-0.6 结构分析(一)

来源:互联网 发布:网络直播足球赛 编辑:程序博客网 时间:2024/06/08 06:30

先前分析程序着眼于细节分析,这样没有框架的概念,花了两天时间分析整理了一下hyperledger fabric的架构设计,分析该程序没有参照任何资料,如有错误欢迎指正,共同进步。


笔者在详细分析程序前有以下疑问:

1)CLI(命令行)客户端如何发送命令给Peer节点

2)本Peer节点如何接收其他节点的数据,接收到数据又如何处理,处理的方式和1又有什么区别

3)数据是何时又是如何被送入consensus模块

4)consensus模块内部又是如何架构的 为什么看起来helper executor pbft controller文件夹交至在一起,保存各自句柄,相互调用

5)ChainCode(链码,简称CC)是如何接收到Peer对其的操作、访问的

6)ChainCode是如何调用fabric API来查询写入数据的

7)在阅读源码初始化过程中,Peer节点会创建大量Server,这些Server后续过程我们是如何使用的


注:本人对于数据库、Docker相关知识不是很了解,尽量避免关于这两个部分的介绍以免错误的引导读者。

下面会慢慢的渗透以上涉及的问题。


Server :


每个Server作用:

AdminServer:控制该节点的命运,可以删除该节点所在的进程。(Start Stop GetStatus )

EventHubServer:Peer节点支持客户端对指定事件进行监听,例如Rejection等。客户端需要先注册自己关心的Events,当事件发生时trigger 监听者。

OpenChainServer:对外提供ledger的访问接口,涉及GetBlockchainInfo GetBlockByNumber等。

DevopsServer:负责与CLI Client对接,外部进行CC操作的入口,Deploy invoke query。

ChaincodeSupportServer:负责与shim/Chaincode通信,ChainCode的所有调用接收发送都要与该Server信息交互。

PeerServer:该Server是一个Engine,Engine关联了内部消息响应实现,同时为周围Peer节点创建Client与之通信。

RESTServer:该Server没有进行分析,应该是REST接口格式相关。



一级模块分类:

Client:  之前创建服务器与之对应的客户端,可以理解成其他节点或者CLI client等。

Protos:  中间层,Server与Client端 API接口定义

ServerProcess:服务响应处理函数,包括各类型的HandleMessage。

Consensus:  共识模块,目前采用的是PBFT NOOPS

ChainCode Shim:代码中shim和我理解的不一致,将ChainCodeSupport也应该算到shim,该模块的作用是连接Peer节点与ChainCode的媒介,用shim形容也可。

ChainCode:  链码,应用(例如智能合约)。

DB:  数据存储。

Library:  代码里有一个叫做Vendor的文件夹,该文件夹里涉及的功能模块自成一体,例如grpcServer等

API:  ChainCode里面会调用Peer节点信息。

Crypto:  伴随着数据加解密。 

Ledger:  账本操作。


该代码使用Handler触发模式,在跟踪代码程序时要注意handler对象赋值位置,否则容易找错HandleMessage,这些Handler处理函数命名基本相同,容易操作混乱。


下面分析几个读者应该最关心的流程:

1)Client通过CLI执行一条invoke命令

2)某节点发送给该节点ViewChange命令

3)ChainCode调用API putStatus

4)Consensus流程


一、 Client通过CLI执行一条invoke命令

1)在Peer节点初始化的时候 创建DevopsServer

[cpp] view plain copy
  1. serverDevops := core.NewDevopsServer(peerServer)  
  2. pb.RegisterDevopsServer(grpcServer, serverDevops)  

2)DevopsServer设置Service规范,例如Invoke Message,调用_Devops_Invoke_Handler函数

[cpp] view plain copy
  1. var _Devops_serviceDesc = grpc.ServiceDesc{  
  2.     ServiceName: "protos.Devops",  
  3.     HandlerType: (*DevopsServer)(nil),  
  4.     Methods: []grpc.MethodDesc{  
  5.         {  
  6.             MethodName: "Login",  
  7.             Handler:    _Devops_Login_Handler,  
  8.         },  
  9.         {  
  10.             MethodName: "Build",  
  11.             Handler:    _Devops_Build_Handler,  
  12.         },  
  13.         {  
  14.             MethodName: "Deploy",  
  15.             Handler:    _Devops_Deploy_Handler,  
  16.         },  
  17.         {  
  18.             MethodName: "Invoke",  
  19.             Handler:    _Devops_Invoke_Handler,  
  20.         },  
  21.         {  
  22.             MethodName: "Query",  
  23.             Handler:    _Devops_Query_Handler,  
  24.         },  
  25.         {  
  26.             MethodName: "EXP_GetApplicationTCert",  
  27.             Handler:    _Devops_EXP_GetApplicationTCert_Handler,  
  28.         },  
  29.         {  
  30.             MethodName: "EXP_PrepareForTx",  
  31.             Handler:    _Devops_EXP_PrepareForTx_Handler,  
  32.         },  
  33.         {  
  34.             MethodName: "EXP_ProduceSigma",  
  35.             Handler:    _Devops_EXP_ProduceSigma_Handler,  
  36.         },  
  37.         {  
  38.             MethodName: "EXP_ExecuteWithBinding",  
  39.             Handler:    _Devops_EXP_ExecuteWithBinding_Handler,  
  40.         },  
  41.     },  
  42.     Streams: []grpc.StreamDesc{},  
  43. }  
3)其中_Devops_Invoke_Handler函数在Protos模块,其负责将Client接入的信息传递到对应的Server模块
[cpp] view plain copy
  1. func _Devops_Invoke_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error) (interface{}, error) {  
  2.     in := new(ChaincodeInvocationSpec)  
  3.     if err := dec(in); err != nil {  
  4.         return nil, err  
  5.     }  
  6.     out, err := srv.(DevopsServer).Invoke(ctx, in)  
  7.     if err != nil {  
  8.         return nil, err  
  9.     }  
  10.     return out, nil  
  11. }  

4)在函数在devops服务端代码中处理

[cpp] view plain copy
  1. func (d *Devops) Invoke(ctx context.Context, chaincodeInvocationSpec *pb.ChaincodeInvocationSpec) (*pb.Response, error) {  
  2.     return d.invokeOrQuery(ctx, chaincodeInvocationSpec, chaincodeInvocationSpec.ChaincodeSpec.Attributes, true)  
  3. }  


5)精简invokeOrQuery代码,d.coord 是PeerServer对象,ExecuteTransaction 是对应Engine的实现方法

[cpp] view plain copy
  1. func (d *Devops) invokeOrQuery(ctx context.Context, chaincodeInvocationSpec *pb.ChaincodeInvocationSpec, attributes []string, invoke bool) (*pb.Response, error) {  
  2.   
  3. resp := d.coord.ExecuteTransaction(transaction)  
  4. }  


6)本次请求被封装成交易Struct,该处理是在PeerServer中。

[cpp] view plain copy
  1. func (p *Impl) ExecuteTransaction(transaction *pb.Transaction) (response *pb.Response) {  
  2.     if p.isValidator {  
  3.         response = p.sendTransactionsToLocalEngine(transaction)  
  4.     } else {  
  5.         peerAddresses := p.discHelper.GetRandomNodes(1)  
  6.         response = p.SendTransactionsToPeer(peerAddresses[0], transaction)  
  7.     }  
  8.     return response  
  9. }  

7)思考可知,最终这笔transaction是要交给到Consensus进行处理,那么如何传递的呢?就在下面p.engine.ProcessTransactionMsg,其中"p"代指PeerServer,engine是在创建PeerServer的时候指定的Engine,而这个Engine的handler实现在Consensus里,在实现EngineHandler过程中加载了PBFT算法。所以ProcessTransactionMsg函数的实现在consensus模块engine代码里。这样解决了开始时提出的疑问3)。

[cpp] view plain copy
  1. func (p *Impl) sendTransactionsToLocalEngine(transaction *pb.Transaction) *pb.Response {  
  2.   
  3.     peerLogger.Debugf("Marshalling transaction %s to send to local engine", transaction.Type)  
  4.     data, err := proto.Marshal(transaction)  
  5.     if err != nil {  
  6.         return &pb.Response{Status: pb.Response_FAILURE, Msg: []byte(fmt.Sprintf("Error sending transaction to local engine: %s", err))}  
  7.     }  
  8.   
  9.     var response *pb.Response  
  10.     msg := &pb.Message{Type: pb.Message_CHAIN_TRANSACTION, Payload: data, Timestamp: util.CreateUtcTimestamp()}  
  11.     peerLogger.Debugf("Sending message %s with timestamp %v to local engine", msg.Type, msg.Timestamp)  
  12.     response = p.engine.ProcessTransactionMsg(msg, transaction)  
  13.   
  14.     return response  
  15. }  

8)从这里开始进入了consensus内部处理,在这里Consensus模块是单独分析。

[cpp] view plain copy
  1. func (eng *EngineImpl) ProcessTransactionMsg(msg *pb.Message, tx *pb.Transaction) (response *pb.Response) {  
  2.        err := eng.consenter.RecvMsg(msg, eng.peerEndpoint.ID)  
  3. }  


画图说明上述流程:


 

该图中没有体现的一点是在Devops Server创建的时候将PeerServer对象作为构造参数传入,而PeerServer创建的过程就是创建Engine的过程,也是加载Engine-handler的过程,而Engine-handler的实现在Consensus模块。图中直接从Devops Server 跳入Consensus模块有些突兀。