Michael.W谈hyperledger Fabric第16期-详细带读Fabric的源码1-orderer模块入口
1 容我先bb两句
如果想对系统或者框架有更详细的了解,那么读源码是必经之路。许多表面上云里雾里的概念逻辑,读完源代码后便豁然开朗。我读源码的经历也不是很多,很多时候完全看不下去了就逼着自己看。多看几遍就突然有顿悟之感。希望大家也可以坚持阅读,得到对Fabric更深的理解。技术交流可以关注我的微信公众号,那里随时都能找到我。
2 hyperledger Fabric的源码结构
首先拿到一个项目源码,先看一下文件目录结构:
我这里简单地做一下目录介绍:
- bccsp:跟密码学相关的目录,有关加密,数字签名和证书相关。它将fabric中需要的函数抽象成了一组接口,可以很方便地进行拓展。
- bddtests:行为驱动开发模式。传统的软件开发流程为:需求->概要设计->详细设计->开发。而行为驱动开发模式流程为:需求->开发。大大简化了流程。
- common:共同库。在前面的手动搭建Fabric网络的帖子中,我就用到了common/crypto和common/configtx两个工具。这个库中主要包括:
- common/errors 错误处理
- common/flogging 日志
- common/ledger 账本存储
- common/mocks 策略
- core:Fabric中用到的核心库,针对每一个组件都有一个子目录。
- 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文件夹下找到。
我们需要重点关注以下操作:
// 初始化多链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路径下找到:
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神,热爱一切被梨花照过的姑娘。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人