本文的内容还需进一步丰富,有时间会继续完善。
Orderer在Fabric网络中的作用主要是原子广播(Atomic Broadcast)和全排序(Total Order )。
broadcast
接口,接受client发送的交易,然后将这些Tx进行排序;排序的的原则为FIFS(First In First Served),但是区块内交易的顺序不一定与实际顺序一样,而是由到达Orderer的时间来决定的。deliver
接口将区块发送给Peer或client;保证所有Orderer节点分发出来的block是一致的。Handle函数负责orderer原子广播服务端的处理(orderer/common/broadcast/broadcast.go):
func (bh *Handler) Handle(srv ab.AtomicBroadcast_BroadcastServer) error {
addr := util.ExtractRemoteAddress(srv.Context())
logger.Debugf("Starting new broadcast loop for %s", addr)
for {
msg, err := srv.Recv()
if err == io.EOF {
logger.Debugf("Received EOF from %s, hangup", addr)
return nil
}
if err != nil {
logger.Warningf("Error reading from %s: %s", addr, err)
return err
}
resp := bh.ProcessMessage(msg, addr)
err = srv.Send(resp)
if resp.Status != cb.Status_SUCCESS {
return err
}
if err != nil {
logger.Warningf("Error sending to %s: %s", addr, err)
return err
}
}
}
Handle从Broadcast流里面读取请求,然后调用ProcessMessage函数进行处理,最后还会将处理结果返回给数据流。
排序节点接收的交易类型分为Normal交易和Config交易。
orderer/common/broadcast/broadcast.go:
func (bh *Handler) ProcessMessage(msg *cb.Envelope, addr string) (resp *ab.BroadcastResponse) {
chdr, isConfig, processor, err := bh.SupportRegistrar.BroadcastChannelSupport(msg)
if !isConfig {
configSeq, err := processor.ProcessNormalMsg(msg)
err = processor.WaitReady()
err = processor.Order(msg, configSeq)
} else { // isConfig
config, configSeq, err := processor.ProcessConfigUpdateMsg(msg)
err = processor.WaitReady()
err = processor.Configure(config, configSeq)
}
return &ab.BroadcastResponse{Status: cb.Status_SUCCESS}
}
上面的ProcessMessage函数只保留了主要逻辑的代码部分,如果是普通的交易,调用ProcessNormalMsg函数基于当前配置验证交易,然后调用Order接口提交一个待排序的交易;如果是config交易,调用ProcessConfigUpdateMsg函数进行处理,生成config快照,并且返回configSeq,然后调用Configure接口提交一个待排序的交易。
值得注意的是,排序节点仅仅是Fabric共识机制的一部分,还包括收集背书、签名、验证等,整个流程才是共识过程。
排序节点将收到的交易序列化,并按一定的规则打包成区块。多个Orderer节点通过可插拔的共识模块对交易进行共识。
共识模块目前有solo、kafka、etcdraft。etcdraft共识的核心代码为orderer/consensus/etcdraft/chain.go,下面就etcdraft共识进行简单的解析。
上面提到的Orderer接口和Configure接口都调用的是Submit函数:
func (c *Chain) Submit(req *orderer.SubmitRequest, sender uint64) error {
if err := c.isRunning(); err != nil {
c.Metrics.ProposalFailures.Add(1)
return err
}
leadC := make(chan uint64, 1)
select {
case c.submitC <- &submit{req, leadC}:
lead := <-leadC
if lead == raft.None {
c.Metrics.ProposalFailures.Add(1)
return errors.Errorf("no Raft leader")
}
if lead != c.raftID {
if err := c.rpc.SendSubmit(lead, req); err != nil {
c.Metrics.ProposalFailures.Add(1)
return err
}
}
case <-c.doneC:
c.Metrics.ProposalFailures.Add(1)
return errors.Errorf("chain is stopped")
}
return nil
}
将待提交的请求通过chan发送另一个goroutine处理;如果当前节点不是leader,还需要将请求发送给leader节点进行处理。
run函数的部分代码如下:
s.leader <- soft.Lead
if soft.Lead != c.raftID {
continue
}
batches, pending, err := c.ordered(s.req)
if err != nil {
c.logger.Errorf("Failed to order message: %s", err)
continue
}
if pending {
startTimer() // no-op if timer is already started
} else {
stopTimer()
}
c.propose(propC, bc, batches...)
首先将leader节点的值给到s.leader,这样Submit函数的goroutine即可获取到本节点是否为leader节点。如果本节点不是leader节点,run函数会等待下一个数据的进入。
run函数还设置了超时机制:
case <-timer.C():
ticking = false
batch := c.support.BlockCutter().Cut()
if len(batch) == 0 {
c.logger.Warningf("Batch timer expired with no pending requests, this might indicate a bug")
continue
}
c.logger.Debugf("Batch timer expired, creating block")
c.propose(propC, bc, batch) // we are certain this is normal block, no need to block
到了一定时间也会切割区块,防止长时间没有交易进入而无法切割。
如果是leader节点,那么将会执行ordered函数,进行batch的增加或切割。
使用spf13/viper
库进行命令配置的解析,orderer的环境变量全部是以“ORDERER_”开头的,前缀使用viper.SetEnvPrefix
指定。
internal/peer/common/ordererenv.go:
viper.Set("orderer.tls.rootcert.file", caFile)
viper.Set("orderer.tls.clientKey.file", keyFile)
viper.Set("orderer.tls.clientCert.file", certFile)
viper.Set("orderer.address", OrderingEndpoint)
viper.Set("orderer.tls.serverhostoverride", ordererTLSHostnameOverride)
viper.Set("orderer.tls.enabled", tlsEnabled)
viper.Set("orderer.tls.clientAuthRequired", clientAuth)
viper.Set("orderer.client.connTimeout", connTimeout)
使用viper.Set设置配置项。
orderer的变量配置通过在configtx.yaml保存在区块中,可以通过config交易进行修改。