github.com/hechain20/hechain@v0.0.0-20220316014945-b544036ba106/orderer/consensus/etcdraft/eviction_test.go (about)

     1  /*
     2  Copyright hechain. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package etcdraft
     8  
     9  import (
    10  	"strings"
    11  	"sync/atomic"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/hechain20/hechain/common/flogging"
    16  	"github.com/hechain20/hechain/orderer/common/cluster"
    17  	"github.com/hechain20/hechain/orderer/common/cluster/mocks"
    18  	"github.com/hechain20/hechain/protoutil"
    19  	"github.com/hyperledger/fabric-protos-go/common"
    20  	"github.com/onsi/gomega"
    21  	"github.com/pkg/errors"
    22  	"github.com/stretchr/testify/require"
    23  	"go.etcd.io/etcd/raft/raftpb"
    24  	"go.uber.org/zap"
    25  	"go.uber.org/zap/zapcore"
    26  )
    27  
    28  func TestPeriodicCheck(t *testing.T) {
    29  	t.Parallel()
    30  
    31  	g := gomega.NewGomegaWithT(t)
    32  
    33  	var cond uint32
    34  	var checkNum uint32
    35  
    36  	fiveChecks := func() bool {
    37  		return atomic.LoadUint32(&checkNum) > uint32(5)
    38  	}
    39  
    40  	condition := func() bool {
    41  		atomic.AddUint32(&checkNum, 1)
    42  		return atomic.LoadUint32(&cond) == uint32(1)
    43  	}
    44  
    45  	reports := make(chan time.Duration, 1000)
    46  
    47  	report := func(duration time.Duration) {
    48  		reports <- duration
    49  	}
    50  
    51  	clears := make(chan struct{}, 1000)
    52  
    53  	reportCleared := func() {
    54  		clears <- struct{}{}
    55  	}
    56  
    57  	check := &PeriodicCheck{
    58  		Logger:        flogging.MustGetLogger("test"),
    59  		Condition:     condition,
    60  		CheckInterval: time.Millisecond,
    61  		Report:        report,
    62  		ReportCleared: reportCleared,
    63  	}
    64  
    65  	go check.Run()
    66  
    67  	g.Eventually(fiveChecks, time.Minute, time.Millisecond).Should(gomega.BeTrue())
    68  	// trigger condition to be true
    69  	atomic.StoreUint32(&cond, 1)
    70  	g.Eventually(reports, time.Minute, time.Millisecond).Should(gomega.Not(gomega.BeEmpty()))
    71  	// read first report
    72  	firstReport := <-reports
    73  	g.Eventually(reports, time.Minute, time.Millisecond).Should(gomega.Not(gomega.BeEmpty()))
    74  	// read second report
    75  	secondReport := <-reports
    76  	// time increases between reports
    77  	g.Expect(secondReport).To(gomega.BeNumerically(">", firstReport))
    78  	// wait for the reports channel to be full
    79  	g.Eventually(func() int { return len(reports) }, time.Minute, time.Millisecond).Should(gomega.BeNumerically("==", 1000))
    80  
    81  	// trigger condition to be false
    82  	atomic.StoreUint32(&cond, 0)
    83  
    84  	var lastReport time.Duration
    85  	// drain the reports channel
    86  	for len(reports) > 0 {
    87  		select {
    88  		case report := <-reports:
    89  			lastReport = report
    90  		default:
    91  		}
    92  	}
    93  
    94  	g.Eventually(clears).Should(gomega.Receive())
    95  	g.Consistently(clears).ShouldNot(gomega.Receive())
    96  
    97  	// ensure the checks have been made
    98  	checksDoneSoFar := atomic.LoadUint32(&checkNum)
    99  	g.Consistently(reports, time.Second*2, time.Millisecond).Should(gomega.BeEmpty())
   100  	checksDoneAfter := atomic.LoadUint32(&checkNum)
   101  	g.Expect(checksDoneAfter).To(gomega.BeNumerically(">", checksDoneSoFar))
   102  	// but nothing has been reported
   103  	g.Expect(reports).To(gomega.BeEmpty())
   104  
   105  	// trigger the condition again
   106  	atomic.StoreUint32(&cond, 1)
   107  	g.Eventually(reports, time.Minute, time.Millisecond).Should(gomega.Not(gomega.BeEmpty()))
   108  	// The first report is smaller than the last report,
   109  	// so the countdown has been reset when the condition was reset
   110  	firstReport = <-reports
   111  	g.Expect(lastReport).To(gomega.BeNumerically(">", firstReport))
   112  	// Stop the periodic check.
   113  	check.Stop()
   114  	checkCountAfterStop := atomic.LoadUint32(&checkNum)
   115  	// Wait 50 times the check interval.
   116  	time.Sleep(check.CheckInterval * 50)
   117  	// Ensure that we cease checking the condition, hence the PeriodicCheck is stopped.
   118  	g.Expect(atomic.LoadUint32(&checkNum)).To(gomega.BeNumerically("<", checkCountAfterStop+2))
   119  	g.Consistently(clears).ShouldNot(gomega.Receive())
   120  }
   121  
   122  func TestEvictionSuspector(t *testing.T) {
   123  	configBlock := &common.Block{
   124  		Header: &common.BlockHeader{Number: 9},
   125  		Metadata: &common.BlockMetadata{
   126  			Metadata: [][]byte{{}, {}, {}, {}},
   127  		},
   128  	}
   129  	configBlock.Metadata.Metadata[common.BlockMetadataIndex_SIGNATURES] = protoutil.MarshalOrPanic(&common.Metadata{
   130  		Value: protoutil.MarshalOrPanic(&common.OrdererBlockMetadata{
   131  			LastConfig: &common.LastConfig{Index: 9},
   132  		}),
   133  	})
   134  
   135  	puller := &mocks.ChainPuller{}
   136  	puller.On("Close")
   137  	puller.On("HeightsByEndpoints").Return(map[string]uint64{"foo": 10}, nil)
   138  	puller.On("PullBlock", uint64(9)).Return(configBlock)
   139  
   140  	for _, testCase := range []struct {
   141  		description                 string
   142  		expectedPanic               string
   143  		expectedLog                 string
   144  		expectedCommittedBlockCount int
   145  		amIInChannelReturns         error
   146  		evictionSuspicionThreshold  time.Duration
   147  		blockPuller                 BlockPuller
   148  		blockPullerErr              error
   149  		height                      uint64
   150  		timesTriggered              int
   151  		halt                        func()
   152  	}{
   153  		{
   154  			description:                "suspected time is lower than threshold",
   155  			evictionSuspicionThreshold: 11 * time.Minute,
   156  			halt:                       t.Fail,
   157  		},
   158  		{
   159  			description:                "timesTriggered multiplier prevents threshold",
   160  			evictionSuspicionThreshold: 6 * time.Minute,
   161  			timesTriggered:             1,
   162  			halt:                       t.Fail,
   163  		},
   164  		{
   165  			description:                "puller creation fails",
   166  			evictionSuspicionThreshold: 10*time.Minute - time.Second,
   167  			blockPullerErr:             errors.New("oops"),
   168  			expectedPanic:              "Failed creating a block puller: oops",
   169  			halt:                       t.Fail,
   170  		},
   171  		{
   172  			description:                "failed pulling the block",
   173  			expectedLog:                "Cannot confirm our own eviction from the channel: bad block",
   174  			evictionSuspicionThreshold: 10*time.Minute - time.Second,
   175  			amIInChannelReturns:        errors.New("bad block"),
   176  			blockPuller:                puller,
   177  			height:                     9,
   178  			halt:                       t.Fail,
   179  		},
   180  		{
   181  			description:                "we are still in the channel",
   182  			expectedLog:                "Cannot confirm our own eviction from the channel, our certificate was found in config block with sequence 9",
   183  			evictionSuspicionThreshold: 10*time.Minute - time.Second,
   184  			amIInChannelReturns:        nil,
   185  			blockPuller:                puller,
   186  			height:                     9,
   187  			halt:                       t.Fail,
   188  		},
   189  		{
   190  			description:                "our height is the highest",
   191  			expectedLog:                "Our height is higher or equal than the height of the orderer we pulled the last block from, aborting",
   192  			evictionSuspicionThreshold: 10*time.Minute - time.Second,
   193  			amIInChannelReturns:        cluster.ErrNotInChannel,
   194  			blockPuller:                puller,
   195  			height:                     10,
   196  			halt:                       func() {},
   197  		},
   198  		{
   199  			description:                 "we are not in the channel",
   200  			expectedLog:                 "Detected our own eviction from the channel in block [9]",
   201  			evictionSuspicionThreshold:  10*time.Minute - time.Second,
   202  			amIInChannelReturns:         cluster.ErrNotInChannel,
   203  			blockPuller:                 puller,
   204  			height:                      8,
   205  			expectedCommittedBlockCount: 2,
   206  			halt: func() {
   207  				puller.On("PullBlock", uint64(8)).Return(&common.Block{
   208  					Header: &common.BlockHeader{Number: 8},
   209  					Metadata: &common.BlockMetadata{
   210  						Metadata: [][]byte{{}, {}, {}, {}},
   211  					},
   212  				})
   213  			},
   214  		},
   215  	} {
   216  		testCase := testCase
   217  		t.Run(testCase.description, func(t *testing.T) {
   218  			committedBlocks := make(chan *common.Block, 2)
   219  
   220  			commitBlock := func(block *common.Block) error {
   221  				committedBlocks <- block
   222  				return nil
   223  			}
   224  
   225  			es := &evictionSuspector{
   226  				halt: testCase.halt,
   227  				amIInChannel: func(_ *common.Block) error {
   228  					return testCase.amIInChannelReturns
   229  				},
   230  				evictionSuspicionThreshold: testCase.evictionSuspicionThreshold,
   231  				createPuller: func() (BlockPuller, error) {
   232  					return testCase.blockPuller, testCase.blockPullerErr
   233  				},
   234  				writeBlock: commitBlock,
   235  				height: func() uint64 {
   236  					return testCase.height
   237  				},
   238  				logger:         flogging.MustGetLogger("test"),
   239  				triggerCatchUp: func(sn *raftpb.Snapshot) {},
   240  				timesTriggered: testCase.timesTriggered,
   241  			}
   242  
   243  			foundExpectedLog := testCase.expectedLog == ""
   244  			es.logger = es.logger.WithOptions(zap.Hooks(func(entry zapcore.Entry) error {
   245  				if strings.Contains(entry.Message, testCase.expectedLog) {
   246  					foundExpectedLog = true
   247  				}
   248  				return nil
   249  			}))
   250  
   251  			runTestCase := func() {
   252  				es.confirmSuspicion(time.Minute * 10)
   253  			}
   254  
   255  			if testCase.expectedPanic != "" {
   256  				require.PanicsWithValue(t, testCase.expectedPanic, runTestCase)
   257  			} else {
   258  				runTestCase()
   259  				// Run the test case again.
   260  				// Conditions that do not lead to a conclusion of a chain eviction
   261  				// should be idempotent.
   262  				// Conditions that do lead to conclusion of a chain eviction
   263  				// in the second time - should result in a no-op.
   264  				runTestCase()
   265  			}
   266  
   267  			require.True(t, foundExpectedLog, "expected to find %s but didn't", testCase.expectedLog)
   268  			require.Equal(t, testCase.expectedCommittedBlockCount, len(committedBlocks))
   269  		})
   270  	}
   271  }