Fabric solo共识很简单,其本身就是为Fabric的开发人员做实验用的。通过这个简单的本地可运行的共识,能够让开发人员在本地开发共识外的代码。也正是这样,我们可以本地运行solo共识,分析Fabric其他部分的代码。
这里先通过solo共识的单元测试及外围的辅助代码,分析Fabric的共识流程和系统逻辑。
fabric solo单元测试过程涉及到的文件:
该函数,测试遇到一个内容较大的交易时,将其单独成块的功能。
处理包括:
具体操作在 mock下package blockcutter的排序函数:
func (mbc *Receiver) Ordered(env *cb.Envelope) ([][]*cb.Envelope, bool)
中的部分:
//切割成两个块:
//1. 之前未满足成块条件下,积累的交易,切割成块1
//2. 将当前内容较大的交易,单独切割成块2
if mbc.IsolatedTx {
logger.Debugf("Receiver: Returning dual batch")
log.Println("Receiver: Returning dual batch")
//[]{块1,块2}
res := [][]*cb.Envelope{mbc.CurBatch, {env}}
//直接写入账本,已清空
mbc.CurBatch = nil
return res, false
}
具体函数,见
func TestLargeMsgStyleMultiBatch(t *testing.T)
func (mbc *Receiver) Ordered(env *cb.Envelope) ([][]*cb.Envelope, bool)
该函数测试配置消息生成块的功能,配置消息为了尽快生效是不需要等待累积到满足生成块的条件,但配置消息之前,可能会有累积的消息,所以要先清理掉之前的消息,生成块;之后再将配置消息单独生成一个块。
即,发送配置消息,要生成两个块:
两个块的生成在solo共识算法的main函数func (ch *chain) main()
中:
//直接切割,不会想普通消息那样还要排序
//1. 这个不是config,这个是普通的累积的消息,清理solo中的消息,这里创建第一个块
batch := ch.support.BlockCutter().Cut()
if batch != nil {
log.Println(batch)
//创建块
block := ch.support.CreateNextBlock(batch)
ch.support.WriteBlock(block, nil)
}
//1. 清理完之前累积的交易,后紧接着创建配置消息块,这里是第二个块,
// 配置消息要快速生效,所以不会累积满足生成块条件
block := ch.support.CreateNextBlock([]*cb.Envelope{msg.configMsg})
log.Println([]*cb.Envelope{msg.configMsg})
ch.support.WriteConfigBlock(block, nil)
timer = nil
具体相关函数,见:
func TestConfigMsg(t *testing.T)
这个函数就是简单的测试solo共识系统错误后的恢复功能,这里的测试就没意思了,这里模拟系统错误是将curBatch设置为nil,即累积的消息丢失。当满足出块条件时,solo检测到是空块,直接丢掉,并报告异常消息;接着如果系统恢复,再次发送消息,整个系统就类似于系统刚启动。可能这里要模拟的系统错误,就是系统宕机,消息丢失吧。
这里在单元测试func TestRecoverFromError(t *testing.T)
函数中,模拟系统错误,造成系统丢失:
//测试solo共识简单的系统错误后的恢复
// This test checks that solo consenter could recover from an erroneous situation
// where empty batch is cut
func TestRecoverFromError(t *testing.T) {
batchTimeout, _ := time.ParseDuration("1ms")
support := &mockmultichannel.ConsenterSupport{
Blocks: make(chan *cb.Block),
BlockCutterVal: mockblockcutter.NewReceiver(),
SharedConfigVal: &mockconfig.Orderer{BatchTimeoutVal: batchTimeout},
}
defer close(support.BlockCutterVal.Block)
bs := newChain(support)
_ = goWithWait(bs.main)
defer bs.Halt()
//消息同步完成后,才会继续执行后续代码
syncQueueMessage(testMessage, bs, support.BlockCutterVal)
//累积消息记录在CurBatch上,这里设置为nil,切块时会得到空块
//在solo中检测到空块,没有写入账本,直接丢掉了,相当于交易丢失
//即上述操作异常,应报告给客户端
support.BlockCutterVal.CurBatch = nil
select {
case <-support.Blocks:
t.Fatalf("Expected no invocations of Append")
case <-time.After(2 * time.Millisecond):
log.Println("时间到了生成块")
}
//系统恢复
//下个消息切块
support.BlockCutterVal.CutNext = true
syncQueueMessage(testMessage, bs, support.BlockCutterVal)
select {
case <-support.Blocks:
case <-time.After(time.Second):
t.Fatalf("Expected block to be cut")
}
}
涉及的相关函数,见:
func TestRecoverFromError(t *testing.T)
//这是计时器时间到了,进行分块
batch := ch.support.BlockCutter().Cut()
if len(batch) == 0 {
logger.Warningf("Batch timer expired with no pending requests, this might indicate a bug")
continue
}
func (mbc *Receiver) Ordered(env *cb.Envelope) ([][]*cb.Envelope, bool)
,正常的分块处理,这里不用太关心该函数,应该是测试solo共识在接收到消息时,能够正确处理异常消息吧,这个异常,应该是在正式共识下,或者在非mocks下的交易验证的结果吧。
这里的单元测试,发现有些没有逻辑,只是对solo共识的覆盖测试,并且单元测试都是用的mocks即模拟的部分,后续要系统运行起来测试分析。
这里测试了配置和普通两种消息的验证结果的处理情况,具体见:
//这就是在验证solo共识的接收到消息时,是否有异常,这应该是在正式共识下验证交易的结果吧;也可能是非mocks下的对交易的验证结果
// This test checks that solo consenter re-validates message if config sequence has advanced
func TestRevalidation(t *testing.T) {
batchTimeout, _ := time.ParseDuration("1h")
support := &mockmultichannel.ConsenterSupport{
Blocks: make(chan *cb.Block),
BlockCutterVal: mockblockcutter.NewReceiver(),
SharedConfigVal: &mockconfig.Orderer{BatchTimeoutVal: batchTimeout},
SequenceVal: uint64(1),
}
defer close(support.BlockCutterVal.Block)
bs := newChain(support)
wg := goWithWait(bs.main)
defer bs.Halt()
//配置消息下的
t.Run("ConfigMsg", func(t *testing.T) {
//消息body
support.ProcessConfigMsgVal = testMessage
//为设置错误信息,即是正常的情况
//配置消息
t.Run("Valid", func(t *testing.T) {
assert.Nil(t, bs.Configure(testMessage, 0))
select {
case <-support.Blocks:
case <-time.After(time.Second):
t.Fatalf("Expected one block to be cut but never got it")
}
})
t.Run("Invalid", func(t *testing.T) {
//这里设置了错误消息,即表示异常情况
support.ProcessConfigMsgErr = fmt.Errorf("Config message is not valid")
//消息发送到出块部分,solo检测的错误消息
assert.Nil(t, bs.Configure(testMessage, 0))
select {
case <-support.Blocks:
t.Fatalf("Expected no block to be cut")
case <-time.After(100 * time.Millisecond):
}
})
})
//普通消息的验证
t.Run("NormalMsg", func(t *testing.T) {
//这里要用到块,直接切一个
support.BlockCutterVal.CutNext = true
t.Run("Valid", func(t *testing.T) {
syncQueueMessage(testMessage, bs, support.BlockCutterVal)
select {
case <-support.Blocks:
case <-time.After(time.Second):
t.Fatalf("Expected one block to be cut but never got it")
}
})
t.Run("Invalid", func(t *testing.T) {
//没有发送消息,直接设置错误消息,模拟验证错误
support.ProcessNormalMsgErr = fmt.Errorf("Normal message is not valid")
// We are not calling `syncQueueMessage` here because we don't expect
// `Ordered` to be invoked at all in this case, so we don't need to
// synchronize on `support.BlockCutterVal.Block`.
assert.Nil(t, bs.Order(testMessage, 0))
select {
case <-support.Blocks:
t.Fatalf("Expected no block to be cut")
case <-time.After(100 * time.Millisecond):
}
})
})
bs.Halt()
select {
case <-time.After(time.Second):
t.Fatalf("Should have exited")
case <-wg.done:
}
}
具体相关函数,见:
func TestRevalidation(t *testing.T)
//ConfigMsg
if msg.configSeq < seq {
//接收到就的消息,忽略
msg.configMsg, _, err = ch.support.ProcessConfigMsg(msg.configMsg)
if err != nil {
logger.Warningf("Discarding bad config message: %s", err)
continue
}
}
// NormalMsg
if msg.configSeq < seq {
_, err = ch.support.ProcessNormalMsg(msg.normalMsg)
if err != nil {
logger.Warningf("Discarding bad normal message: %s", err)
continue
}
}