github.com/ethereum/go-ethereum@v1.16.1/core/txpool/blobpool/blobpool_test.go (about) 1 // Copyright 2023 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 blobpool 18 19 import ( 20 "bytes" 21 "crypto/ecdsa" 22 "crypto/sha256" 23 "errors" 24 "fmt" 25 "math" 26 "math/big" 27 "os" 28 "path/filepath" 29 "sync" 30 "testing" 31 32 "github.com/ethereum/go-ethereum/common" 33 "github.com/ethereum/go-ethereum/consensus/misc/eip1559" 34 "github.com/ethereum/go-ethereum/consensus/misc/eip4844" 35 "github.com/ethereum/go-ethereum/core" 36 "github.com/ethereum/go-ethereum/core/state" 37 "github.com/ethereum/go-ethereum/core/tracing" 38 "github.com/ethereum/go-ethereum/core/txpool" 39 "github.com/ethereum/go-ethereum/core/types" 40 "github.com/ethereum/go-ethereum/crypto" 41 "github.com/ethereum/go-ethereum/crypto/kzg4844" 42 "github.com/ethereum/go-ethereum/params" 43 "github.com/ethereum/go-ethereum/rlp" 44 "github.com/holiman/billy" 45 "github.com/holiman/uint256" 46 ) 47 48 var ( 49 testBlobs []*kzg4844.Blob 50 testBlobCommits []kzg4844.Commitment 51 testBlobProofs []kzg4844.Proof 52 testBlobVHashes [][32]byte 53 ) 54 55 const testMaxBlobsPerBlock = 6 56 57 func init() { 58 for i := 0; i < 24; i++ { 59 testBlob := &kzg4844.Blob{byte(i)} 60 testBlobs = append(testBlobs, testBlob) 61 62 testBlobCommit, _ := kzg4844.BlobToCommitment(testBlob) 63 testBlobCommits = append(testBlobCommits, testBlobCommit) 64 65 testBlobProof, _ := kzg4844.ComputeBlobProof(testBlob, testBlobCommit) 66 testBlobProofs = append(testBlobProofs, testBlobProof) 67 68 testBlobVHash := kzg4844.CalcBlobHashV1(sha256.New(), &testBlobCommit) 69 testBlobVHashes = append(testBlobVHashes, testBlobVHash) 70 } 71 } 72 73 // testBlockChain is a mock of the live chain for testing the pool. 74 type testBlockChain struct { 75 config *params.ChainConfig 76 basefee *uint256.Int 77 blobfee *uint256.Int 78 statedb *state.StateDB 79 80 blocks map[uint64]*types.Block 81 } 82 83 func (bc *testBlockChain) Config() *params.ChainConfig { 84 return bc.config 85 } 86 87 func (bc *testBlockChain) CurrentBlock() *types.Header { 88 // Yolo, life is too short to invert misc.CalcBaseFee and misc.CalcBlobFee, 89 // just binary search it them. 90 91 // The base fee at 5714 ETH translates into the 21000 base gas higher than 92 // mainnet ether existence, use that as a cap for the tests. 93 var ( 94 blockNumber = new(big.Int).Add(bc.config.LondonBlock, big.NewInt(1)) 95 blockTime = *bc.config.CancunTime + 1 96 gasLimit = uint64(30_000_000) 97 ) 98 lo := new(big.Int) 99 hi := new(big.Int).Mul(big.NewInt(5714), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) 100 101 for new(big.Int).Add(lo, big.NewInt(1)).Cmp(hi) != 0 { 102 mid := new(big.Int).Add(lo, hi) 103 mid.Div(mid, big.NewInt(2)) 104 105 if eip1559.CalcBaseFee(bc.config, &types.Header{ 106 Number: blockNumber, 107 GasLimit: gasLimit, 108 GasUsed: 0, 109 BaseFee: mid, 110 }).Cmp(bc.basefee.ToBig()) > 0 { 111 hi = mid 112 } else { 113 lo = mid 114 } 115 } 116 baseFee := lo 117 118 // The excess blob gas at 2^27 translates into a blob fee higher than mainnet 119 // ether existence, use that as a cap for the tests. 120 lo = new(big.Int) 121 hi = new(big.Int).Exp(big.NewInt(2), big.NewInt(27), nil) 122 123 for new(big.Int).Add(lo, big.NewInt(1)).Cmp(hi) != 0 { 124 mid := new(big.Int).Add(lo, hi) 125 mid.Div(mid, big.NewInt(2)) 126 127 tmp := mid.Uint64() 128 if eip4844.CalcBlobFee(bc.Config(), &types.Header{ 129 Number: blockNumber, 130 Time: blockTime, 131 ExcessBlobGas: &tmp, 132 }).Cmp(bc.blobfee.ToBig()) > 0 { 133 hi = mid 134 } else { 135 lo = mid 136 } 137 } 138 excessBlobGas := lo.Uint64() 139 140 return &types.Header{ 141 Number: blockNumber, 142 Time: blockTime, 143 GasLimit: gasLimit, 144 BaseFee: baseFee, 145 ExcessBlobGas: &excessBlobGas, 146 Difficulty: common.Big0, 147 } 148 } 149 150 func (bc *testBlockChain) CurrentFinalBlock() *types.Header { 151 return &types.Header{ 152 Number: big.NewInt(0), 153 } 154 } 155 156 func (bc *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { 157 // This is very yolo for the tests. If the number is the origin block we use 158 // to init the pool, return an empty block with the correct starting header. 159 // 160 // If it is something else, return some baked in mock data. 161 if number == bc.config.LondonBlock.Uint64()+1 { 162 return types.NewBlockWithHeader(bc.CurrentBlock()) 163 } 164 return bc.blocks[number] 165 } 166 167 func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { 168 return bc.statedb, nil 169 } 170 171 // reserver is a utility struct to sanity check that accounts are 172 // properly reserved by the blobpool (no duplicate reserves or unreserves). 173 type reserver struct { 174 accounts map[common.Address]struct{} 175 lock sync.RWMutex 176 } 177 178 func newReserver() txpool.Reserver { 179 return &reserver{accounts: make(map[common.Address]struct{})} 180 } 181 182 func (r *reserver) Hold(addr common.Address) error { 183 r.lock.Lock() 184 defer r.lock.Unlock() 185 if _, exists := r.accounts[addr]; exists { 186 panic("already reserved") 187 } 188 r.accounts[addr] = struct{}{} 189 return nil 190 } 191 192 func (r *reserver) Release(addr common.Address) error { 193 r.lock.Lock() 194 defer r.lock.Unlock() 195 if _, exists := r.accounts[addr]; !exists { 196 panic("not reserved") 197 } 198 delete(r.accounts, addr) 199 return nil 200 } 201 202 func (r *reserver) Has(address common.Address) bool { 203 r.lock.RLock() 204 defer r.lock.RUnlock() 205 _, exists := r.accounts[address] 206 return exists 207 } 208 209 // makeTx is a utility method to construct a random blob transaction and sign it 210 // with a valid key, only setting the interesting fields from the perspective of 211 // the blob pool. 212 func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction { 213 blobtx := makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap) 214 return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx) 215 } 216 217 // makeMultiBlobTx is a utility method to construct a ramdom blob tx with 218 // certain number of blobs in its sidecar. 219 func makeMultiBlobTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobCount int, key *ecdsa.PrivateKey) *types.Transaction { 220 var ( 221 blobs []kzg4844.Blob 222 blobHashes []common.Hash 223 commitments []kzg4844.Commitment 224 proofs []kzg4844.Proof 225 ) 226 for i := 0; i < blobCount; i++ { 227 blobs = append(blobs, *testBlobs[i]) 228 commitments = append(commitments, testBlobCommits[i]) 229 proofs = append(proofs, testBlobProofs[i]) 230 blobHashes = append(blobHashes, testBlobVHashes[i]) 231 } 232 blobtx := &types.BlobTx{ 233 ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID), 234 Nonce: nonce, 235 GasTipCap: uint256.NewInt(gasTipCap), 236 GasFeeCap: uint256.NewInt(gasFeeCap), 237 Gas: 21000, 238 BlobFeeCap: uint256.NewInt(blobFeeCap), 239 BlobHashes: blobHashes, 240 Value: uint256.NewInt(100), 241 Sidecar: &types.BlobTxSidecar{ 242 Blobs: blobs, 243 Commitments: commitments, 244 Proofs: proofs, 245 }, 246 } 247 return types.MustSignNewTx(key, types.LatestSigner(params.MainnetChainConfig), blobtx) 248 } 249 250 // makeUnsignedTx is a utility method to construct a random blob transaction 251 // without signing it. 252 func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx { 253 return makeUnsignedTxWithTestBlob(nonce, gasTipCap, gasFeeCap, blobFeeCap, rnd.Intn(len(testBlobs))) 254 } 255 256 // makeUnsignedTx is a utility method to construct a random blob transaction 257 // without signing it. 258 func makeUnsignedTxWithTestBlob(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, blobIdx int) *types.BlobTx { 259 return &types.BlobTx{ 260 ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID), 261 Nonce: nonce, 262 GasTipCap: uint256.NewInt(gasTipCap), 263 GasFeeCap: uint256.NewInt(gasFeeCap), 264 Gas: 21000, 265 BlobFeeCap: uint256.NewInt(blobFeeCap), 266 BlobHashes: []common.Hash{testBlobVHashes[blobIdx]}, 267 Value: uint256.NewInt(100), 268 Sidecar: &types.BlobTxSidecar{ 269 Blobs: []kzg4844.Blob{*testBlobs[blobIdx]}, 270 Commitments: []kzg4844.Commitment{testBlobCommits[blobIdx]}, 271 Proofs: []kzg4844.Proof{testBlobProofs[blobIdx]}, 272 }, 273 } 274 } 275 276 // verifyPoolInternals iterates over all the transactions in the pool and checks 277 // that sort orders, calculated fields, cumulated fields are correct. 278 func verifyPoolInternals(t *testing.T, pool *BlobPool) { 279 // Mark this method as a helper to remove from stack traces 280 t.Helper() 281 282 // Verify that all items in the index are present in the tx lookup and nothing more 283 seen := make(map[common.Hash]struct{}) 284 for addr, txs := range pool.index { 285 for _, tx := range txs { 286 if _, ok := seen[tx.hash]; ok { 287 t.Errorf("duplicate hash #%x in transaction index: address %s, nonce %d", tx.hash, addr, tx.nonce) 288 } 289 seen[tx.hash] = struct{}{} 290 } 291 } 292 for hash, id := range pool.lookup.txIndex { 293 if _, ok := seen[hash]; !ok { 294 t.Errorf("tx lookup entry missing from transaction index: hash #%x, id %d", hash, id) 295 } 296 delete(seen, hash) 297 } 298 for hash := range seen { 299 t.Errorf("indexed transaction hash #%x missing from tx lookup table", hash) 300 } 301 // Verify that all blobs in the index are present in the blob lookup and nothing more 302 blobs := make(map[common.Hash]map[common.Hash]struct{}) 303 for _, txs := range pool.index { 304 for _, tx := range txs { 305 for _, vhash := range tx.vhashes { 306 if blobs[vhash] == nil { 307 blobs[vhash] = make(map[common.Hash]struct{}) 308 } 309 blobs[vhash][tx.hash] = struct{}{} 310 } 311 } 312 } 313 for vhash, txs := range pool.lookup.blobIndex { 314 for txhash := range txs { 315 if _, ok := blobs[vhash][txhash]; !ok { 316 t.Errorf("blob lookup entry missing from transaction index: blob hash #%x, tx hash #%x", vhash, txhash) 317 } 318 delete(blobs[vhash], txhash) 319 if len(blobs[vhash]) == 0 { 320 delete(blobs, vhash) 321 } 322 } 323 } 324 for vhash := range blobs { 325 t.Errorf("indexed transaction blob hash #%x missing from blob lookup table", vhash) 326 } 327 // Verify that transactions are sorted per account and contain no nonce gaps, 328 // and that the first nonce is the next expected one based on the state. 329 for addr, txs := range pool.index { 330 for i := 1; i < len(txs); i++ { 331 if txs[i].nonce != txs[i-1].nonce+1 { 332 t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].nonce, txs[i-1].nonce+1) 333 } 334 } 335 if txs[0].nonce != pool.state.GetNonce(addr) { 336 t.Errorf("addr %v, first tx nonce mismatch: have %d, want %d", addr, txs[0].nonce, pool.state.GetNonce(addr)) 337 } 338 } 339 // Verify that calculated evacuation thresholds are correct 340 for addr, txs := range pool.index { 341 if !txs[0].evictionExecTip.Eq(txs[0].execTipCap) { 342 t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, 0, txs[0].evictionExecTip, txs[0].execTipCap) 343 } 344 if math.Abs(txs[0].evictionExecFeeJumps-txs[0].basefeeJumps) > 0.001 { 345 t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionExecFeeJumps, txs[0].basefeeJumps) 346 } 347 if math.Abs(txs[0].evictionBlobFeeJumps-txs[0].blobfeeJumps) > 0.001 { 348 t.Errorf("addr %v, tx %d eviction blob fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionBlobFeeJumps, txs[0].blobfeeJumps) 349 } 350 for i := 1; i < len(txs); i++ { 351 wantExecTip := txs[i-1].evictionExecTip 352 if wantExecTip.Gt(txs[i].execTipCap) { 353 wantExecTip = txs[i].execTipCap 354 } 355 if !txs[i].evictionExecTip.Eq(wantExecTip) { 356 t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, i, txs[i].evictionExecTip, wantExecTip) 357 } 358 359 wantExecFeeJumps := txs[i-1].evictionExecFeeJumps 360 if wantExecFeeJumps > txs[i].basefeeJumps { 361 wantExecFeeJumps = txs[i].basefeeJumps 362 } 363 if math.Abs(txs[i].evictionExecFeeJumps-wantExecFeeJumps) > 0.001 { 364 t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, i, txs[i].evictionExecFeeJumps, wantExecFeeJumps) 365 } 366 367 wantBlobFeeJumps := txs[i-1].evictionBlobFeeJumps 368 if wantBlobFeeJumps > txs[i].blobfeeJumps { 369 wantBlobFeeJumps = txs[i].blobfeeJumps 370 } 371 if math.Abs(txs[i].evictionBlobFeeJumps-wantBlobFeeJumps) > 0.001 { 372 t.Errorf("addr %v, tx %d eviction blob fee jumps mismatch: have %f, want %f", addr, i, txs[i].evictionBlobFeeJumps, wantBlobFeeJumps) 373 } 374 } 375 } 376 // Verify that account balance accumulations are correct 377 for addr, txs := range pool.index { 378 spent := new(uint256.Int) 379 for _, tx := range txs { 380 spent.Add(spent, tx.costCap) 381 } 382 if !pool.spent[addr].Eq(spent) { 383 t.Errorf("addr %v expenditure mismatch: have %d, want %d", addr, pool.spent[addr], spent) 384 } 385 } 386 // Verify that pool storage size is correct 387 var stored uint64 388 for _, txs := range pool.index { 389 for _, tx := range txs { 390 stored += uint64(tx.storageSize) 391 } 392 } 393 if pool.stored != stored { 394 t.Errorf("pool storage mismatch: have %d, want %d", pool.stored, stored) 395 } 396 // Verify the price heap internals 397 verifyHeapInternals(t, pool.evict) 398 399 // Verify that all the blobs can be retrieved 400 verifyBlobRetrievals(t, pool) 401 } 402 403 // verifyBlobRetrievals attempts to retrieve all testing blobs and checks that 404 // whatever is in the pool, it can be retrieved correctly. 405 func verifyBlobRetrievals(t *testing.T, pool *BlobPool) { 406 // Collect all the blobs tracked by the pool 407 known := make(map[common.Hash]struct{}) 408 for _, txs := range pool.index { 409 for _, tx := range txs { 410 for _, vhash := range tx.vhashes { 411 known[vhash] = struct{}{} 412 } 413 } 414 } 415 // Attempt to retrieve all test blobs 416 hashes := make([]common.Hash, len(testBlobVHashes)) 417 for i := range testBlobVHashes { 418 copy(hashes[i][:], testBlobVHashes[i][:]) 419 } 420 sidecars := pool.GetBlobs(hashes) 421 var blobs []*kzg4844.Blob 422 var proofs []*kzg4844.Proof 423 for idx, sidecar := range sidecars { 424 if sidecar == nil { 425 blobs = append(blobs, nil) 426 proofs = append(proofs, nil) 427 continue 428 } 429 blobHashes := sidecar.BlobHashes() 430 for i, hash := range blobHashes { 431 if hash == hashes[idx] { 432 blobs = append(blobs, &sidecar.Blobs[i]) 433 proofs = append(proofs, &sidecar.Proofs[i]) 434 } 435 } 436 } 437 // Cross validate what we received vs what we wanted 438 if len(blobs) != len(hashes) || len(proofs) != len(hashes) { 439 t.Errorf("retrieved blobs/proofs size mismatch: have %d/%d, want %d", len(blobs), len(proofs), len(hashes)) 440 return 441 } 442 for i, hash := range hashes { 443 // If an item is missing, but shouldn't, error 444 if blobs[i] == nil || proofs[i] == nil { 445 if _, ok := known[hash]; ok { 446 t.Errorf("tracked blob retrieval failed: item %d, hash %x", i, hash) 447 } 448 continue 449 } 450 // Item retrieved, make sure it matches the expectation 451 if *blobs[i] != *testBlobs[i] || *proofs[i] != testBlobProofs[i] { 452 t.Errorf("retrieved blob or proof mismatch: item %d, hash %x", i, hash) 453 continue 454 } 455 delete(known, hash) 456 } 457 for hash := range known { 458 t.Errorf("indexed blob #%x missing from retrieval", hash) 459 } 460 } 461 462 // Tests that transactions can be loaded from disk on startup and that they are 463 // correctly discarded if invalid. 464 // 465 // - 1. A transaction that cannot be decoded must be dropped 466 // - 2. A transaction that cannot be recovered (bad signature) must be dropped 467 // - 3. All transactions after a nonce gap must be dropped 468 // - 4. All transactions after an already included nonce must be dropped 469 // - 5. All transactions after an underpriced one (including it) must be dropped 470 // - 6. All transactions after an overdrafting sequence must be dropped 471 // - 7. All transactions exceeding the per-account limit must be dropped 472 // 473 // Furthermore, some strange corner-cases can also occur after a crash, as Billy's 474 // simplicity also allows it to resurrect past deleted entities: 475 // 476 // - 8. Fully duplicate transactions (matching hash) must be dropped 477 // - 9. Duplicate nonces from the same account must be dropped 478 func TestOpenDrops(t *testing.T) { 479 //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 480 481 // Create a temporary folder for the persistent backend 482 storage := t.TempDir() 483 484 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 485 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) 486 487 // Insert a malformed transaction to verify that decoding errors (or format 488 // changes) are handled gracefully (case 1) 489 malformed, _ := store.Put([]byte("this is a badly encoded transaction")) 490 491 // Insert a transaction with a bad signature to verify that stale junk after 492 // potential hard-forks can get evicted (case 2) 493 tx := types.NewTx(&types.BlobTx{ 494 ChainID: uint256.MustFromBig(params.MainnetChainConfig.ChainID), 495 GasTipCap: new(uint256.Int), 496 GasFeeCap: new(uint256.Int), 497 Gas: 0, 498 Value: new(uint256.Int), 499 Data: nil, 500 BlobFeeCap: new(uint256.Int), 501 V: new(uint256.Int), 502 R: new(uint256.Int), 503 S: new(uint256.Int), 504 }) 505 blob, _ := rlp.EncodeToBytes(tx) 506 badsig, _ := store.Put(blob) 507 508 // Insert a sequence of transactions with a nonce gap in between to verify 509 // that anything gapped will get evicted (case 3). 510 var ( 511 gapper, _ = crypto.GenerateKey() 512 513 valids = make(map[uint64]struct{}) 514 gapped = make(map[uint64]struct{}) 515 ) 516 for _, nonce := range []uint64{0, 1, 3, 4, 6, 7} { // first gap at #2, another at #5 517 tx := makeTx(nonce, 1, 1, 1, gapper) 518 blob, _ := rlp.EncodeToBytes(tx) 519 520 id, _ := store.Put(blob) 521 if nonce < 2 { 522 valids[id] = struct{}{} 523 } else { 524 gapped[id] = struct{}{} 525 } 526 } 527 // Insert a sequence of transactions with a gapped starting nonce to verify 528 // that the entire set will get dropped (case 3). 529 var ( 530 dangler, _ = crypto.GenerateKey() 531 dangling = make(map[uint64]struct{}) 532 ) 533 for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling 534 tx := makeTx(nonce, 1, 1, 1, dangler) 535 blob, _ := rlp.EncodeToBytes(tx) 536 537 id, _ := store.Put(blob) 538 dangling[id] = struct{}{} 539 } 540 // Insert a sequence of transactions with already passed nonces to veirfy 541 // that the entire set will get dropped (case 4). 542 var ( 543 filler, _ = crypto.GenerateKey() 544 filled = make(map[uint64]struct{}) 545 ) 546 for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled 547 tx := makeTx(nonce, 1, 1, 1, filler) 548 blob, _ := rlp.EncodeToBytes(tx) 549 550 id, _ := store.Put(blob) 551 filled[id] = struct{}{} 552 } 553 // Insert a sequence of transactions with partially passed nonces to verify 554 // that the included part of the set will get dropped (case 4). 555 var ( 556 overlapper, _ = crypto.GenerateKey() 557 overlapped = make(map[uint64]struct{}) 558 ) 559 for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled 560 tx := makeTx(nonce, 1, 1, 1, overlapper) 561 blob, _ := rlp.EncodeToBytes(tx) 562 563 id, _ := store.Put(blob) 564 if nonce >= 2 { 565 valids[id] = struct{}{} 566 } else { 567 overlapped[id] = struct{}{} 568 } 569 } 570 // Insert a sequence of transactions with an underpriced first to verify that 571 // the entire set will get dropped (case 5). 572 var ( 573 underpayer, _ = crypto.GenerateKey() 574 underpaid = make(map[uint64]struct{}) 575 ) 576 for i := 0; i < 5; i++ { // make #0 underpriced 577 var tx *types.Transaction 578 if i == 0 { 579 tx = makeTx(uint64(i), 0, 0, 0, underpayer) 580 } else { 581 tx = makeTx(uint64(i), 1, 1, 1, underpayer) 582 } 583 blob, _ := rlp.EncodeToBytes(tx) 584 585 id, _ := store.Put(blob) 586 underpaid[id] = struct{}{} 587 } 588 589 // Insert a sequence of transactions with an underpriced in between to verify 590 // that it and anything newly gapped will get evicted (case 5). 591 var ( 592 outpricer, _ = crypto.GenerateKey() 593 outpriced = make(map[uint64]struct{}) 594 ) 595 for i := 0; i < 5; i++ { // make #2 underpriced 596 var tx *types.Transaction 597 if i == 2 { 598 tx = makeTx(uint64(i), 0, 0, 0, outpricer) 599 } else { 600 tx = makeTx(uint64(i), 1, 1, 1, outpricer) 601 } 602 blob, _ := rlp.EncodeToBytes(tx) 603 604 id, _ := store.Put(blob) 605 if i < 2 { 606 valids[id] = struct{}{} 607 } else { 608 outpriced[id] = struct{}{} 609 } 610 } 611 // Insert a sequence of transactions fully overdrafted to verify that the 612 // entire set will get invalidated (case 6). 613 var ( 614 exceeder, _ = crypto.GenerateKey() 615 exceeded = make(map[uint64]struct{}) 616 ) 617 for _, nonce := range []uint64{0, 1, 2} { // nonce 0 overdrafts the account 618 var tx *types.Transaction 619 if nonce == 0 { 620 tx = makeTx(nonce, 1, 100, 1, exceeder) 621 } else { 622 tx = makeTx(nonce, 1, 1, 1, exceeder) 623 } 624 blob, _ := rlp.EncodeToBytes(tx) 625 626 id, _ := store.Put(blob) 627 exceeded[id] = struct{}{} 628 } 629 // Insert a sequence of transactions partially overdrafted to verify that part 630 // of the set will get invalidated (case 6). 631 var ( 632 overdrafter, _ = crypto.GenerateKey() 633 overdrafted = make(map[uint64]struct{}) 634 ) 635 for _, nonce := range []uint64{0, 1, 2} { // nonce 1 overdrafts the account 636 var tx *types.Transaction 637 if nonce == 1 { 638 tx = makeTx(nonce, 1, 100, 1, overdrafter) 639 } else { 640 tx = makeTx(nonce, 1, 1, 1, overdrafter) 641 } 642 blob, _ := rlp.EncodeToBytes(tx) 643 644 id, _ := store.Put(blob) 645 if nonce < 1 { 646 valids[id] = struct{}{} 647 } else { 648 overdrafted[id] = struct{}{} 649 } 650 } 651 // Insert a sequence of transactions overflowing the account cap to verify 652 // that part of the set will get invalidated (case 7). 653 var ( 654 overcapper, _ = crypto.GenerateKey() 655 overcapped = make(map[uint64]struct{}) 656 ) 657 for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ { 658 blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, overcapper)) 659 660 id, _ := store.Put(blob) 661 if nonce < maxTxsPerAccount { 662 valids[id] = struct{}{} 663 } else { 664 overcapped[id] = struct{}{} 665 } 666 } 667 // Insert a batch of duplicated transactions to verify that only one of each 668 // version will remain (case 8). 669 var ( 670 duplicater, _ = crypto.GenerateKey() 671 duplicated = make(map[uint64]struct{}) 672 ) 673 for _, nonce := range []uint64{0, 1, 2} { 674 blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, duplicater)) 675 676 for i := 0; i < int(nonce)+1; i++ { 677 id, _ := store.Put(blob) 678 if i == 0 { 679 valids[id] = struct{}{} 680 } else { 681 duplicated[id] = struct{}{} 682 } 683 } 684 } 685 // Insert a batch of duplicated nonces to verify that only one of each will 686 // remain (case 9). 687 var ( 688 repeater, _ = crypto.GenerateKey() 689 repeated = make(map[uint64]struct{}) 690 ) 691 for _, nonce := range []uint64{0, 1, 2} { 692 for i := 0; i < int(nonce)+1; i++ { 693 blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, uint64(i)+1 /* unique hashes */, 1, repeater)) 694 695 id, _ := store.Put(blob) 696 if i == 0 { 697 valids[id] = struct{}{} 698 } else { 699 repeated[id] = struct{}{} 700 } 701 } 702 } 703 store.Close() 704 705 // Create a blob pool out of the pre-seeded data 706 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 707 statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 708 statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 709 statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 710 statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3, tracing.NonceChangeUnspecified) 711 statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 712 statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2, tracing.NonceChangeUnspecified) 713 statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 714 statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 715 statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 716 statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 717 statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), uint256.NewInt(10000000), tracing.BalanceChangeUnspecified) 718 statedb.AddBalance(crypto.PubkeyToAddress(duplicater.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 719 statedb.AddBalance(crypto.PubkeyToAddress(repeater.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 720 statedb.Commit(0, true, false) 721 722 chain := &testBlockChain{ 723 config: params.MainnetChainConfig, 724 basefee: uint256.NewInt(params.InitialBaseFee), 725 blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), 726 statedb: statedb, 727 } 728 pool := New(Config{Datadir: storage}, chain, nil) 729 if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { 730 t.Fatalf("failed to create blob pool: %v", err) 731 } 732 defer pool.Close() 733 734 // Verify that the malformed (case 1), badly signed (case 2) and gapped (case 735 // 3) txs have been deleted from the pool 736 alive := make(map[uint64]struct{}) 737 for _, txs := range pool.index { 738 for _, tx := range txs { 739 switch tx.id { 740 case malformed: 741 t.Errorf("malformed RLP transaction remained in storage") 742 case badsig: 743 t.Errorf("invalidly signed transaction remained in storage") 744 default: 745 if _, ok := dangling[tx.id]; ok { 746 t.Errorf("dangling transaction remained in storage: %d", tx.id) 747 } else if _, ok := filled[tx.id]; ok { 748 t.Errorf("filled transaction remained in storage: %d", tx.id) 749 } else if _, ok := overlapped[tx.id]; ok { 750 t.Errorf("overlapped transaction remained in storage: %d", tx.id) 751 } else if _, ok := gapped[tx.id]; ok { 752 t.Errorf("gapped transaction remained in storage: %d", tx.id) 753 } else if _, ok := underpaid[tx.id]; ok { 754 t.Errorf("underpaid transaction remained in storage: %d", tx.id) 755 } else if _, ok := outpriced[tx.id]; ok { 756 t.Errorf("outpriced transaction remained in storage: %d", tx.id) 757 } else if _, ok := exceeded[tx.id]; ok { 758 t.Errorf("fully overdrafted transaction remained in storage: %d", tx.id) 759 } else if _, ok := overdrafted[tx.id]; ok { 760 t.Errorf("partially overdrafted transaction remained in storage: %d", tx.id) 761 } else if _, ok := overcapped[tx.id]; ok { 762 t.Errorf("overcapped transaction remained in storage: %d", tx.id) 763 } else if _, ok := duplicated[tx.id]; ok { 764 t.Errorf("duplicated transaction remained in storage: %d", tx.id) 765 } else if _, ok := repeated[tx.id]; ok { 766 t.Errorf("repeated nonce transaction remained in storage: %d", tx.id) 767 } else { 768 alive[tx.id] = struct{}{} 769 } 770 } 771 } 772 } 773 // Verify that the rest of the transactions remained alive 774 if len(alive) != len(valids) { 775 t.Errorf("valid transaction count mismatch: have %d, want %d", len(alive), len(valids)) 776 } 777 for id := range alive { 778 if _, ok := valids[id]; !ok { 779 t.Errorf("extra transaction %d", id) 780 } 781 } 782 for id := range valids { 783 if _, ok := alive[id]; !ok { 784 t.Errorf("missing transaction %d", id) 785 } 786 } 787 // Verify all the calculated pool internals. Interestingly, this is **not** 788 // a duplication of the above checks, this actually validates the verifier 789 // using the above already hard coded checks. 790 // 791 // Do not remove this, nor alter the above to be generic. 792 verifyPoolInternals(t, pool) 793 } 794 795 // Tests that transactions loaded from disk are indexed correctly. 796 // 797 // - 1. Transactions must be grouped by sender, sorted by nonce 798 // - 2. Eviction thresholds are calculated correctly for the sequences 799 // - 3. Balance usage of an account is totals across all transactions 800 func TestOpenIndex(t *testing.T) { 801 //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 802 803 // Create a temporary folder for the persistent backend 804 storage := t.TempDir() 805 806 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 807 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) 808 809 // Insert a sequence of transactions with varying price points to check that 810 // the cumulative minimum will be maintained. 811 var ( 812 key, _ = crypto.GenerateKey() 813 addr = crypto.PubkeyToAddress(key.PublicKey) 814 815 txExecTipCaps = []uint64{10, 25, 5, 7, 1, 100} 816 txExecFeeCaps = []uint64{100, 90, 200, 10, 80, 300} 817 txBlobFeeCaps = []uint64{55, 66, 77, 33, 22, 11} 818 819 //basefeeJumps = []float64{39.098, 38.204, 44.983, 19.549, 37.204, 48.426} // log 1.125 (exec fee cap) 820 //blobfeeJumps = []float64{34.023, 35.570, 36.879, 29.686, 26.243, 20.358} // log 1.125 (blob fee cap) 821 822 evictExecTipCaps = []uint64{10, 10, 5, 5, 1, 1} 823 evictExecFeeJumps = []float64{39.098, 38.204, 38.204, 19.549, 19.549, 19.549} // min(log 1.125 (exec fee cap)) 824 evictBlobFeeJumps = []float64{34.023, 34.023, 34.023, 29.686, 26.243, 20.358} // min(log 1.125 (blob fee cap)) 825 826 totalSpent = uint256.NewInt(21000*(100+90+200+10+80+300) + blobSize*(55+66+77+33+22+11) + 100*6) // 21000 gas x price + 128KB x blobprice + value 827 ) 828 for _, i := range []int{5, 3, 4, 2, 0, 1} { // Randomize the tx insertion order to force sorting on load 829 tx := makeTx(uint64(i), txExecTipCaps[i], txExecFeeCaps[i], txBlobFeeCaps[i], key) 830 blob, _ := rlp.EncodeToBytes(tx) 831 store.Put(blob) 832 } 833 store.Close() 834 835 // Create a blob pool out of the pre-seeded data 836 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 837 statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 838 statedb.Commit(0, true, false) 839 840 chain := &testBlockChain{ 841 config: params.MainnetChainConfig, 842 basefee: uint256.NewInt(params.InitialBaseFee), 843 blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), 844 statedb: statedb, 845 } 846 pool := New(Config{Datadir: storage}, chain, nil) 847 if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { 848 t.Fatalf("failed to create blob pool: %v", err) 849 } 850 defer pool.Close() 851 852 // Verify that the transactions have been sorted by nonce (case 1) 853 for i := 0; i < len(pool.index[addr]); i++ { 854 if pool.index[addr][i].nonce != uint64(i) { 855 t.Errorf("tx %d nonce mismatch: have %d, want %d", i, pool.index[addr][i].nonce, uint64(i)) 856 } 857 } 858 // Verify that the cumulative fee minimums have been correctly calculated (case 2) 859 for i, cap := range evictExecTipCaps { 860 if !pool.index[addr][i].evictionExecTip.Eq(uint256.NewInt(cap)) { 861 t.Errorf("eviction tip cap %d mismatch: have %d, want %d", i, pool.index[addr][i].evictionExecTip, cap) 862 } 863 } 864 for i, jumps := range evictExecFeeJumps { 865 if math.Abs(pool.index[addr][i].evictionExecFeeJumps-jumps) > 0.001 { 866 t.Errorf("eviction fee cap jumps %d mismatch: have %f, want %f", i, pool.index[addr][i].evictionExecFeeJumps, jumps) 867 } 868 } 869 for i, jumps := range evictBlobFeeJumps { 870 if math.Abs(pool.index[addr][i].evictionBlobFeeJumps-jumps) > 0.001 { 871 t.Errorf("eviction blob fee cap jumps %d mismatch: have %f, want %f", i, pool.index[addr][i].evictionBlobFeeJumps, jumps) 872 } 873 } 874 // Verify that the balance usage has been correctly calculated (case 3) 875 if !pool.spent[addr].Eq(totalSpent) { 876 t.Errorf("expenditure mismatch: have %d, want %d", pool.spent[addr], totalSpent) 877 } 878 // Verify all the calculated pool internals. Interestingly, this is **not** 879 // a duplication of the above checks, this actually validates the verifier 880 // using the above already hard coded checks. 881 // 882 // Do not remove this, nor alter the above to be generic. 883 verifyPoolInternals(t, pool) 884 } 885 886 // Tests that after indexing all the loaded transactions from disk, a price heap 887 // is correctly constructed based on the head basefee and blobfee. 888 func TestOpenHeap(t *testing.T) { 889 //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 890 891 // Create a temporary folder for the persistent backend 892 storage := t.TempDir() 893 894 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 895 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) 896 897 // Insert a few transactions from a few accounts. To remove randomness from 898 // the heap initialization, use a deterministic account/tx/priority ordering. 899 var ( 900 key1, _ = crypto.GenerateKey() 901 key2, _ = crypto.GenerateKey() 902 key3, _ = crypto.GenerateKey() 903 904 addr1 = crypto.PubkeyToAddress(key1.PublicKey) 905 addr2 = crypto.PubkeyToAddress(key2.PublicKey) 906 addr3 = crypto.PubkeyToAddress(key3.PublicKey) 907 ) 908 if bytes.Compare(addr1[:], addr2[:]) > 0 { 909 key1, addr1, key2, addr2 = key2, addr2, key1, addr1 910 } 911 if bytes.Compare(addr1[:], addr3[:]) > 0 { 912 key1, addr1, key3, addr3 = key3, addr3, key1, addr1 913 } 914 if bytes.Compare(addr2[:], addr3[:]) > 0 { 915 key2, addr2, key3, addr3 = key3, addr3, key2, addr2 916 } 917 var ( 918 tx1 = makeTx(0, 1, 1000, 90, key1) 919 tx2 = makeTx(0, 1, 800, 70, key2) 920 tx3 = makeTx(0, 1, 1500, 110, key3) 921 922 blob1, _ = rlp.EncodeToBytes(tx1) 923 blob2, _ = rlp.EncodeToBytes(tx2) 924 blob3, _ = rlp.EncodeToBytes(tx3) 925 926 heapOrder = []common.Address{addr2, addr1, addr3} 927 heapIndex = map[common.Address]int{addr2: 0, addr1: 1, addr3: 2} 928 ) 929 store.Put(blob1) 930 store.Put(blob2) 931 store.Put(blob3) 932 store.Close() 933 934 // Create a blob pool out of the pre-seeded data 935 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 936 statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 937 statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 938 statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 939 statedb.Commit(0, true, false) 940 941 chain := &testBlockChain{ 942 config: params.MainnetChainConfig, 943 basefee: uint256.NewInt(1050), 944 blobfee: uint256.NewInt(105), 945 statedb: statedb, 946 } 947 pool := New(Config{Datadir: storage}, chain, nil) 948 if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { 949 t.Fatalf("failed to create blob pool: %v", err) 950 } 951 defer pool.Close() 952 953 // Verify that the heap's internal state matches the expectations 954 for i, addr := range pool.evict.addrs { 955 if addr != heapOrder[i] { 956 t.Errorf("slot %d mismatch: have %v, want %v", i, addr, heapOrder[i]) 957 } 958 } 959 for addr, i := range pool.evict.index { 960 if i != heapIndex[addr] { 961 t.Errorf("index for %v mismatch: have %d, want %d", addr, i, heapIndex[addr]) 962 } 963 } 964 // Verify all the calculated pool internals. Interestingly, this is **not** 965 // a duplication of the above checks, this actually validates the verifier 966 // using the above already hard coded checks. 967 // 968 // Do not remove this, nor alter the above to be generic. 969 verifyPoolInternals(t, pool) 970 } 971 972 // Tests that after the pool's previous state is loaded back, any transactions 973 // over the new storage cap will get dropped. 974 func TestOpenCap(t *testing.T) { 975 //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 976 977 // Create a temporary folder for the persistent backend 978 storage := t.TempDir() 979 980 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 981 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) 982 983 // Insert a few transactions from a few accounts 984 var ( 985 key1, _ = crypto.GenerateKey() 986 key2, _ = crypto.GenerateKey() 987 key3, _ = crypto.GenerateKey() 988 989 addr1 = crypto.PubkeyToAddress(key1.PublicKey) 990 addr2 = crypto.PubkeyToAddress(key2.PublicKey) 991 addr3 = crypto.PubkeyToAddress(key3.PublicKey) 992 993 tx1 = makeTx(0, 1, 1000, 100, key1) 994 tx2 = makeTx(0, 1, 800, 70, key2) 995 tx3 = makeTx(0, 1, 1500, 110, key3) 996 997 blob1, _ = rlp.EncodeToBytes(tx1) 998 blob2, _ = rlp.EncodeToBytes(tx2) 999 blob3, _ = rlp.EncodeToBytes(tx3) 1000 1001 keep = []common.Address{addr1, addr3} 1002 drop = []common.Address{addr2} 1003 size = uint64(2 * (txAvgSize + blobSize)) 1004 ) 1005 store.Put(blob1) 1006 store.Put(blob2) 1007 store.Put(blob3) 1008 store.Close() 1009 1010 // Verify pool capping twice: first by reducing the data cap, then restarting 1011 // with a high cap to ensure everything was persisted previously 1012 for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} { 1013 // Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction 1014 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 1015 statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 1016 statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 1017 statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 1018 statedb.Commit(0, true, false) 1019 1020 chain := &testBlockChain{ 1021 config: params.MainnetChainConfig, 1022 basefee: uint256.NewInt(1050), 1023 blobfee: uint256.NewInt(105), 1024 statedb: statedb, 1025 } 1026 pool := New(Config{Datadir: storage, Datacap: datacap}, chain, nil) 1027 if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { 1028 t.Fatalf("failed to create blob pool: %v", err) 1029 } 1030 // Verify that enough transactions have been dropped to get the pool's size 1031 // under the requested limit 1032 if len(pool.index) != len(keep) { 1033 t.Errorf("tracked account count mismatch: have %d, want %d", len(pool.index), len(keep)) 1034 } 1035 for _, addr := range keep { 1036 if _, ok := pool.index[addr]; !ok { 1037 t.Errorf("expected account %v missing from pool", addr) 1038 } 1039 } 1040 for _, addr := range drop { 1041 if _, ok := pool.index[addr]; ok { 1042 t.Errorf("unexpected account %v present in pool", addr) 1043 } 1044 } 1045 if pool.stored != size { 1046 t.Errorf("pool stored size mismatch: have %v, want %v", pool.stored, size) 1047 } 1048 // Verify all the calculated pool internals. Interestingly, this is **not** 1049 // a duplication of the above checks, this actually validates the verifier 1050 // using the above already hard coded checks. 1051 // 1052 // Do not remove this, nor alter the above to be generic. 1053 verifyPoolInternals(t, pool) 1054 1055 pool.Close() 1056 } 1057 } 1058 1059 // TestChangingSlotterSize attempts to mimic a scenario where the max blob count 1060 // of the pool is increased. This would happen during a client release where a 1061 // new fork is added with a max blob count higher than the previous fork. We 1062 // want to make sure transactions a persisted between those runs. 1063 func TestChangingSlotterSize(t *testing.T) { 1064 //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 1065 1066 // Create a temporary folder for the persistent backend 1067 storage := t.TempDir() 1068 1069 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 1070 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(6), nil) 1071 1072 // Create transactions from a few accounts. 1073 var ( 1074 key1, _ = crypto.GenerateKey() 1075 key2, _ = crypto.GenerateKey() 1076 key3, _ = crypto.GenerateKey() 1077 1078 addr1 = crypto.PubkeyToAddress(key1.PublicKey) 1079 addr2 = crypto.PubkeyToAddress(key2.PublicKey) 1080 addr3 = crypto.PubkeyToAddress(key3.PublicKey) 1081 1082 tx1 = makeMultiBlobTx(0, 1, 1000, 100, 6, key1) 1083 tx2 = makeMultiBlobTx(0, 1, 800, 70, 6, key2) 1084 tx3 = makeMultiBlobTx(0, 1, 800, 110, 24, key3) 1085 1086 blob1, _ = rlp.EncodeToBytes(tx1) 1087 blob2, _ = rlp.EncodeToBytes(tx2) 1088 ) 1089 1090 // Write the two safely sized txs to store. note: although the store is 1091 // configured for a blob count of 6, it can also support around ~1mb of call 1092 // data - all this to say that we aren't using the the absolute largest shelf 1093 // available. 1094 store.Put(blob1) 1095 store.Put(blob2) 1096 store.Close() 1097 1098 // Mimic a blobpool with max blob count of 6 upgrading to a max blob count of 24. 1099 for _, maxBlobs := range []int{6, 24} { 1100 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 1101 statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 1102 statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 1103 statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 1104 statedb.Commit(0, true, false) 1105 1106 // Make custom chain config where the max blob count changes based on the loop variable. 1107 cancunTime := uint64(0) 1108 config := ¶ms.ChainConfig{ 1109 ChainID: big.NewInt(1), 1110 LondonBlock: big.NewInt(0), 1111 BerlinBlock: big.NewInt(0), 1112 CancunTime: &cancunTime, 1113 BlobScheduleConfig: ¶ms.BlobScheduleConfig{ 1114 Cancun: ¶ms.BlobConfig{ 1115 Target: maxBlobs / 2, 1116 Max: maxBlobs, 1117 UpdateFraction: params.DefaultCancunBlobConfig.UpdateFraction, 1118 }, 1119 }, 1120 } 1121 chain := &testBlockChain{ 1122 config: config, 1123 basefee: uint256.NewInt(1050), 1124 blobfee: uint256.NewInt(105), 1125 statedb: statedb, 1126 } 1127 pool := New(Config{Datadir: storage}, chain, nil) 1128 if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { 1129 t.Fatalf("failed to create blob pool: %v", err) 1130 } 1131 1132 // Try to add the big blob tx. In the initial iteration it should overflow 1133 // the pool. On the subsequent iteration it should be accepted. 1134 errs := pool.Add([]*types.Transaction{tx3}, true) 1135 if _, ok := pool.index[addr3]; ok && maxBlobs == 6 { 1136 t.Errorf("expected insert of oversized blob tx to fail: blobs=24, maxBlobs=%d, err=%v", maxBlobs, errs[0]) 1137 } else if !ok && maxBlobs == 10 { 1138 t.Errorf("expected insert of oversized blob tx to succeed: blobs=24, maxBlobs=%d, err=%v", maxBlobs, errs[0]) 1139 } 1140 1141 // Verify the regular two txs are always available. 1142 if got := pool.Get(tx1.Hash()); got == nil { 1143 t.Errorf("expected tx %s from %s in pool", tx1.Hash(), addr1) 1144 } 1145 if got := pool.Get(tx2.Hash()); got == nil { 1146 t.Errorf("expected tx %s from %s in pool", tx2.Hash(), addr2) 1147 } 1148 1149 // Verify all the calculated pool internals. Interestingly, this is **not** 1150 // a duplication of the above checks, this actually validates the verifier 1151 // using the above already hard coded checks. 1152 // 1153 // Do not remove this, nor alter the above to be generic. 1154 verifyPoolInternals(t, pool) 1155 1156 pool.Close() 1157 } 1158 } 1159 1160 // TestBlobCountLimit tests the blobpool enforced limits on the max blob count. 1161 func TestBlobCountLimit(t *testing.T) { 1162 var ( 1163 key1, _ = crypto.GenerateKey() 1164 key2, _ = crypto.GenerateKey() 1165 1166 addr1 = crypto.PubkeyToAddress(key1.PublicKey) 1167 addr2 = crypto.PubkeyToAddress(key2.PublicKey) 1168 ) 1169 1170 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 1171 statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 1172 statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 1173 statedb.Commit(0, true, false) 1174 1175 // Make Prague-enabled custom chain config. 1176 cancunTime := uint64(0) 1177 pragueTime := uint64(0) 1178 config := ¶ms.ChainConfig{ 1179 ChainID: big.NewInt(1), 1180 LondonBlock: big.NewInt(0), 1181 BerlinBlock: big.NewInt(0), 1182 CancunTime: &cancunTime, 1183 PragueTime: &pragueTime, 1184 BlobScheduleConfig: ¶ms.BlobScheduleConfig{ 1185 Cancun: params.DefaultCancunBlobConfig, 1186 Prague: params.DefaultPragueBlobConfig, 1187 }, 1188 } 1189 chain := &testBlockChain{ 1190 config: config, 1191 basefee: uint256.NewInt(1050), 1192 blobfee: uint256.NewInt(105), 1193 statedb: statedb, 1194 } 1195 pool := New(Config{Datadir: t.TempDir()}, chain, nil) 1196 if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { 1197 t.Fatalf("failed to create blob pool: %v", err) 1198 } 1199 1200 // Attempt to add transactions. 1201 var ( 1202 tx1 = makeMultiBlobTx(0, 1, 1000, 100, 7, key1) 1203 tx2 = makeMultiBlobTx(0, 1, 800, 70, 8, key2) 1204 ) 1205 errs := pool.Add([]*types.Transaction{tx1, tx2}, true) 1206 1207 // Check that first succeeds second fails. 1208 if errs[0] != nil { 1209 t.Fatalf("expected tx with 7 blobs to succeed") 1210 } 1211 if !errors.Is(errs[1], txpool.ErrTxBlobLimitExceeded) { 1212 t.Fatalf("expected tx with 8 blobs to fail, got: %v", errs[1]) 1213 } 1214 1215 verifyPoolInternals(t, pool) 1216 pool.Close() 1217 } 1218 1219 // Tests that adding transaction will correctly store it in the persistent store 1220 // and update all the indices. 1221 // 1222 // Note, this tests mostly checks the pool transaction shuffling logic or things 1223 // specific to the blob pool. It does not do an exhaustive transaction validity 1224 // check. 1225 func TestAdd(t *testing.T) { 1226 //log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 1227 1228 // seed is a helper tuple to seed an initial state db and pool 1229 type seed struct { 1230 balance uint64 1231 nonce uint64 1232 txs []*types.BlobTx 1233 } 1234 // addtx is a helper sender/tx tuple to represent a new tx addition 1235 type addtx struct { 1236 from string 1237 tx *types.BlobTx 1238 err error 1239 } 1240 1241 tests := []struct { 1242 seeds map[string]seed 1243 adds []addtx 1244 block []addtx 1245 }{ 1246 // Transactions from new accounts should be accepted if their initial 1247 // nonce matches the expected one from the statedb. Higher or lower must 1248 // be rejected. 1249 { 1250 seeds: map[string]seed{ 1251 "alice": {balance: 21100 + blobSize}, 1252 "bob": {balance: 21100 + blobSize, nonce: 1}, 1253 "claire": {balance: 21100 + blobSize}, 1254 "dave": {balance: 21100 + blobSize, nonce: 1}, 1255 }, 1256 adds: []addtx{ 1257 { // New account, no previous txs: accept nonce 0 1258 from: "alice", 1259 tx: makeUnsignedTx(0, 1, 1, 1), 1260 err: nil, 1261 }, 1262 { // Old account, 1 tx in chain, 0 pending: accept nonce 1 1263 from: "bob", 1264 tx: makeUnsignedTx(1, 1, 1, 1), 1265 err: nil, 1266 }, 1267 { // New account, no previous txs: reject nonce 1 1268 from: "claire", 1269 tx: makeUnsignedTx(1, 1, 1, 1), 1270 err: core.ErrNonceTooHigh, 1271 }, 1272 { // Old account, 1 tx in chain, 0 pending: reject nonce 0 1273 from: "dave", 1274 tx: makeUnsignedTx(0, 1, 1, 1), 1275 err: core.ErrNonceTooLow, 1276 }, 1277 { // Old account, 1 tx in chain, 0 pending: reject nonce 2 1278 from: "dave", 1279 tx: makeUnsignedTx(2, 1, 1, 1), 1280 err: core.ErrNonceTooHigh, 1281 }, 1282 }, 1283 }, 1284 // Transactions from already pooled accounts should only be accepted if 1285 // the nonces are contiguous (ignore prices for now, will check later) 1286 { 1287 seeds: map[string]seed{ 1288 "alice": { 1289 balance: 1000000, 1290 txs: []*types.BlobTx{ 1291 makeUnsignedTxWithTestBlob(0, 1, 1, 1, 0), 1292 }, 1293 }, 1294 "bob": { 1295 balance: 1000000, 1296 nonce: 1, 1297 txs: []*types.BlobTx{ 1298 makeUnsignedTxWithTestBlob(1, 1, 1, 1, 1), 1299 }, 1300 }, 1301 }, 1302 adds: []addtx{ 1303 { // New account, 1 tx pending: reject duplicate nonce 0 1304 from: "alice", 1305 tx: makeUnsignedTxWithTestBlob(0, 1, 1, 1, 0), 1306 err: txpool.ErrAlreadyKnown, 1307 }, 1308 { // New account, 1 tx pending: reject replacement nonce 0 (ignore price for now) 1309 from: "alice", 1310 tx: makeUnsignedTx(0, 1, 1, 2), 1311 err: txpool.ErrReplaceUnderpriced, 1312 }, 1313 { // New account, 1 tx pending: accept nonce 1 1314 from: "alice", 1315 tx: makeUnsignedTx(1, 1, 1, 1), 1316 err: nil, 1317 }, 1318 { // New account, 2 txs pending: reject nonce 3 1319 from: "alice", 1320 tx: makeUnsignedTx(3, 1, 1, 1), 1321 err: core.ErrNonceTooHigh, 1322 }, 1323 { // New account, 2 txs pending: accept nonce 2 1324 from: "alice", 1325 tx: makeUnsignedTx(2, 1, 1, 1), 1326 err: nil, 1327 }, 1328 { // New account, 3 txs pending: accept nonce 3 now 1329 from: "alice", 1330 tx: makeUnsignedTx(3, 1, 1, 1), 1331 err: nil, 1332 }, 1333 { // Old account, 1 tx in chain, 1 tx pending: reject duplicate nonce 1 1334 from: "bob", 1335 tx: makeUnsignedTxWithTestBlob(1, 1, 1, 1, 1), 1336 err: txpool.ErrAlreadyKnown, 1337 }, 1338 { // Old account, 1 tx in chain, 1 tx pending: accept nonce 2 (ignore price for now) 1339 from: "bob", 1340 tx: makeUnsignedTx(2, 1, 1, 1), 1341 err: nil, 1342 }, 1343 }, 1344 }, 1345 // Transactions should only be accepted into the pool if the cumulative 1346 // expenditure doesn't overflow the account balance 1347 { 1348 seeds: map[string]seed{ 1349 "alice": {balance: 63299 + 3*blobSize}, // 3 tx - 1 wei 1350 }, 1351 adds: []addtx{ 1352 { // New account, no previous txs: accept nonce 0 with 21100 wei spend 1353 from: "alice", 1354 tx: makeUnsignedTx(0, 1, 1, 1), 1355 err: nil, 1356 }, 1357 { // New account, 1 pooled tx with 21100 wei spent: accept nonce 1 with 21100 wei spend 1358 from: "alice", 1359 tx: makeUnsignedTx(1, 1, 1, 1), 1360 err: nil, 1361 }, 1362 { // New account, 2 pooled tx with 42200 wei spent: reject nonce 2 with 21100 wei spend (1 wei overflow) 1363 from: "alice", 1364 tx: makeUnsignedTx(2, 1, 1, 1), 1365 err: core.ErrInsufficientFunds, 1366 }, 1367 }, 1368 }, 1369 // Transactions should only be accepted into the pool if the total count 1370 // from the same account doesn't overflow the pool limits 1371 { 1372 seeds: map[string]seed{ 1373 "alice": {balance: 10000000}, 1374 }, 1375 adds: []addtx{ 1376 { // New account, no previous txs, 16 slots left: accept nonce 0 1377 from: "alice", 1378 tx: makeUnsignedTx(0, 1, 1, 1), 1379 err: nil, 1380 }, 1381 { // New account, 1 pooled tx, 15 slots left: accept nonce 1 1382 from: "alice", 1383 tx: makeUnsignedTx(1, 1, 1, 1), 1384 err: nil, 1385 }, 1386 { // New account, 2 pooled tx, 14 slots left: accept nonce 2 1387 from: "alice", 1388 tx: makeUnsignedTx(2, 1, 1, 1), 1389 err: nil, 1390 }, 1391 { // New account, 3 pooled tx, 13 slots left: accept nonce 3 1392 from: "alice", 1393 tx: makeUnsignedTx(3, 1, 1, 1), 1394 err: nil, 1395 }, 1396 { // New account, 4 pooled tx, 12 slots left: accept nonce 4 1397 from: "alice", 1398 tx: makeUnsignedTx(4, 1, 1, 1), 1399 err: nil, 1400 }, 1401 { // New account, 5 pooled tx, 11 slots left: accept nonce 5 1402 from: "alice", 1403 tx: makeUnsignedTx(5, 1, 1, 1), 1404 err: nil, 1405 }, 1406 { // New account, 6 pooled tx, 10 slots left: accept nonce 6 1407 from: "alice", 1408 tx: makeUnsignedTx(6, 1, 1, 1), 1409 err: nil, 1410 }, 1411 { // New account, 7 pooled tx, 9 slots left: accept nonce 7 1412 from: "alice", 1413 tx: makeUnsignedTx(7, 1, 1, 1), 1414 err: nil, 1415 }, 1416 { // New account, 8 pooled tx, 8 slots left: accept nonce 8 1417 from: "alice", 1418 tx: makeUnsignedTx(8, 1, 1, 1), 1419 err: nil, 1420 }, 1421 { // New account, 9 pooled tx, 7 slots left: accept nonce 9 1422 from: "alice", 1423 tx: makeUnsignedTx(9, 1, 1, 1), 1424 err: nil, 1425 }, 1426 { // New account, 10 pooled tx, 6 slots left: accept nonce 10 1427 from: "alice", 1428 tx: makeUnsignedTx(10, 1, 1, 1), 1429 err: nil, 1430 }, 1431 { // New account, 11 pooled tx, 5 slots left: accept nonce 11 1432 from: "alice", 1433 tx: makeUnsignedTx(11, 1, 1, 1), 1434 err: nil, 1435 }, 1436 { // New account, 12 pooled tx, 4 slots left: accept nonce 12 1437 from: "alice", 1438 tx: makeUnsignedTx(12, 1, 1, 1), 1439 err: nil, 1440 }, 1441 { // New account, 13 pooled tx, 3 slots left: accept nonce 13 1442 from: "alice", 1443 tx: makeUnsignedTx(13, 1, 1, 1), 1444 err: nil, 1445 }, 1446 { // New account, 14 pooled tx, 2 slots left: accept nonce 14 1447 from: "alice", 1448 tx: makeUnsignedTx(14, 1, 1, 1), 1449 err: nil, 1450 }, 1451 { // New account, 15 pooled tx, 1 slots left: accept nonce 15 1452 from: "alice", 1453 tx: makeUnsignedTx(15, 1, 1, 1), 1454 err: nil, 1455 }, 1456 { // New account, 16 pooled tx, 0 slots left: accept nonce 15 replacement 1457 from: "alice", 1458 tx: makeUnsignedTx(15, 10, 10, 10), 1459 err: nil, 1460 }, 1461 { // New account, 16 pooled tx, 0 slots left: reject nonce 16 with overcap 1462 from: "alice", 1463 tx: makeUnsignedTx(16, 1, 1, 1), 1464 err: txpool.ErrAccountLimitExceeded, 1465 }, 1466 }, 1467 }, 1468 // Previously existing transactions should be allowed to be replaced iff 1469 // the new cumulative expenditure can be covered by the account and the 1470 // prices are bumped all around (no percentage check here). 1471 { 1472 seeds: map[string]seed{ 1473 "alice": {balance: 2*100 + 5*21000 + 3*blobSize}, 1474 }, 1475 adds: []addtx{ 1476 { // New account, no previous txs: reject nonce 0 with 341172 wei spend 1477 from: "alice", 1478 tx: makeUnsignedTx(0, 1, 20, 1), 1479 err: core.ErrInsufficientFunds, 1480 }, 1481 { // New account, no previous txs: accept nonce 0 with 173172 wei spend 1482 from: "alice", 1483 tx: makeUnsignedTx(0, 1, 2, 1), 1484 err: nil, 1485 }, 1486 { // New account, 1 pooled tx with 173172 wei spent: accept nonce 1 with 152172 wei spend 1487 from: "alice", 1488 tx: makeUnsignedTx(1, 1, 1, 1), 1489 err: nil, 1490 }, 1491 { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with 599684 wei spend (173072 extra) (would overflow balance at nonce 1) 1492 from: "alice", 1493 tx: makeUnsignedTx(0, 2, 5, 2), 1494 err: core.ErrInsufficientFunds, 1495 }, 1496 { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-gastip-bump 1497 from: "alice", 1498 tx: makeUnsignedTx(0, 1, 3, 2), 1499 err: txpool.ErrReplaceUnderpriced, 1500 }, 1501 { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-gascap-bump 1502 from: "alice", 1503 tx: makeUnsignedTx(0, 2, 2, 2), 1504 err: txpool.ErrReplaceUnderpriced, 1505 }, 1506 { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-blobcap-bump 1507 from: "alice", 1508 tx: makeUnsignedTx(0, 2, 4, 1), 1509 err: txpool.ErrReplaceUnderpriced, 1510 }, 1511 { // New account, 2 pooled tx with 325344 wei spent: accept nonce 0 with 84100 wei spend (42000 extra) 1512 from: "alice", 1513 tx: makeUnsignedTx(0, 2, 4, 2), 1514 err: nil, 1515 }, 1516 }, 1517 }, 1518 // Previously existing transactions should be allowed to be replaced iff 1519 // the new prices are bumped by a sufficient amount. 1520 { 1521 seeds: map[string]seed{ 1522 "alice": {balance: 100 + 8*21000 + 4*blobSize}, 1523 }, 1524 adds: []addtx{ 1525 { // New account, no previous txs: accept nonce 0 1526 from: "alice", 1527 tx: makeUnsignedTx(0, 2, 4, 2), 1528 err: nil, 1529 }, 1530 { // New account, 1 pooled tx: reject nonce 0 with low-gastip-bump 1531 from: "alice", 1532 tx: makeUnsignedTx(0, 3, 8, 4), 1533 err: txpool.ErrReplaceUnderpriced, 1534 }, 1535 { // New account, 1 pooled tx: reject nonce 0 with low-gascap-bump 1536 from: "alice", 1537 tx: makeUnsignedTx(0, 4, 6, 4), 1538 err: txpool.ErrReplaceUnderpriced, 1539 }, 1540 { // New account, 1 pooled tx: reject nonce 0 with low-blobcap-bump 1541 from: "alice", 1542 tx: makeUnsignedTx(0, 4, 8, 3), 1543 err: txpool.ErrReplaceUnderpriced, 1544 }, 1545 { // New account, 1 pooled tx: accept nonce 0 with all-bumps 1546 from: "alice", 1547 tx: makeUnsignedTx(0, 4, 8, 4), 1548 err: nil, 1549 }, 1550 }, 1551 }, 1552 // Blob transactions that don't meet the min blob gas price should be rejected 1553 { 1554 seeds: map[string]seed{ 1555 "alice": {balance: 10000000}, 1556 }, 1557 adds: []addtx{ 1558 { // New account, no previous txs, nonce 0, but blob fee cap too low 1559 from: "alice", 1560 tx: makeUnsignedTx(0, 1, 1, 0), 1561 err: txpool.ErrTxGasPriceTooLow, 1562 }, 1563 { // Same as above but blob fee cap equals minimum, should be accepted 1564 from: "alice", 1565 tx: makeUnsignedTx(0, 1, 1, params.BlobTxMinBlobGasprice), 1566 err: nil, 1567 }, 1568 }, 1569 }, 1570 // Tests issue #30518 where a refactor broke internal state invariants, 1571 // causing included transactions not to be properly accounted and thus 1572 // account states going our of sync with the chain. 1573 { 1574 seeds: map[string]seed{ 1575 "alice": { 1576 balance: 1000000, 1577 txs: []*types.BlobTx{ 1578 makeUnsignedTx(0, 1, 1, 1), 1579 }, 1580 }, 1581 }, 1582 block: []addtx{ 1583 { 1584 from: "alice", 1585 tx: makeUnsignedTx(0, 1, 1, 1), 1586 }, 1587 }, 1588 }, 1589 } 1590 for i, tt := range tests { 1591 // Create a temporary folder for the persistent backend 1592 storage := filepath.Join(t.TempDir(), fmt.Sprintf("test-%d", i)) 1593 1594 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 1595 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(testMaxBlobsPerBlock), nil) 1596 1597 // Insert the seed transactions for the pool startup 1598 var ( 1599 keys = make(map[string]*ecdsa.PrivateKey) 1600 addrs = make(map[string]common.Address) 1601 ) 1602 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 1603 for acc, seed := range tt.seeds { 1604 // Generate a new random key/address for the seed account 1605 keys[acc], _ = crypto.GenerateKey() 1606 addrs[acc] = crypto.PubkeyToAddress(keys[acc].PublicKey) 1607 1608 // Seed the state database with this account 1609 statedb.AddBalance(addrs[acc], new(uint256.Int).SetUint64(seed.balance), tracing.BalanceChangeUnspecified) 1610 statedb.SetNonce(addrs[acc], seed.nonce, tracing.NonceChangeUnspecified) 1611 1612 // Sign the seed transactions and store them in the data store 1613 for _, tx := range seed.txs { 1614 signed := types.MustSignNewTx(keys[acc], types.LatestSigner(params.MainnetChainConfig), tx) 1615 blob, _ := rlp.EncodeToBytes(signed) 1616 store.Put(blob) 1617 } 1618 } 1619 statedb.Commit(0, true, false) 1620 store.Close() 1621 1622 // Create a blob pool out of the pre-seeded dats 1623 chain := &testBlockChain{ 1624 config: params.MainnetChainConfig, 1625 basefee: uint256.NewInt(1050), 1626 blobfee: uint256.NewInt(105), 1627 statedb: statedb, 1628 } 1629 pool := New(Config{Datadir: storage}, chain, nil) 1630 if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { 1631 t.Fatalf("test %d: failed to create blob pool: %v", i, err) 1632 } 1633 verifyPoolInternals(t, pool) 1634 1635 // Add each transaction one by one, verifying the pool internals in between 1636 for j, add := range tt.adds { 1637 signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(params.MainnetChainConfig), add.tx) 1638 if err := pool.add(signed); !errors.Is(err, add.err) { 1639 t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err) 1640 } 1641 if add.err == nil { 1642 size, exist := pool.lookup.sizeOfTx(signed.Hash()) 1643 if !exist { 1644 t.Errorf("test %d, tx %d: failed to lookup transaction's size", i, j) 1645 } 1646 if size != signed.Size() { 1647 t.Errorf("test %d, tx %d: transaction's size mismatches: have %v, want %v", 1648 i, j, size, signed.Size()) 1649 } 1650 } 1651 verifyPoolInternals(t, pool) 1652 } 1653 verifyPoolInternals(t, pool) 1654 1655 // If the test contains a chain head event, run that and gain verify the internals 1656 if tt.block != nil { 1657 // Fake a header for the new set of transactions 1658 header := &types.Header{ 1659 Number: big.NewInt(int64(chain.CurrentBlock().Number.Uint64() + 1)), 1660 Difficulty: common.Big0, 1661 BaseFee: chain.CurrentBlock().BaseFee, // invalid, but nothing checks it, yolo 1662 } 1663 // Inject the fake block into the chain 1664 txs := make([]*types.Transaction, len(tt.block)) 1665 for j, inc := range tt.block { 1666 txs[j] = types.MustSignNewTx(keys[inc.from], types.LatestSigner(params.MainnetChainConfig), inc.tx) 1667 } 1668 chain.blocks = map[uint64]*types.Block{ 1669 header.Number.Uint64(): types.NewBlockWithHeader(header).WithBody(types.Body{ 1670 Transactions: txs, 1671 }), 1672 } 1673 // Apply the nonce updates to the state db 1674 for _, tx := range txs { 1675 sender, _ := types.Sender(types.LatestSigner(params.MainnetChainConfig), tx) 1676 chain.statedb.SetNonce(sender, tx.Nonce()+1, tracing.NonceChangeUnspecified) 1677 } 1678 pool.Reset(chain.CurrentBlock(), header) 1679 verifyPoolInternals(t, pool) 1680 } 1681 // Close down the test 1682 pool.Close() 1683 } 1684 } 1685 1686 // fakeBilly is a billy.Database implementation which just drops data on the floor. 1687 type fakeBilly struct { 1688 billy.Database 1689 count uint64 1690 } 1691 1692 func (f *fakeBilly) Put(data []byte) (uint64, error) { 1693 f.count++ 1694 return f.count, nil 1695 } 1696 1697 var _ billy.Database = (*fakeBilly)(nil) 1698 1699 // Benchmarks the time it takes to assemble the lazy pending transaction list 1700 // from the pool contents. 1701 func BenchmarkPoolPending100Mb(b *testing.B) { benchmarkPoolPending(b, 100_000_000) } 1702 func BenchmarkPoolPending1GB(b *testing.B) { benchmarkPoolPending(b, 1_000_000_000) } 1703 func BenchmarkPoolPending10GB(b *testing.B) { 1704 if testing.Short() { 1705 b.Skip("Skipping in short-mode") 1706 } 1707 benchmarkPoolPending(b, 10_000_000_000) 1708 } 1709 1710 func benchmarkPoolPending(b *testing.B, datacap uint64) { 1711 // Calculate the maximum number of transaction that would fit into the pool 1712 // and generate a set of random accounts to seed them with. 1713 capacity := datacap / params.BlobTxBlobGasPerBlob 1714 1715 var ( 1716 basefee = uint64(1050) 1717 blobfee = uint64(105) 1718 signer = types.LatestSigner(params.MainnetChainConfig) 1719 statedb, _ = state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 1720 chain = &testBlockChain{ 1721 config: params.MainnetChainConfig, 1722 basefee: uint256.NewInt(basefee), 1723 blobfee: uint256.NewInt(blobfee), 1724 statedb: statedb, 1725 } 1726 pool = New(Config{Datadir: ""}, chain, nil) 1727 ) 1728 1729 if err := pool.Init(1, chain.CurrentBlock(), newReserver()); err != nil { 1730 b.Fatalf("failed to create blob pool: %v", err) 1731 } 1732 // Make the pool not use disk (just drop everything). This test never reads 1733 // back the data, it just iterates over the pool in-memory items 1734 pool.store = &fakeBilly{pool.store, 0} 1735 // Avoid validation - verifying all blob proofs take significant time 1736 // when the capacity is large. The purpose of this bench is to measure assembling 1737 // the lazies, not the kzg verifications. 1738 pool.txValidationFn = func(tx *types.Transaction, head *types.Header, signer types.Signer, opts *txpool.ValidationOptions) error { 1739 return nil // accept all 1740 } 1741 // Fill the pool up with one random transaction from each account with the 1742 // same price and everything to maximize the worst case scenario 1743 for i := 0; i < int(capacity); i++ { 1744 blobtx := makeUnsignedTx(0, 10, basefee+10, blobfee) 1745 blobtx.R = uint256.NewInt(1) 1746 blobtx.S = uint256.NewInt(uint64(100 + i)) 1747 blobtx.V = uint256.NewInt(0) 1748 tx := types.NewTx(blobtx) 1749 addr, err := types.Sender(signer, tx) 1750 if err != nil { 1751 b.Fatal(err) 1752 } 1753 statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 1754 pool.add(tx) 1755 } 1756 statedb.Commit(0, true, false) 1757 defer pool.Close() 1758 1759 // Benchmark assembling the pending 1760 b.ResetTimer() 1761 b.ReportAllocs() 1762 1763 for i := 0; i < b.N; i++ { 1764 p := pool.Pending(txpool.PendingFilter{ 1765 MinTip: uint256.NewInt(1), 1766 BaseFee: chain.basefee, 1767 BlobFee: chain.blobfee, 1768 }) 1769 if len(p) != int(capacity) { 1770 b.Fatalf("have %d want %d", len(p), capacity) 1771 } 1772 } 1773 }