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