Michael.W谈hyperledger Fabric第16期-详细带读Fabric的源码1-orderer模块入口

Michael.W谈hyperledger Fabric第16期-详细带读Fabric的源码1-orderer模块入口

    • 1 容我先bb两句
    • 2 hyperledger Fabric的源码结构
    • 3 orderer的入口

1 容我先bb两句

如果想对系统或者框架有更详细的了解,那么读源码是必经之路。许多表面上云里雾里的概念逻辑,读完源代码后便豁然开朗。我读源码的经历也不是很多,很多时候完全看不下去了就逼着自己看。多看几遍就突然有顿悟之感。希望大家也可以坚持阅读,得到对Fabric更深的理解。技术交流可以关注我的微信公众号,那里随时都能找到我。

2 hyperledger Fabric的源码结构

首先拿到一个项目源码,先看一下文件目录结构:
Michael.W谈hyperledger Fabric第16期-详细带读Fabric的源码1-orderer模块入口_第1张图片
我这里简单地做一下目录介绍:

  • bccsp:跟密码学相关的目录,有关加密,数字签名和证书相关。它将fabric中需要的函数抽象成了一组接口,可以很方便地进行拓展。
  • bddtests:行为驱动开发模式。传统的软件开发流程为:需求->概要设计->详细设计->开发。而行为驱动开发模式流程为:需求->开发。大大简化了流程。
  • common:共同库。在前面的手动搭建Fabric网络的帖子中,我就用到了common/crypto和common/configtx两个工具。这个库中主要包括:
    1. common/errors 错误处理
    2. common/flogging 日志
    3. common/ledger 账本存储
    4. common/mocks 策略
  • core:Fabric中用到的核心库,针对每一个组件都有一个子目录。
    Michael.W谈hyperledger Fabric第16期-详细带读Fabric的源码1-orderer模块入口_第2张图片
  • devenv:Fabric官方提供的开发环境,使用Vagrant
  • docs:文档相关
  • events:区块链是一个分布式的异步通信系统,一笔交易从提交到打包成块是需要时间的。那么客户端是如何知道该笔交易已经被打包到区块里了呢?这就需要这个事件监听机制
  • example:一些样例
  • gossip:组织内部数据同步所使用的一个协议。也是分布式系统中常见的共识协议。达到一个最终一致性的效果,属于最终一致性共识算法
  • gotools:里面有个makefile,用于编译
  • images:用于docker镜像打包的,前面使用的docker镜像都是通过这个目录下的配置文件生成的
  • msp:全称member service provider,成员服务管理。 Fabric会为每一个成员和用户提供证书,msp模块是用于读取这些证书做一些相关的处理
  • orderer:排序节点的入口
  • peer:peer节点的入口
  • proposals:Fabric现在已经成为了一个开源项目,它是一个开源社区,所以说对新功能的加入和旧功能的修改是需要一个社区协调的过程。不能说某一个组织就能来单独修改的,是需要所有组织进行体验然后投票选择的。所以该路径是用于新功能提案
  • protos:几乎所有的Fabric中提供的数据结构和数据存储都是在这个目录下定义的。

补充:Fabric使用gRPC作为各节点间通信的框架。gRPC是protobuffer+RPC,平时也能看见一些区块链使用的是jsonRPC(json+RPC)。
gRPC是谷歌开源的一套RPC框架,结合protobuffer可以很方便地对外提供服务。在课后可以好好了解一下gRPC,可能以后的开发中你会摒弃http服务。

3 orderer的入口

说到搭建Fabric网络搭建,我所写的yaml配置文件最开始都是关于orderer节点的容器。所以,在Fabric网络中orderer节点往往是最先启动的。那么就先来看看关于orderer的源代码。

	func main() {
		// 打印版本信息
		kingpin.Version("0.0.1")
		switch kingpin.MustParse(app.Parse(os.Args[1:])) {
		case start.FullCommand():
			logger.Infof("Starting %s", metadata.GetVersionInfo())
			// 载入配置信息[1]。采用go语言常用的配置文件库viper,想了解可以去github上看一下viper的使用方法
			conf := config.Load()
			// 初始化日志级别。根据配置文件动态修改的。
			initializeLoggingLevel(conf)
			// 初始化profile。profile是go语言内置的一个可以观察程序运行的工具,可以通过http服务暴露出来。
			initializeProfilingService(conf)
			// 初始化grpc服务端
			grpcServer := initializeGrpcServer(conf)
			// 载入msp证书
			initializeLocalMsp(conf)
			// msp证书用于签名者实例化
			signer := localmsp.NewSigner()
			// 初始化多链manager
			manager := initializeMultiChainManager(conf, signer)
			// 实例化服务实现
			server := NewServer(manager, signer)
			// 绑定服务器 + 服务实现
			ab.RegisterAtomicBroadcastServer(grpcServer.Server(), server)
			logger.Info("Beginning to serve requests")
			// 启动服务
			grpcServer.Start()
		case version.FullCommand():
			fmt.Println(metadata.GetVersionInfo())
		}
	}

[1]载入配置信息:关于配置文件的位置可以在sampleconfig文件夹下找到。
Michael.W谈hyperledger Fabric第16期-详细带读Fabric的源码1-orderer模块入口_第3张图片
我们需要重点关注以下操作:

		// 初始化多链manager
		manager := initializeMultiChainManager(conf, signer)
		// 实例化服务实现
		server := NewServer(manager, signer)

进入initializeMultiChainManager函数:

	func initializeMultiChainManager(conf *config.TopLevel, signer crypto.LocalSigner) multichain.Manager {
	  // 创建账本工厂。账本功能是存储orderer产生的临时区块。现在他提供了三种实现方法:1.基于文件类型(file) 2.基于json类型 3.基于内存类型(ram)  一般我们都使用基于文件类型的。
		lf, _ := createLedgerFactory(conf)
		// 是否有链?这是一个从无到有的过程 
		if len(lf.ChainIDs()) == 0 {
			// 启动引导
			initializeBootstrapChannel(conf, lf)
		} else {
	    	//如果已经存在链
			logger.Info("Not bootstrapping because of existing chains")
		}
		// 实例化共识机制。目前官方只给出了solo和kafka两种共识机制,如果我们想自定义共识机制的话,需要在这里注册
		consenters := make(map[string]multichain.Consenter)
		consenters["solo"] = solo.New()
		consenters["kafka"] = kafka.New(conf.Kafka.TLS, conf.Kafka.Retry, conf.Kafka.Version)
		// 实例化manager。我们可以将manager当成所有链的一个中枢。所有链的生成和交易都会经过它。
		return multichain.NewManagerImpl(lf, consenters, signer)
	}

退回主函数,进入实例化服务实现函数NewServer:

	func NewServer(ml multichain.Manager, signer crypto.LocalSigner) ab.AtomicBroadcastServer {
	  // 实现了一个内部类server
		s := &server{	
			dh: deliver.NewHandlerImpl(deliverSupport{Manager: ml}),
			bh: broadcast.NewHandlerImpl(broadcastSupport{
				Manager:               ml,
				ConfigUpdateProcessor: configupdate.New(ml.SystemChannelID(), configUpdateSupport{Manager: ml}, signer),
			}),
		}
		return s
	}
	// 定义了一个内部类server
	type server struct {
		// 交易收集。所有客户端向orderer提交交易的时候,都会被这个成员处理
		bh broadcast.Handler 
		// 区块扩散。通过这个变量向其他组织的主节点进行区块广播
		dh deliver.Handler 
	  //上面这两个变量都是interface,这实际上是对gRPC提供的服务接口的封装
	}

关于server类中对gRPC提供的服务接口的封装处于两个不同的文件中:

	// broadcast.go
	type Handler interface {
		Handle(srv ab.AtomicBroadcast_BroadcastServer) error
	}

	// deliver.go
	type Handler interface {
		Handle(srv ab.AtomicBroadcast_DeliverServer) error
	}

下面看一下这个server内部类提供了哪些方法?

	// 从客户端接收虚拟交易后的背书结果来进行排序
	func (s *server) Broadcast(srv ab.AtomicBroadcast_BroadcastServer) error {
		logger.Debugf("Starting new Broadcast handler")
		defer func() {	//匿名函数
			if r := recover(); r != nil {
				logger.Criticalf("Broadcast client triggered panic: %s\n%s", r, debug.Stack())
			}
			logger.Debugf("Closing Broadcast stream")
		}()
		return s.bh.Handle(srv)
	}
	// 在排序完成后向客户端发送区块流
	func (s *server) Deliver(srv ab.AtomicBroadcast_DeliverServer) error {
		logger.Debugf("Starting new Deliver handler")
		defer func() {
			if r := recover(); r != nil {
				logger.Criticalf("Deliver client triggered panic: %s\n%s", r, debug.Stack())
			}
			logger.Debugf("Closing Deliver stream")
		}()
		return s.dh.Handle(srv)
	}

那为什么server类会有这两个方法呢?
在main函数中有一步:

		// 绑定服务器 + 服务实现
		ab.RegisterAtomicBroadcastServer(grpcServer.Server(), server)

这一步是gRPC提供的方法。主要是将gRPC提供的服务定义与具体实现绑定起来。
gRPC提供的服务定义可以在protos路径下找到:
Michael.W谈hyperledger Fabric第16期-详细带读Fabric的源码1-orderer模块入口_第4张图片
ab.proto文件中定义了两个接口:Broadcast和Deliver。这就是为什么server类要实现这两个方法的原因。

	service AtomicBroadcast {
	    // broadcast receives a reply of Acknowledgement for each common.Envelope in order, indicating success or type of failure
	    rpc Broadcast(stream common.Envelope) returns (stream BroadcastResponse) {}
	
	    // deliver first requires an Envelope of type DELIVER_SEEK_INFO with Payload data as a mashaled SeekInfo message, then a stream of block replies is received.
	    rpc Deliver(stream common.Envelope) returns (stream DeliverResponse) {}
	}

接着重新回到实例化服务实现函数NewServer:

	func NewServer(ml multichain.Manager, signer crypto.LocalSigner) ab.AtomicBroadcastServer {
	  // 实现了一个内部类server
		s := &server{	
			 //对deliver接口进行了实例化,参数是对deliverSupport接口的实例化
			dh: deliver.NewHandlerImpl(deliverSupport{Manager: ml}),
			//对broadcast接口进行了实例化,参数是对broadcastSupport的实例化
			bh: broadcast.NewHandlerImpl(broadcastSupport{
				Manager:               ml,
				ConfigUpdateProcessor: configupdate.New(ml.SystemChannelID(), configUpdateSupport{Manager: ml}, signer),
			}),	 
		}
		return s
	}
	// 定义了一个内部类server
	type server struct {
		// 交易收集。所有客户端向orderer提交交易的时候,都会被这个成员处理
		bh broadcast.Handler 
		// 区块扩散。通过这个变量向其他组织的主节点进行区块广播
		dh deliver.Handler 
	  //上面这两个变量都是interface,这实际上是对gRPC提供的服务接口的封装
	}

可以注意到NewServer中对deliver和broadcast实例化并不是简单地将Manager传进去,而是由进行了一次重装,即实例化了deliverSupport和broadcastSupport接口。重装的内容是在该文件的里面:

	type broadcastSupport struct {
		multichain.Manager
		broadcast.ConfigUpdateProcessor
	}
	type deliverSupport struct {
		multichain.Manager
	}

分别实现了broadcast和deliver的SupportManager接口。可以看出这个项目中大量地使用了接口。
注意:go语言有个特性:接口的实现并不是像java那样直接关联的关系,而是隐式的关联。一个类实现了一个接口的所有方法就是实现了该接口,这对阅读源代码造成很大困扰。所以在阅读时一定要理清思路。

综上可见,所有接口的实现都离不开这样的一个multichain.Manager实例化对象。该实例化对象是在main函数中的initializeMultiChainManager方法实现的。

	// 初始化多链manager
		manager := initializeMultiChainManager(conf, signer)

ps:
本人热爱图灵,热爱中本聪,热爱V神,热爱一切被梨花照过的姑娘。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
后现代泼痞浪漫主义奠基人
公众号名称:后现代泼痞浪漫主义奠基人

你可能感兴趣的:(Fabric,Fabric)