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