如果想对系统或者框架有更详细的了解,那么读源码是必经之路。许多表面上云里雾里的概念逻辑,读完源代码后便豁然开朗。我读源码的经历也不是很多,很多时候完全看不下去了就逼着自己看。多看几遍就突然有顿悟之感。希望大家也可以坚持阅读,得到对Fabric更深的理解。技术交流可以关注我的微信公众号,那里随时都能找到我。
首先拿到一个项目源码,先看一下文件目录结构:
我这里简单地做一下目录介绍:
补充:Fabric使用gRPC作为各节点间通信的框架。gRPC是protobuffer+RPC,平时也能看见一些区块链使用的是jsonRPC(json+RPC)。
gRPC是谷歌开源的一套RPC框架,结合protobuffer可以很方便地对外提供服务。在课后可以好好了解一下gRPC,可能以后的开发中你会摒弃http服务。
说到搭建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())
}
}
// 初始化多链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神,热爱一切被梨花照过的姑娘。
以下是我个人的公众号,如果有技术问题可以关注我的公众号来跟我交流。
同时我也会在这个公众号上每周更新我的原创文章,喜欢的小伙伴或者老伙计可以支持一下!
如果需要转发,麻烦注明作者。十分感谢!
公众号名称:后现代泼痞浪漫主义奠基人