您的位置:首页 > 其它

Hyperledger Fabric v1.4.3 Orderer模块部分源码分析(1)——整体流程

2020-03-06 21:53 387 查看

写在前面

  • 本系列博客主要用于记录总结自己在做一个区块链相关项目中所涉及到的源码知识,一方面加深自己的记忆,另一方面也可以锻炼自己的知识叙述能力,博客内容仅供参考,真诚的欢迎对Fabric源码感兴趣的dalao来与我讨论问题。
  • 因自己所做项目的原因,该系列博客侧重点在于Orderer模块中共识机制的分析以及修改,因此源码中的其他部分可能不会做特别深入的研究,使用的平台为Ubuntu16.04+Windows10双系统,源代码为Hyperledger Fabric项目的1.4.3版本。点击跳转源码。
  • 码字不易,转载请说明出处:)

Orderer模块启动流程

Orderer模块指令

从下列源码中可以看出Orderer模块指令共有三条

app = kingpin.New("orderer", "Hyperledger Fabric orderer node")

start     = app.Command("start", "Start the orderer node").Default()
version   = app.Command("version", "Show version information")
benchmark = app.Command("benchmark", "Run orderer in benchmark mode")

分别是orderer start、orderer version和orderer benchmark。

我们从orderer start出发来看看模块启动的流程。
在命令行中输入orderer start指令(前提是已经在linux系统中完成了环境的配置),会运行fabric/orderer/main.go文件,该文件中的main函数会调用运行fabric/orderer/common/server/main.go文件中的Main函数。
在Main函数中会涉及到orderer节点启动的所有内容,因此这篇博客主要分析server/main.go文件。

Main函数

在Main函数中包含如下代码:

fullCmd := kingpin.MustParse(app.Parse(os.Args[1:]))

// "version" command
if fullCmd == version.FullCommand() {
fmt.Println(metadata.GetVersionInfo())
return
}

// conf directly corresponds to the orderer config YAML.
conf, err := localconfig.Load()
if err != nil {
logger.Error("failed to parse config: ", err)
os.Exit(1)
}
initializeLogging()
initializeLocalMsp(conf)

prettyPrintStruct(conf)
Start(fullCmd, conf)

第一句将linux命令行中的指令解析为字符串fullCmd;
然后判断fullCmd是否为“version”指令:

  • 如果是,则直接打印出版本信息,程序返回。
  • 否则要启动一个orderer节点。

从本地的YAML配置文件中导入配置信息结构体conf,再进行日志的初始化操作,prettyPrintStruct函数将配置信息打印在日志中,最后调用本地函数Start

Start函数

Start函数将分成五个片段来进行分析,叙述略长,因此最好能对照着源码来看@_@

代码片段一

bootstrapBlock := extractBootstrapBlock(conf)
if err := ValidateBootstrapBlock(bootstrapBlock); err != nil {
logger.Panicf("Failed validating bootstrap block: %v", err)
}

opsSystem := newOperationsSystem(conf.Operations, conf.Metrics)
err := opsSystem.Start()
if err != nil {
logger.Panicf("failed to initialize operations subsystem: %s", err)
}
defer opsSystem.Stop()
metricsProvider := opsSystem.Provider

首先通过配置信息conf提取出创世块,在创世块中包含有通道的初始配置信息;
然后newOperationsSystem函数可以创建一个“操作系统”,该操作系统或许也可以叫做是一个“orderer节点运行健康监测器”,在之后节点启动过程中会陆续通过metricsProvider创建一些Metrics,这些Metrics会随着系统运行实时更新,并作为数据由该操作系统操作,同时这些数据可以通过http协议,在浏览器中进行查看。

代码片段二

lf, _ := createLedgerFactory(conf, metricsProvider)
sysChanLastConfigBlock := extractSysChanLastConfig(lf, bootstrapBlock)
clusterBootBlock := selectClusterBootBlock(bootstrapBlock, sysChanLastConfigBlock)

clusterType := isClusterType(clusterBootBlock)
signer := localmsp.NewSigner()

第一句创建一个账本“工厂”lf,该账本工厂中包含所有通道的账本实体,可以通过该工厂来获取某个通道的区块链操作对象,进而可以读取和写入区块,有关Fabric账本体系的结构体结构图可以访问账本体系结构图。
创建完成账本工厂lf之后,从lf中提取出最新的通道配置区块sysChanLastConfigBlock,这个区块可能是nil。
然后从sysChanLastConfigBlock和创世块中筛选出最新的配置区块clusterBootBlock。
isClusterType函数返回值为bool类型,当配置区块中orderer配置信息为使用etcdraft共识机制,则会返回true,如果是使用其它共识算法,则会返回false。
signer为一个签名器,返回值是一个结构体,包含两个方法,可用于对消息进行签名。

代码片段三

clusterClientConfig := initializeClusterClientConfig(conf, clusterType, bootstrapBlock)
clusterDialer := &cluster.PredicateDialer{
ClientConfig: clusterClientConfig,
}

serverConfig := initializeServerConfig(conf, metricsProvider)
grpcServer := initializeGrpcServer(conf, serverConfig)
caSupport := &comm.CredentialSupport{
AppRootCAsByChain:           make(map[string]comm.CertificateBundle),
OrdererRootCAsByChainAndOrg: make(comm.OrgRootCAs),
ClientRootCAs:               serverConfig.SecOpts.ClientRootCAs,
}

clusterServerConfig := serverConfig
clusterGRPCServer := grpcServer
if clusterType {
clusterServerConfig, clusterGRPCServer = configureClusterListener(conf, serverConfig, grpcServer, ioutil.ReadFile)
}

var servers = []*comm.GRPCServer{grpcServer}
// If we have a separate gRPC server for the cluster, we need to update its TLS
// CA certificate pool too.
if clusterGRPCServer != grpcServer {
servers = append(servers, clusterGRPCServer)
}

initializeClusterClientConfig函数初始化一个关于orderer节点的GRPCclient客户端配置。

补充:当使用etcdraft共识机制时,会额外涉及到orderer节点之间的数据通信,而kafka和solo模式均只有orderer和peer节点之间的通信,因此,在etcdraft共识机制下,每个orderer节点即是gRPC服务器,又是gRPC客户端,并且每个节点内部均会运行两个gRPC服务器,一个用来处理peer节点提交的消息,一个用来处理其余orderer节点提交而来的消息,在代码中的区别为,前者是grpcServer,后者是clusterGRPCServer。两个gRPC服务器应该要监听不同的端口?
在启动时由gRPC客户端主动与gRPC服务器建立连接
创建gRPC服务器时,需要先注册服务,然后才能启动服务器监听数据!
有关gRPC通信的结构体结构图可参考gRPC通信结构图(只有与通信相关结构体,没有gRPC客户端和服务器)

clusterDialer变量是一个PredicateDialer结构体,该结构体与gRPC连接有关,在gRPC客户端主动与gRPC服务器建立连接时,会使用该结构体的Dial函数来产生ClientConn结构体,表示生成了一个gRPC连接。
initializeServerConfig函数返回gRPCserver的配置结构体,通过配置信息,使用initializeGrpcServer函数创建一个gRPC服务器,该函数返回值为GRPCServer结构体,该结构体在启动后会循环监听gRPC连接请求,并对数据进行处理。
然后判断clusterType变量:

  • 当clusterType为true,说明使用的是etcdraft共识机制,那么就需要额外创建一个用于orderer节点之间进行通信的gRPC服务器clusterGRPCServer。
  • 当clusterType为false,说明使用的是solo或kafka共识机制,那么clusterGRPCServer就相当于没有用了,保持与grpcServer相同即可。

最后将所有要使用的gRPC服务器添加在servers切片中。

代码片段四

manager := initializeMultichannelRegistrar(clusterBootBlock, r, clusterDialer, clusterServerConfig, clusterGRPCServer, conf, signer, metricsProvider, opsSystem, lf, tlsCallback)

initializeMultichannelRegistrar函数用于创建一个类似“大杂烩”一样的管理器manager,可以被用来调配各个子模块,其内部包含所有共识机制的Consenter,并且包装了chain集合(chainsupport),签名工具(signer),账本工厂,系统链条等等,manager可以为其他变量提供这些模块的接入点和控制。
并且在manager初始化完成之后,会相应的对内部一些模块进行实例化(初始化),initializeMultichannelRegistrar函数具体代码如下所示。

func initializeMultichannelRegistrar(
bootstrapBlock *cb.Block,
ri *replicationInitiator,
clusterDialer *cluster.PredicateDialer,
srvConf comm.ServerConfig,
srv *comm.GRPCServer,
conf *localconfig.TopLevel,
signer crypto.LocalSigner,
metricsProvider metrics.Provider,
healthChecker healthChecker,
lf blockledger.Factory,
callbacks ...channelconfig.BundleActor,
) *multichannel.Registrar {
genesisBlock := extractBootstrapBlock(conf)
// Are we bootstrapping?
if len(lf.ChainIDs()) == 0 {
initializeBootstrapChannel(genesisBlock, lf)
} else {
logger.Info("Not bootstrapping because of existing channels")
}

consenters := make(map[string]consensus.Consenter)

registrar := multichannel.NewRegistrar(*conf, lf, signer, metricsProvider, callbacks...)  // Registrar serves as a point of access and control for the individual channel resources.

consenters["solo"] = solo.New()
var kafkaMetrics *kafka.Metrics
consenters["kafka"], kafkaMetrics = kafka.New(conf.Kafka, metricsProvider, healthChecker)
// Note, we pass a 'nil' channel here, we could pass a channel that
// closes if we wished to cleanup this routine on exit.
go kafkaMetrics.PollGoMetricsUntilStop(time.Minute, nil)
if isClusterType(bootstrapBlock) {
initializeEtcdraftConsenter(consenters, conf, lf, clusterDialer, bootstrapBlock, ri, srvConf, srv, registrar, metricsProvider)
}

registrar.Initialize(consenters)
return registrar
}

使用extractBootstrapBlock函数导入创世块,判断账本工厂中是否存在通道,若不存在,则创建该创世块所对应的通道所使用的账本,并在创建完成之后将创世块添加在账本中。
consenters变量用于存储三种共识算法的Consenter实例。
NewRegistrar函数返回Registrar结构体,是管理器manager的实体。
通过调用各个共识机制的初始化函数,如solo.New等,可以创建三种共识机制的Consenter实例,并存储在consenters映射中。

注: 在创建etcdraft共识机制的Consenter时,会同时注册clusterGRPCServer的服务,因此,在我们实现自己的共识机制时,如果orderer节点之间需要通信,那在这里初始化的时候也需要注册服务,这个时候,不能让我们自己共识机制注册的服务与etcdraft的服务有冲突,即我们自己的共识机制与etcdraft的Consenter同时只能有一个。

registrar.Initialize函数内部会初始化通道ChainSupport,并启动区块链Chain,该函数具体代码如下。

func (r *Registrar) Initialize(consenters map[string]consensus.Consenter) {
r.consenters = consenters
existingChains := r.ledgerFactory.ChainIDs()  // 返回账本工厂中存在的所有链条ID

for _, chainID := range existingChains {  // 索引每一个已经存在的通道
rl, err := r.ledgerFactory.GetOrCreate(chainID)  // 获取账本读写器FileLedger(即实际的账本)
if err != nil {
logger.Panicf("Ledger factory reported chainID %s but could not retrieve it: %s", chainID, err)
}
configTx := configTx(rl)  // 从最新配置的区块中,提取出索引号为0的envelope
if configTx == nil {
logger.Panic("Programming error, configTx should never be nil here")
}
ledgerResources := r.newLedgerResources(configTx)
chainID := ledgerResources.ConfigtxValidator().ChainID()

if _, ok := ledgerResources.ConsortiumsConfig(); ok {
if r.systemChannelID != "" {
logger.Panicf("There appear to be two system chains %s and %s", r.systemChannelID, chainID)
}

chain := newChainSupport(
r,
ledgerResources,
r.consenters,
r.signer,
r.blockcutterMetrics,
)
r.templator = msgprocessor.NewDefaultTemplator(chain)
chain.Processor = msgprocessor.NewSystemChannel(chain, r.templator, msgprocessor.CreateSystemChannelFilters(r, chain, r.config))

// Retrieve genesis block to log its hash. See FAB-5450 for the purpose
iter, pos := rl.Iterator(&ab.SeekPosition{Type: &ab.SeekPosition_Oldest{Oldest: &ab.SeekOldest{}}})
defer iter.Close()
if pos != uint64(0) {
logger.Panicf("Error iterating over system channel: '%s', expected position 0, got %d", chainID, pos)
}
genesisBlock, status := iter.Next()
if status != cb.Status_SUCCESS {
logger.Panicf("Error reading genesis block of system channel '%s'", chainID)
}
logger.Infof("Starting system channel '%s' with genesis block hash %x and orderer type %s",
chainID, genesisBlock.Header.Hash(), chain.SharedConfig().ConsensusType())

r.chains[chainID] = chain // 存储的是ChainSupport指针
r.systemChannelID = chainID
r.systemChannel = chain
// We delay starting this chain, as it might try to copy and replace the chains map via newChain before the map is fully built
// 初始化完成chainsupport之后,会启动chain
// 其中chainsupport中的Chain是由共识机制Consenter的HandleChain方法得到的
defer chain.start()
} else {
logger.Debugf("Starting chain: %s", chainID)
chain := newChainSupport(
r,
ledgerResources,
r.consenters,
r.signer,
r.blockcutterMetrics,
)
r.chains[chainID] = chain
chain.start()
}

}

if r.systemChannelID == "" {
logger.Panicf("No system chain found.  If bootstrapping, does your system channel contain a consortiums group definition?")
}
}

Initialize函数中,生成的ledgerResources变量是一个ledgerResources结构体,内部包含有关于当前通道的配置信息以及账本实体,一个通道对应一个ledgerResources结构体,Fabric中所有与通道配置信息有关的结构体结构图详见通道配置信息结构图1,通道配置信息结构图2。
通过newChainSupport函数可以创建ChainSupport结构体,在该函数中会通过ledgerResources结构体获取当前通道所使用的共识机制类型,并且由此调用consenters映射中的共识机制Consenter,通过调用Consenter的HandleChain函数,产生Chain结构体,并保存在ChainSupport结构体中。
在产生完成一个ChainSupport结构体之后,会将ChainSupport结构体保存在管理器的chains映射中,并调用start函数,启动共识机制的处理主协程,start函数因共识机制而异,因此在此暂且不做讨论,在开启主协程之后就可以对交易信息进行共识处理了。

至此,“大杂烩”管理器创建并实例化完毕,与此同时,相应的共识机制处理协程也已开启。

代码片段五

mutualTLS := serverConfig.SecOpts.UseTLS && serverConfig.SecOpts.RequireClientCert  // 是否用TLS协议通信
expiration := conf.General.Authentication.NoExpirationChecks
// 创建一个server,用于进行broadcast和deliver操作(即原子广播),使用内部的两个Handler进行处理
server := NewServer(manager, metricsProvider, &conf.Debug, conf.General.Authentication.TimeWindow, mutualTLS, expiration)

logger.Infof("Starting %s", metadata.GetVersionInfo())

go handleSignals(addPlatformSignals(map[os.Signal]func(){
syscall.SIGTERM: func() {
grpcServer.Stop()
if clusterGRPCServer != grpcServer {
clusterGRPCServer.Stop()
}
},
}))

if clusterGRPCServer != grpcServer {  // kafka和solo中的两者一定是相同的,只有etcdraft是不同的
logger.Info("Starting cluster listener on", clusterGRPCServer.Address())
// clusterGRPCServer的service是在创建etcdraft的Consenter时注册的。
go clusterGRPCServer.Start()
}

initializeProfilingService(conf)
ab.RegisterAtomicBroadcastServer(grpcServer.Server(), server)  // 在grpcServer中的Server中注册原子广播service服务
logger.Info("Beginning to serve requests")
grpcServer.Start()  // 开启grpc服务,处理连接请求

使用NewServer函数,创建一个server,用于提供AtomicBroadcast服务,在server中包含两个Handler,分别用于处理Broadcast数据流和Deliver数据流,使用ab.RegisterAtomicBroadcastServer函数将server服务注册进gRPC服务器grpcServer中,clusterGRPCServer的服务是在etcdraft的Consenter创建的时候进行注册的,在当前函数中并未体现。
调用gRPC服务器的Start函数启动监听服务,在给定的端口进行监听,等待gRPC客户端的连接。

总结

至此,orderer模块就启动完成,在这个过程中启动了共识机制处理协程、一个或者两个gRPC服务器监听协程和一个状态监测器。
在后续文章中,将首先从solo共识入手来分析共识机制的实现方式以及基本的处理流程,进而以此为基础扩展到raft及kafka共识机制,最终实现自己的共识模块的嵌入。

  • 点赞
  • 收藏
  • 分享
  • 文章举报
起名要谨慎 发布了1 篇原创文章 · 获赞 0 · 访问量 178 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: