github.com/ethereum/go-ethereum@v1.16.1/core/txindexer_test.go (about) 1 // Copyright 2024 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 "math/big" 21 "testing" 22 23 "github.com/ethereum/go-ethereum/common" 24 "github.com/ethereum/go-ethereum/consensus/ethash" 25 "github.com/ethereum/go-ethereum/core/rawdb" 26 "github.com/ethereum/go-ethereum/core/types" 27 "github.com/ethereum/go-ethereum/crypto" 28 "github.com/ethereum/go-ethereum/ethdb" 29 "github.com/ethereum/go-ethereum/params" 30 ) 31 32 func verifyIndexes(t *testing.T, db ethdb.Database, block *types.Block, exist bool) { 33 for _, tx := range block.Transactions() { 34 lookup := rawdb.ReadTxLookupEntry(db, tx.Hash()) 35 if exist && lookup == nil { 36 t.Fatalf("missing %d %x", block.NumberU64(), tx.Hash().Hex()) 37 } 38 if !exist && lookup != nil { 39 t.Fatalf("unexpected %d %x", block.NumberU64(), tx.Hash().Hex()) 40 } 41 } 42 } 43 44 func verify(t *testing.T, db ethdb.Database, blocks []*types.Block, expTail uint64) { 45 tail := rawdb.ReadTxIndexTail(db) 46 if tail == nil { 47 t.Fatal("Failed to write tx index tail") 48 return 49 } 50 if *tail != expTail { 51 t.Fatalf("Unexpected tx index tail, want %v, got %d", expTail, *tail) 52 } 53 for _, b := range blocks { 54 if b.Number().Uint64() < *tail { 55 verifyIndexes(t, db, b, false) 56 } else { 57 verifyIndexes(t, db, b, true) 58 } 59 } 60 } 61 62 func verifyNoIndex(t *testing.T, db ethdb.Database, blocks []*types.Block) { 63 tail := rawdb.ReadTxIndexTail(db) 64 if tail != nil { 65 t.Fatalf("Unexpected tx index tail %d", *tail) 66 } 67 for _, b := range blocks { 68 verifyIndexes(t, db, b, false) 69 } 70 } 71 72 // TestTxIndexer tests the functionalities for managing transaction indexes. 73 func TestTxIndexer(t *testing.T) { 74 var ( 75 testBankKey, _ = crypto.GenerateKey() 76 testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) 77 testBankFunds = big.NewInt(1000000000000000000) 78 79 gspec = &Genesis{ 80 Config: params.TestChainConfig, 81 Alloc: types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, 82 BaseFee: big.NewInt(params.InitialBaseFee), 83 } 84 engine = ethash.NewFaker() 85 nonce = uint64(0) 86 chainHead = uint64(128) 87 ) 88 _, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(chainHead), func(i int, gen *BlockGen) { 89 tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey) 90 gen.AddTx(tx) 91 nonce += 1 92 }) 93 var cases = []struct { 94 limits []uint64 95 tails []uint64 96 }{ 97 { 98 limits: []uint64{0, 1, 64, 129, 0}, 99 tails: []uint64{0, 128, 65, 0, 0}, 100 }, 101 { 102 limits: []uint64{64, 1, 64, 0}, 103 tails: []uint64{65, 128, 65, 0}, 104 }, 105 { 106 limits: []uint64{127, 1, 64, 0}, 107 tails: []uint64{2, 128, 65, 0}, 108 }, 109 { 110 limits: []uint64{128, 1, 64, 0}, 111 tails: []uint64{1, 128, 65, 0}, 112 }, 113 { 114 limits: []uint64{129, 1, 64, 0}, 115 tails: []uint64{0, 128, 65, 0}, 116 }, 117 } 118 for _, c := range cases { 119 db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{}) 120 rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...))) 121 122 // Index the initial blocks from ancient store 123 indexer := &txIndexer{ 124 limit: 0, 125 db: db, 126 } 127 for i, limit := range c.limits { 128 indexer.limit = limit 129 indexer.run(chainHead, make(chan struct{}), make(chan struct{})) 130 verify(t, db, blocks, c.tails[i]) 131 } 132 db.Close() 133 } 134 } 135 136 func TestTxIndexerRepair(t *testing.T) { 137 var ( 138 testBankKey, _ = crypto.GenerateKey() 139 testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) 140 testBankFunds = big.NewInt(1000000000000000000) 141 142 gspec = &Genesis{ 143 Config: params.TestChainConfig, 144 Alloc: types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, 145 BaseFee: big.NewInt(params.InitialBaseFee), 146 } 147 engine = ethash.NewFaker() 148 nonce = uint64(0) 149 chainHead = uint64(128) 150 ) 151 _, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(chainHead), func(i int, gen *BlockGen) { 152 tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey) 153 gen.AddTx(tx) 154 nonce += 1 155 }) 156 tailPointer := func(n uint64) *uint64 { 157 return &n 158 } 159 var cases = []struct { 160 limit uint64 161 head uint64 162 cutoff uint64 163 expTail *uint64 164 }{ 165 // if *tail > head => purge indexes 166 { 167 limit: 0, 168 head: chainHead / 2, 169 cutoff: 0, 170 expTail: tailPointer(0), 171 }, 172 { 173 limit: 1, // tail = 128 174 head: chainHead / 2, // newhead = 64 175 cutoff: 0, 176 expTail: nil, 177 }, 178 { 179 limit: 64, // tail = 65 180 head: chainHead / 2, // newhead = 64 181 cutoff: 0, 182 expTail: nil, 183 }, 184 { 185 limit: 65, // tail = 64 186 head: chainHead / 2, // newhead = 64 187 cutoff: 0, 188 expTail: tailPointer(64), 189 }, 190 { 191 limit: 66, // tail = 63 192 head: chainHead / 2, // newhead = 64 193 cutoff: 0, 194 expTail: tailPointer(63), 195 }, 196 197 // if tail < cutoff => remove indexes below cutoff 198 { 199 limit: 0, // tail = 0 200 head: chainHead, // head = 128 201 cutoff: chainHead, // cutoff = 128 202 expTail: tailPointer(chainHead), 203 }, 204 { 205 limit: 1, // tail = 128 206 head: chainHead, // head = 128 207 cutoff: chainHead, // cutoff = 128 208 expTail: tailPointer(128), 209 }, 210 { 211 limit: 2, // tail = 127 212 head: chainHead, // head = 128 213 cutoff: chainHead, // cutoff = 128 214 expTail: tailPointer(chainHead), 215 }, 216 { 217 limit: 2, // tail = 127 218 head: chainHead, // head = 128 219 cutoff: chainHead / 2, // cutoff = 64 220 expTail: tailPointer(127), 221 }, 222 223 // if head < cutoff => purge indexes 224 { 225 limit: 0, // tail = 0 226 head: chainHead, // head = 128 227 cutoff: 2 * chainHead, // cutoff = 256 228 expTail: nil, 229 }, 230 { 231 limit: 64, // tail = 65 232 head: chainHead, // head = 128 233 cutoff: chainHead / 2, // cutoff = 64 234 expTail: tailPointer(65), 235 }, 236 } 237 for _, c := range cases { 238 db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{}) 239 encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...)) 240 rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts) 241 242 // Index the initial blocks from ancient store 243 indexer := &txIndexer{ 244 limit: c.limit, 245 db: db, 246 } 247 indexer.run(chainHead, make(chan struct{}), make(chan struct{})) 248 249 indexer.cutoff = c.cutoff 250 indexer.repair(c.head) 251 252 if c.expTail == nil { 253 verifyNoIndex(t, db, blocks) 254 } else { 255 verify(t, db, blocks, *c.expTail) 256 } 257 db.Close() 258 } 259 } 260 261 func TestTxIndexerReport(t *testing.T) { 262 var ( 263 testBankKey, _ = crypto.GenerateKey() 264 testBankAddress = crypto.PubkeyToAddress(testBankKey.PublicKey) 265 testBankFunds = big.NewInt(1000000000000000000) 266 267 gspec = &Genesis{ 268 Config: params.TestChainConfig, 269 Alloc: types.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}, 270 BaseFee: big.NewInt(params.InitialBaseFee), 271 } 272 engine = ethash.NewFaker() 273 nonce = uint64(0) 274 chainHead = uint64(128) 275 ) 276 _, blocks, receipts := GenerateChainWithGenesis(gspec, engine, int(chainHead), func(i int, gen *BlockGen) { 277 tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0xdeadbeef"), big.NewInt(1000), params.TxGas, big.NewInt(10*params.InitialBaseFee), nil), types.HomesteadSigner{}, testBankKey) 278 gen.AddTx(tx) 279 nonce += 1 280 }) 281 tailPointer := func(n uint64) *uint64 { 282 return &n 283 } 284 var cases = []struct { 285 head uint64 286 limit uint64 287 cutoff uint64 288 tail *uint64 289 expIndexed uint64 290 expRemaining uint64 291 }{ 292 // The entire chain is supposed to be indexed 293 { 294 // head = 128, limit = 0, cutoff = 0 => all: 129 295 head: chainHead, 296 limit: 0, 297 cutoff: 0, 298 299 // tail = 0 300 tail: tailPointer(0), 301 expIndexed: 129, 302 expRemaining: 0, 303 }, 304 { 305 // head = 128, limit = 0, cutoff = 0 => all: 129 306 head: chainHead, 307 limit: 0, 308 cutoff: 0, 309 310 // tail = 1 311 tail: tailPointer(1), 312 expIndexed: 128, 313 expRemaining: 1, 314 }, 315 { 316 // head = 128, limit = 0, cutoff = 0 => all: 129 317 head: chainHead, 318 limit: 0, 319 cutoff: 0, 320 321 // tail = 128 322 tail: tailPointer(chainHead), 323 expIndexed: 1, 324 expRemaining: 128, 325 }, 326 { 327 // head = 128, limit = 256, cutoff = 0 => all: 129 328 head: chainHead, 329 limit: 256, 330 cutoff: 0, 331 332 // tail = 0 333 tail: tailPointer(0), 334 expIndexed: 129, 335 expRemaining: 0, 336 }, 337 338 // The chain with specific range is supposed to be indexed 339 { 340 // head = 128, limit = 64, cutoff = 0 => index: [65, 128] 341 head: chainHead, 342 limit: 64, 343 cutoff: 0, 344 345 // tail = 0, part of them need to be unindexed 346 tail: tailPointer(0), 347 expIndexed: 129, 348 expRemaining: 0, 349 }, 350 { 351 // head = 128, limit = 64, cutoff = 0 => index: [65, 128] 352 head: chainHead, 353 limit: 64, 354 cutoff: 0, 355 356 // tail = 64, one of them needs to be unindexed 357 tail: tailPointer(64), 358 expIndexed: 65, 359 expRemaining: 0, 360 }, 361 { 362 // head = 128, limit = 64, cutoff = 0 => index: [65, 128] 363 head: chainHead, 364 limit: 64, 365 cutoff: 0, 366 367 // tail = 65, all of them have been indexed 368 tail: tailPointer(65), 369 expIndexed: 64, 370 expRemaining: 0, 371 }, 372 { 373 // head = 128, limit = 64, cutoff = 0 => index: [65, 128] 374 head: chainHead, 375 limit: 64, 376 cutoff: 0, 377 378 // tail = 66, one of them has to be indexed 379 tail: tailPointer(66), 380 expIndexed: 63, 381 expRemaining: 1, 382 }, 383 384 // The chain with configured cutoff, the chain range could be capped 385 { 386 // head = 128, limit = 64, cutoff = 66 => index: [66, 128] 387 head: chainHead, 388 limit: 64, 389 cutoff: 66, 390 391 // tail = 0, part of them need to be unindexed 392 tail: tailPointer(0), 393 expIndexed: 129, 394 expRemaining: 0, 395 }, 396 { 397 // head = 128, limit = 64, cutoff = 66 => index: [66, 128] 398 head: chainHead, 399 limit: 64, 400 cutoff: 66, 401 402 // tail = 66, all of them have been indexed 403 tail: tailPointer(66), 404 expIndexed: 63, 405 expRemaining: 0, 406 }, 407 { 408 // head = 128, limit = 64, cutoff = 66 => index: [66, 128] 409 head: chainHead, 410 limit: 64, 411 cutoff: 66, 412 413 // tail = 67, one of them has to be indexed 414 tail: tailPointer(67), 415 expIndexed: 62, 416 expRemaining: 1, 417 }, 418 { 419 // head = 128, limit = 64, cutoff = 256 => index: [66, 128] 420 head: chainHead, 421 limit: 0, 422 cutoff: 256, 423 tail: nil, 424 expIndexed: 0, 425 expRemaining: 0, 426 }, 427 } 428 for _, c := range cases { 429 db, _ := rawdb.Open(rawdb.NewMemoryDatabase(), rawdb.OpenOptions{}) 430 encReceipts := types.EncodeBlockReceiptLists(append([]types.Receipts{{}}, receipts...)) 431 rawdb.WriteAncientBlocks(db, append([]*types.Block{gspec.ToBlock()}, blocks...), encReceipts) 432 433 // Index the initial blocks from ancient store 434 indexer := &txIndexer{ 435 limit: c.limit, 436 cutoff: c.cutoff, 437 db: db, 438 } 439 p := indexer.report(c.head, c.tail) 440 if p.Indexed != c.expIndexed { 441 t.Fatalf("Unexpected indexed: %d, expected: %d", p.Indexed, c.expIndexed) 442 } 443 if p.Remaining != c.expRemaining { 444 t.Fatalf("Unexpected remaining: %d, expected: %d", p.Remaining, c.expRemaining) 445 } 446 db.Close() 447 } 448 }