Fabric solo源码分析之单元测试部分(2)

Fabric solo源码分析之单元测试(2)

Fabric solo共识很简单,其本身就是为Fabric的开发人员做实验用的。通过这个简单的本地可运行的共识,能够让开发人员在本地开发共识外的代码。也正是这样,我们可以本地运行solo共识,分析Fabric其他部分的代码。
这里先通过solo共识的单元测试及外围的辅助代码,分析Fabric的共识流程和系统逻辑。

fabric solo单元测试过程涉及到的文件:

  1. hyperledger/fabric/orderer/consensus/solo/ 共识部分及其单元测试
  2. hyperledger/fabric/orderer/mocks/common/blockcutter/ 切分块部分
  3. hyperledger/fabric/orderer/mocks/common/multichannel/ 写入账本的部分

func TestLargeMsgStyleMultiBatch(t *testing.T)

该函数,测试遇到一个内容较大的交易时,将其单独成块的功能。
处理包括:

  1. 清理之前的交易,尽管还未满足成块条件,但为了满足交易的出块顺序,此时会将其分割成块;
  2. 接着,将此次内容较大的交易单独成块;
  3. 最后将这两个块的内容返回,写入账本。

具体操作在 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
	}

具体函数,见

  1. 单元测试函数:
    func TestLargeMsgStyleMultiBatch(t *testing.T)
  2. solo算法main,可打印出两个块的内容
  3. mock下package blockcutter的排序函数:
    func (mbc *Receiver) Ordered(env *cb.Envelope) ([][]*cb.Envelope, bool)

func TestConfigMsg(t *testing.T)

该函数测试配置消息生成块的功能,配置消息为了尽快生效是不需要等待累积到满足生成块的条件,但配置消息之前,可能会有累积的消息,所以要先清理掉之前的消息,生成块;之后再将配置消息单独生成一个块。
即,发送配置消息,要生成两个块:

  1. 重配置前,累积的尚未满足成块条件的消息,要生成一个块
  2. 配置消息单独生成一个块

两个块的生成在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

具体相关函数,见:

  1. 单元测试函数func TestConfigMsg(t *testing.T)
  2. solo算法的main函数
  3. mocks的bockcutter package的Ordered函数,这里不用太关心

func TestRecoverFromError(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")
	}
}

涉及的相关函数,见:

  1. solo单元测试函数func TestRecoverFromError(t *testing.T)
  2. solo共识的main函数超时出块的空块检测部分
    //这是计时器时间到了,进行分块
    batch := ch.support.BlockCutter().Cut()
    if len(batch) == 0 {
        logger.Warningf("Batch timer expired with no pending requests, this might indicate a bug")
        continue
    }
  1. mocks blockcutter的func (mbc *Receiver) Ordered(env *cb.Envelope) ([][]*cb.Envelope, bool),正常的分块处理,这里不用太关心

func TestRevalidation(t *testing.T)

该函数,应该是测试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:
	}
}

具体相关函数,见:

  1. 单元测试函数func TestRevalidation(t *testing.T)
  2. solo算法的main函数
    //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
            }
        }
  1. mocks的bockcutter package的Ordered函数

你可能感兴趣的:(区块链,Hyperledger,Fabric)