github.com/4000d/go-ethereum@v1.8.2-0.20180223170251-423c8bb1d821/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 "fmt" 21 "math/big" 22 "math/rand" 23 "testing" 24 "time" 25 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/core/types" 28 "github.com/ethereum/go-ethereum/ethdb" 29 ) 30 31 // Runs multiple tests with randomized parameters. 32 func TestChainIndexerSingle(t *testing.T) { 33 for i := 0; i < 10; i++ { 34 testChainIndexer(t, 1) 35 } 36 } 37 38 // Runs multiple tests with randomized parameters and different number of 39 // chain backends. 40 func TestChainIndexerWithChildren(t *testing.T) { 41 for i := 2; i < 8; i++ { 42 testChainIndexer(t, i) 43 } 44 } 45 46 // testChainIndexer runs a test with either a single chain indexer or a chain of 47 // multiple backends. The section size and required confirmation count parameters 48 // are randomized. 49 func testChainIndexer(t *testing.T, count int) { 50 db, _ := ethdb.NewMemDatabase() 51 defer db.Close() 52 53 // Create a chain of indexers and ensure they all report empty 54 backends := make([]*testChainIndexBackend, count) 55 for i := 0; i < count; i++ { 56 var ( 57 sectionSize = uint64(rand.Intn(100) + 1) 58 confirmsReq = uint64(rand.Intn(10)) 59 ) 60 backends[i] = &testChainIndexBackend{t: t, processCh: make(chan uint64)} 61 backends[i].indexer = NewChainIndexer(db, ethdb.NewTable(db, string([]byte{byte(i)})), backends[i], sectionSize, confirmsReq, 0, fmt.Sprintf("indexer-%d", i)) 62 63 if sections, _, _ := backends[i].indexer.Sections(); sections != 0 { 64 t.Fatalf("Canonical section count mismatch: have %v, want %v", sections, 0) 65 } 66 if i > 0 { 67 backends[i-1].indexer.AddChildIndexer(backends[i].indexer) 68 } 69 } 70 defer backends[0].indexer.Close() // parent indexer shuts down children 71 // notify pings the root indexer about a new head or reorg, then expect 72 // processed blocks if a section is processable 73 notify := func(headNum, failNum uint64, reorg bool) { 74 backends[0].indexer.newHead(headNum, reorg) 75 if reorg { 76 for _, backend := range backends { 77 headNum = backend.reorg(headNum) 78 backend.assertSections() 79 } 80 return 81 } 82 var cascade bool 83 for _, backend := range backends { 84 headNum, cascade = backend.assertBlocks(headNum, failNum) 85 if !cascade { 86 break 87 } 88 backend.assertSections() 89 } 90 } 91 // inject inserts a new random canonical header into the database directly 92 inject := func(number uint64) { 93 header := &types.Header{Number: big.NewInt(int64(number)), Extra: big.NewInt(rand.Int63()).Bytes()} 94 if number > 0 { 95 header.ParentHash = GetCanonicalHash(db, number-1) 96 } 97 WriteHeader(db, header) 98 WriteCanonicalHash(db, header.Hash(), number) 99 } 100 // Start indexer with an already existing chain 101 for i := uint64(0); i <= 100; i++ { 102 inject(i) 103 } 104 notify(100, 100, false) 105 106 // Add new blocks one by one 107 for i := uint64(101); i <= 1000; i++ { 108 inject(i) 109 notify(i, i, false) 110 } 111 // Do a reorg 112 notify(500, 500, true) 113 114 // Create new fork 115 for i := uint64(501); i <= 1000; i++ { 116 inject(i) 117 notify(i, i, false) 118 } 119 for i := uint64(1001); i <= 1500; i++ { 120 inject(i) 121 } 122 // Failed processing scenario where less blocks are available than notified 123 notify(2000, 1500, false) 124 125 // Notify about a reorg (which could have caused the missing blocks if happened during processing) 126 notify(1500, 1500, true) 127 128 // Create new fork 129 for i := uint64(1501); i <= 2000; i++ { 130 inject(i) 131 notify(i, i, false) 132 } 133 } 134 135 // testChainIndexBackend implements ChainIndexerBackend 136 type testChainIndexBackend struct { 137 t *testing.T 138 indexer *ChainIndexer 139 section, headerCnt, stored uint64 140 processCh chan uint64 141 } 142 143 // assertSections verifies if a chain indexer has the correct number of section. 144 func (b *testChainIndexBackend) assertSections() { 145 // Keep trying for 3 seconds if it does not match 146 var sections uint64 147 for i := 0; i < 300; i++ { 148 sections, _, _ = b.indexer.Sections() 149 if sections == b.stored { 150 return 151 } 152 time.Sleep(10 * time.Millisecond) 153 } 154 b.t.Fatalf("Canonical section count mismatch: have %v, want %v", sections, b.stored) 155 } 156 157 // assertBlocks expects processing calls after new blocks have arrived. If the 158 // failNum < headNum then we are simulating a scenario where a reorg has happened 159 // after the processing has started and the processing of a section fails. 160 func (b *testChainIndexBackend) assertBlocks(headNum, failNum uint64) (uint64, bool) { 161 var sections uint64 162 if headNum >= b.indexer.confirmsReq { 163 sections = (headNum + 1 - b.indexer.confirmsReq) / b.indexer.sectionSize 164 if sections > b.stored { 165 // expect processed blocks 166 for expectd := b.stored * b.indexer.sectionSize; expectd < sections*b.indexer.sectionSize; expectd++ { 167 if expectd > failNum { 168 // rolled back after processing started, no more process calls expected 169 // wait until updating is done to make sure that processing actually fails 170 var updating bool 171 for i := 0; i < 300; i++ { 172 b.indexer.lock.Lock() 173 updating = b.indexer.knownSections > b.indexer.storedSections 174 b.indexer.lock.Unlock() 175 if !updating { 176 break 177 } 178 time.Sleep(10 * time.Millisecond) 179 } 180 if updating { 181 b.t.Fatalf("update did not finish") 182 } 183 sections = expectd / b.indexer.sectionSize 184 break 185 } 186 select { 187 case <-time.After(10 * time.Second): 188 b.t.Fatalf("Expected processed block #%d, got nothing", expectd) 189 case processed := <-b.processCh: 190 if processed != expectd { 191 b.t.Errorf("Expected processed block #%d, got #%d", expectd, processed) 192 } 193 } 194 } 195 b.stored = sections 196 } 197 } 198 if b.stored == 0 { 199 return 0, false 200 } 201 return b.stored*b.indexer.sectionSize - 1, true 202 } 203 204 func (b *testChainIndexBackend) reorg(headNum uint64) uint64 { 205 firstChanged := headNum / b.indexer.sectionSize 206 if firstChanged < b.stored { 207 b.stored = firstChanged 208 } 209 return b.stored * b.indexer.sectionSize 210 } 211 212 func (b *testChainIndexBackend) Reset(section uint64, prevHead common.Hash) error { 213 b.section = section 214 b.headerCnt = 0 215 return nil 216 } 217 218 func (b *testChainIndexBackend) Process(header *types.Header) { 219 b.headerCnt++ 220 if b.headerCnt > b.indexer.sectionSize { 221 b.t.Error("Processing too many headers") 222 } 223 //t.processCh <- header.Number.Uint64() 224 select { 225 case <-time.After(10 * time.Second): 226 b.t.Fatal("Unexpected call to Process") 227 case b.processCh <- header.Number.Uint64(): 228 } 229 } 230 231 func (b *testChainIndexBackend) Commit() error { 232 if b.headerCnt != b.indexer.sectionSize { 233 b.t.Error("Not enough headers processed") 234 } 235 return nil 236 }