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