Hyperledger-Fabric源码分析-共识算法-solo

solo 共识

​ genesis block决定当前fabric网络使用何种共识算法,并在Orderer启动时在ChainSupport数据结构中保存具体的共识实例,ChainSupport包含了操作一个通道的所有资源,通道即链

//ChainSupport实现了consensus.ConsenterSupport接口
type ChainSupport struct {
   *ledgerResources
   msgprocessor.Processor
   *BlockWriter
   consensus.Chain   //共识实例
   cutter blockcutter.Receiver
   crypto.LocalSigner
}
1.数据结构

​ 主要定义了三种结构

type consenter struct{}

type chain struct {
   support  consensus.ConsenterSupport
   sendChan chan *message
   exitChan chan struct{}
}

type message struct {
   configSeq uint64
   normalMsg *cb.Envelope
   configMsg *cb.Envelope
}
1.1 consenter

​ 实现了Consenter接口,HandleChain方法主要用来创建共识实例chain,support即上面提到的ChainSupport

func (solo *consenter) HandleChain(support consensus.ConsenterSupport, metadata *cb.Metadata) (consensus.Chain, error) {
   return newChain(support), nil   
}
func newChain(support consensus.ConsenterSupport) *chain {
   return &chain{
      support:  support,
      sendChan: make(chan *message),
      exitChan: make(chan struct{}),
   }
}
1.2chain

​ 共识实例,可以是Solo、Kafka和EtcdRaft,由HandleChain方法进行初始化

​ support:ChainSupport,提供链的所有操作

​ sendChan:定义无缓冲chan,接收交易

​ exitChan:退出

1.3message

​ 交易类型主要有两种, 且每个交易都带一个配置交易区块号 configSeq,此configSeq是校验当前交易时的最新配置交易区块号

​ normalMsg:普通交易

​ configMsg: 配置交易

2.共识流程
2.1启动solo

​ 启动之后通过for-select模式来处理交易,见2.3处理交易

func (ch *chain) Start() {
   go ch.main()
}
2.2接收交易

​ 交易类型有两种,一种是normalMsg,另一种是configMsg,所以这里用了两种方式接收,Order方法接收normalMsg,Configure方法接收configMsg,都将接收到的交易写入到sendChan中

func (ch *chain) Order(env *cb.Envelope, configSeq uint64) error {
   select {
   case ch.sendChan <- &message{
      configSeq: configSeq,
      normalMsg: env,
   }:
      return nil
   case <-ch.exitChan:
      return fmt.Errorf("Exiting")
   }
}

func (ch *chain) Configure(config *cb.Envelope, configSeq uint64) error {
   select {
   case ch.sendChan <- &message{
      configSeq: configSeq,
      configMsg: config,
   }:
      return nil
   case <-ch.exitChan:
      return fmt.Errorf("Exiting")
   }
}
2.3处理交易
  1. 首先检查seq,如果msg.configSeq < seq,说明配置发生更新,需重新校验交易的有效性
  2. 依据出块机制,生成batchs,一个batch对应一个block
  3. 将batchs打包成区块,写入orderer账本
  4. 设置timer,处理pending中的交易,这里的timer设置很重要,试想一下,如果不设置timer,那么直到新的交易来临时,pending中的交易都不会有机会出块
func (ch *chain) main() {
   var timer <-chan time.Time
   var err error
   for {
      seq := ch.support.Sequence()  //获取最新前配置交易区块号
      err = nil
      select {
      case msg := <-ch.sendChan:
         if msg.configMsg == nil {
            // 处理普通交易
             
            //如果校验该交易时的configSeq小于当前seq,说明配置发生更新,需重新校验交易的有效性
            if msg.configSeq < seq {
               _, err = ch.support.ProcessNormalMsg(msg.normalMsg)
               if err != nil {
                  logger.Warningf("Discarding bad normal message: %s", err)
                  continue
               }
            }
             
            //batchs将会被打包成区块,一个batch对应一个block
            //pendind 表示 pendingBatch(可看作交易池)中是否还存在未处理交易 
            batches, pending := ch.support.BlockCutter().Ordered(msg.normalMsg)

            //出块 
            for _, batch := range batches {
               block := ch.support.CreateNextBlock(batch)
               ch.support.WriteBlock(block, nil)
            }

            switch {
            case timer != nil && !pending:
               timer = nil
            case timer == nil && pending:
                //设置timer,用于case <-timer分支来处理pending中的交易
               timer = time.After(ch.support.SharedConfig().BatchTimeout())
               logger.Debugf("Just began %s batch timer", ch.support.SharedConfig().BatchTimeout().String())
            default:
            }

         } else {
            // 处理配置交易
            if msg.configSeq < seq {
               msg.configMsg, _, err = ch.support.ProcessConfigMsg(msg.configMsg)
               if err != nil {
                  logger.Warningf("Discarding bad config message: %s", err)
                  continue
               }
            }
            batch := ch.support.BlockCutter().Cut()
            if batch != nil {
               block := ch.support.CreateNextBlock(batch)
               ch.support.WriteBlock(block, nil)
            }

            block := ch.support.CreateNextBlock([]*cb.Envelope{msg.configMsg})
            ch.support.WriteConfigBlock(block, nil)
            timer = nil
         }
      case <-timer:
         //clear the timer
         timer = nil

         batch := ch.support.BlockCutter().Cut()
         if len(batch) == 0 {
            logger.Warningf("Batch timer expired with no pending requests, this might indicate a bug")
            continue
         }
         logger.Debugf("Batch timer expired, creating block")
         block := ch.support.CreateNextBlock(batch)
         ch.support.WriteBlock(block, nil)
      case <-ch.exitChan:
         logger.Debugf("Exiting")
         return
      }
   }
}
2.3.1 出块机制

​ ch.support.BlockCutter().Ordered()是依据出块机制来进行出块的,出块机制的配置由configtx.yaml文件给出

Orderer: &OrdererDefaults

    # Orderer Type: The orderer implementation to start.
    # Available types are "solo", "kafka" and "etcdraft".
    OrdererType: solo

    # Addresses used to be the list of orderer addresses that clients and peers
    # could connect to.  However, this does not allow clients to associate orderer
    # addresses and orderer organizations which can be useful for things such
    # as TLS validation.  The preferred way to specify orderer addresses is now
    # to include the OrdererEndpoints item in your org definition
    Addresses:
        # - 127.0.0.1:7050

    # Batch Timeout: The amount of time to wait before creating a batch.
    BatchTimeout: 2s   //ch.support.SharedConfig().BatchTimeout()  timer的触发间隔

    # Batch Size: Controls the number of messages batched into a block.
    # The orderer views messages opaquely, but typically, messages may
    # be considered to be Fabric transactions.  The 'batch' is the group
    # of messages in the 'data' field of the block.  Blocks will be a few kb
    # larger than the batch size, when signatures, hashes, and other metadata
    # is applied.
    BatchSize:

        # Max Message Count: The maximum number of messages to permit in a
        # batch.  No block will contain more than this number of messages.
        MaxMessageCount: 500

        # Absolute Max Bytes: The absolute maximum number of bytes allowed for
        # the serialized messages in a batch. The maximum block size is this value
        # plus the size of the associated metadata (usually a few KB depending
        # upon the size of the signing identities). Any transaction larger than
        # this value will be rejected by ordering. If the "kafka" OrdererType is
        # selected, set 'message.max.bytes' and 'replica.fetch.max.bytes' on
        # the Kafka brokers to a value that is larger than this one.
        AbsoluteMaxBytes: 10 MB

        # Preferred Max Bytes: The preferred maximum number of bytes allowed
        # for the serialized messages in a batch. Roughly, this field may be considered
        # the best effort maximum size of a batch. A batch will fill with messages
        # until this size is reached (or the max message count, or batch timeout is
        # exceeded).  If adding a new message to the batch would cause the batch to
        # exceed the preferred max bytes, then the current batch is closed and written
        # to a block, and a new batch containing the new message is created.  If a
        # message larger than the preferred max bytes is received, then its batch
        # will contain only that message.  Because messages may be larger than
        # preferred max bytes (up to AbsoluteMaxBytes), some batches may exceed
        # the preferred max bytes, but will always contain exactly one transaction.
        PreferredMaxBytes: 2 MB

​ 下面结合代码具体看下出块机制

func (r *receiver) Ordered(msg *cb.Envelope) (messageBatches [][]*cb.Envelope, pending bool) {
   if len(r.pendingBatch) == 0 {
      // We are beginning a new batch, mark the time
      r.PendingBatchStartTime = time.Now()
   }

   //获取orderer的出块配置
   ordererConfig, ok := r.sharedConfigFetcher.OrdererConfig()  
   if !ok {
      logger.Panicf("Could not retrieve orderer config to query batch parameters, block cutting is not possible")
   }

   batchSize := ordererConfig.BatchSize()

   messageSizeBytes := messageSizeBytes(msg)
    
   //如果当前交易的size大于PreferredMaxBytes,此交易单独作为一个区块,进行出块
   //如果pendingBatch中还有未处理的交易,pendingBatch也作为一个区块,进行出块
   if messageSizeBytes > batchSize.PreferredMaxBytes {
      logger.Debugf("The current message, with %v bytes, is larger than the preferred batch size of %v bytes and will be isolated.", messageSizeBytes, batchSize.PreferredMaxBytes)

      // cut pending batch, if it has any messages
      if len(r.pendingBatch) > 0 {
         messageBatch := r.Cut()
         messageBatches = append(messageBatches, messageBatch)
      }

      // create new batch with single message
      messageBatches = append(messageBatches, []*cb.Envelope{msg})

      // Record that this batch took no time to fill
      r.Metrics.BlockFillDuration.With("channel", r.ChannelID).Observe(0)

      return
   }

   messageWillOverflowBatchSizeBytes := r.pendingBatchSizeBytes+messageSizeBytes > batchSize.PreferredMaxBytes

   //如果pendingBatch的size + 当前交易的size > PreferredMaxBytes,将pendingBatch中的交易出块,当前交易放进pendingBatch中
   //否则,将当前交易放进pendingBatch中,判断pendingBatch中的交易个数是否大于MaxMessageCount,如果大于,将pendingBatch中的交易出块
   if messageWillOverflowBatchSizeBytes {
      logger.Debugf("The current message, with %v bytes, will overflow the pending batch of %v bytes.", messageSizeBytes, r.pendingBatchSizeBytes)
      logger.Debugf("Pending batch would overflow if current message is added, cutting batch now.")
      messageBatch := r.Cut()
      r.PendingBatchStartTime = time.Now()
      messageBatches = append(messageBatches, messageBatch)
   }

   logger.Debugf("Enqueuing message into batch")
   r.pendingBatch = append(r.pendingBatch, msg)
   r.pendingBatchSizeBytes += messageSizeBytes
   pending = true

   if uint32(len(r.pendingBatch)) >= batchSize.MaxMessageCount {
      logger.Debugf("Batch size met, cutting batch")
      messageBatch := r.Cut()
      messageBatches = append(messageBatches, messageBatch)
      pending = false
   }

   return
  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值