github.com/MetalBlockchain/subnet-evm@v0.4.9/core/chain_indexer_test.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2017 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package core 28 29 import ( 30 "context" 31 "errors" 32 "fmt" 33 "math/big" 34 "math/rand" 35 "testing" 36 "time" 37 38 "github.com/MetalBlockchain/subnet-evm/core/rawdb" 39 "github.com/MetalBlockchain/subnet-evm/core/types" 40 "github.com/ethereum/go-ethereum/common" 41 ) 42 43 // Runs multiple tests with randomized parameters. 44 func TestChainIndexerSingle(t *testing.T) { 45 for i := 0; i < 10; i++ { 46 testChainIndexer(t, 1) 47 } 48 } 49 50 // Runs multiple tests with randomized parameters and different number of 51 // chain backends. 52 func TestChainIndexerWithChildren(t *testing.T) { 53 for i := 2; i < 8; i++ { 54 testChainIndexer(t, i) 55 } 56 } 57 58 // testChainIndexer runs a test with either a single chain indexer or a chain of 59 // multiple backends. The section size and required confirmation count parameters 60 // are randomized. 61 func testChainIndexer(t *testing.T, count int) { 62 db := rawdb.NewMemoryDatabase() 63 defer db.Close() 64 65 // Create a chain of indexers and ensure they all report empty 66 backends := make([]*testChainIndexBackend, count) 67 for i := 0; i < count; i++ { 68 var ( 69 sectionSize = uint64(rand.Intn(100) + 1) 70 confirmsReq = uint64(rand.Intn(10)) 71 ) 72 backends[i] = &testChainIndexBackend{t: t, processCh: make(chan uint64)} 73 backends[i].indexer = NewChainIndexer(db, rawdb.NewTable(db, string([]byte{byte(i)})), backends[i], sectionSize, confirmsReq, 0, fmt.Sprintf("indexer-%d", i)) 74 75 if sections, _, _ := backends[i].indexer.Sections(); sections != 0 { 76 t.Fatalf("Canonical section count mismatch: have %v, want %v", sections, 0) 77 } 78 if i > 0 { 79 backends[i-1].indexer.AddChildIndexer(backends[i].indexer) 80 } 81 } 82 defer backends[0].indexer.Close() // parent indexer shuts down children 83 // notify pings the root indexer about a new head or reorg, then expect 84 // processed blocks if a section is processable 85 notify := func(headNum, failNum uint64, reorg bool) { 86 backends[0].indexer.newHead(headNum, reorg) 87 if reorg { 88 for _, backend := range backends { 89 headNum = backend.reorg(headNum) 90 backend.assertSections() 91 } 92 return 93 } 94 var cascade bool 95 for _, backend := range backends { 96 headNum, cascade = backend.assertBlocks(headNum, failNum) 97 if !cascade { 98 break 99 } 100 backend.assertSections() 101 } 102 } 103 // inject inserts a new random canonical header into the database directly 104 inject := func(number uint64) { 105 header := &types.Header{Number: big.NewInt(int64(number)), Extra: big.NewInt(rand.Int63()).Bytes()} 106 if number > 0 { 107 header.ParentHash = rawdb.ReadCanonicalHash(db, number-1) 108 } 109 rawdb.WriteHeader(db, header) 110 rawdb.WriteCanonicalHash(db, header.Hash(), number) 111 } 112 // Start indexer with an already existing chain 113 for i := uint64(0); i <= 100; i++ { 114 inject(i) 115 } 116 notify(100, 100, false) 117 118 // Add new blocks one by one 119 for i := uint64(101); i <= 1000; i++ { 120 inject(i) 121 notify(i, i, false) 122 } 123 // Do a reorg 124 notify(500, 500, true) 125 126 // Create new fork 127 for i := uint64(501); i <= 1000; i++ { 128 inject(i) 129 notify(i, i, false) 130 } 131 for i := uint64(1001); i <= 1500; i++ { 132 inject(i) 133 } 134 // Failed processing scenario where less blocks are available than notified 135 notify(2000, 1500, false) 136 137 // Notify about a reorg (which could have caused the missing blocks if happened during processing) 138 notify(1500, 1500, true) 139 140 // Create new fork 141 for i := uint64(1501); i <= 2000; i++ { 142 inject(i) 143 notify(i, i, false) 144 } 145 } 146 147 // testChainIndexBackend implements ChainIndexerBackend 148 type testChainIndexBackend struct { 149 t *testing.T 150 indexer *ChainIndexer 151 section, headerCnt, stored uint64 152 processCh chan uint64 153 } 154 155 // assertSections verifies if a chain indexer has the correct number of section. 156 func (b *testChainIndexBackend) assertSections() { 157 // Keep trying for 3 seconds if it does not match 158 var sections uint64 159 for i := 0; i < 300; i++ { 160 sections, _, _ = b.indexer.Sections() 161 if sections == b.stored { 162 return 163 } 164 time.Sleep(10 * time.Millisecond) 165 } 166 b.t.Fatalf("Canonical section count mismatch: have %v, want %v", sections, b.stored) 167 } 168 169 // assertBlocks expects processing calls after new blocks have arrived. If the 170 // failNum < headNum then we are simulating a scenario where a reorg has happened 171 // after the processing has started and the processing of a section fails. 172 func (b *testChainIndexBackend) assertBlocks(headNum, failNum uint64) (uint64, bool) { 173 var sections uint64 174 if headNum >= b.indexer.confirmsReq { 175 sections = (headNum + 1 - b.indexer.confirmsReq) / b.indexer.sectionSize 176 if sections > b.stored { 177 // expect processed blocks 178 for expectd := b.stored * b.indexer.sectionSize; expectd < sections*b.indexer.sectionSize; expectd++ { 179 if expectd > failNum { 180 // rolled back after processing started, no more process calls expected 181 // wait until updating is done to make sure that processing actually fails 182 var updating bool 183 for i := 0; i < 300; i++ { 184 b.indexer.lock.Lock() 185 updating = b.indexer.knownSections > b.indexer.storedSections 186 b.indexer.lock.Unlock() 187 if !updating { 188 break 189 } 190 time.Sleep(10 * time.Millisecond) 191 } 192 if updating { 193 b.t.Fatalf("update did not finish") 194 } 195 sections = expectd / b.indexer.sectionSize 196 break 197 } 198 select { 199 case <-time.After(10 * time.Second): 200 b.t.Fatalf("Expected processed block #%d, got nothing", expectd) 201 case processed := <-b.processCh: 202 if processed != expectd { 203 b.t.Errorf("Expected processed block #%d, got #%d", expectd, processed) 204 } 205 } 206 } 207 b.stored = sections 208 } 209 } 210 if b.stored == 0 { 211 return 0, false 212 } 213 return b.stored*b.indexer.sectionSize - 1, true 214 } 215 216 func (b *testChainIndexBackend) reorg(headNum uint64) uint64 { 217 firstChanged := (headNum + 1) / b.indexer.sectionSize 218 if firstChanged < b.stored { 219 b.stored = firstChanged 220 } 221 return b.stored * b.indexer.sectionSize 222 } 223 224 func (b *testChainIndexBackend) Reset(ctx context.Context, section uint64, prevHead common.Hash) error { 225 b.section = section 226 b.headerCnt = 0 227 return nil 228 } 229 230 func (b *testChainIndexBackend) Process(ctx context.Context, header *types.Header) error { 231 b.headerCnt++ 232 if b.headerCnt > b.indexer.sectionSize { 233 b.t.Error("Processing too many headers") 234 } 235 //t.processCh <- header.Number.Uint64() 236 select { 237 case <-time.After(10 * time.Second): 238 b.t.Error("Unexpected call to Process") 239 // Can't use Fatal since this is not the test's goroutine. 240 // Returning error stops the chainIndexer's updateLoop 241 return errors.New("Unexpected call to Process") 242 case b.processCh <- header.Number.Uint64(): 243 } 244 return nil 245 } 246 247 func (b *testChainIndexBackend) Commit() error { 248 if b.headerCnt != b.indexer.sectionSize { 249 b.t.Error("Not enough headers processed") 250 } 251 return nil 252 } 253 254 func (b *testChainIndexBackend) Prune(threshold uint64) error { 255 return nil 256 }