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处理交易
- 首先检查seq,如果msg.configSeq < seq,说明配置发生更新,需重新校验交易的有效性
- 依据出块机制,生成batchs,一个batch对应一个block
- 将batchs打包成区块,写入orderer账本
- 设置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