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 }