github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/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 "math" 25 "math/big" 26 "os" 27 "path/filepath" 28 "sync" 29 "testing" 30 "time" 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/rawdb" 37 "github.com/ethereum/go-ethereum/core/state" 38 "github.com/ethereum/go-ethereum/core/tracing" 39 "github.com/ethereum/go-ethereum/core/txpool" 40 "github.com/ethereum/go-ethereum/core/types" 41 "github.com/ethereum/go-ethereum/crypto" 42 "github.com/ethereum/go-ethereum/crypto/kzg4844" 43 "github.com/ethereum/go-ethereum/ethdb/memorydb" 44 "github.com/ethereum/go-ethereum/log" 45 "github.com/ethereum/go-ethereum/params" 46 "github.com/ethereum/go-ethereum/rlp" 47 "github.com/holiman/billy" 48 "github.com/holiman/uint256" 49 ) 50 51 var ( 52 emptyBlob = new(kzg4844.Blob) 53 emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob) 54 emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit) 55 emptyBlobVHash = kzg4844.CalcBlobHashV1(sha256.New(), &emptyBlobCommit) 56 ) 57 58 // Chain configuration with Cancun enabled. 59 // 60 // TODO(karalabe): replace with params.MainnetChainConfig after Cancun. 61 var testChainConfig *params.ChainConfig 62 63 func init() { 64 testChainConfig = new(params.ChainConfig) 65 *testChainConfig = *params.MainnetChainConfig 66 67 testChainConfig.CancunTime = new(uint64) 68 *testChainConfig.CancunTime = uint64(time.Now().Unix()) 69 } 70 71 // testBlockChain is a mock of the live chain for testing the pool. 72 type testBlockChain struct { 73 config *params.ChainConfig 74 basefee *uint256.Int 75 blobfee *uint256.Int 76 statedb *state.StateDB 77 } 78 79 func (bc *testBlockChain) Config() *params.ChainConfig { 80 return bc.config 81 } 82 83 func (bc *testBlockChain) CurrentBlock() *types.Header { 84 // Yolo, life is too short to invert mist.CalcBaseFee and misc.CalcBlobFee, 85 // just binary search it them. 86 87 // The base fee at 5714 ETH translates into the 21000 base gas higher than 88 // mainnet ether existence, use that as a cap for the tests. 89 var ( 90 blockNumber = new(big.Int).Add(bc.config.LondonBlock, big.NewInt(1)) 91 blockTime = *bc.config.CancunTime + 1 92 gasLimit = uint64(30_000_000) 93 ) 94 lo := new(big.Int) 95 hi := new(big.Int).Mul(big.NewInt(5714), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil)) 96 97 for new(big.Int).Add(lo, big.NewInt(1)).Cmp(hi) != 0 { 98 mid := new(big.Int).Add(lo, hi) 99 mid.Div(mid, big.NewInt(2)) 100 101 if eip1559.CalcBaseFee(bc.config, &types.Header{ 102 Number: blockNumber, 103 GasLimit: gasLimit, 104 GasUsed: 0, 105 BaseFee: mid, 106 }).Cmp(bc.basefee.ToBig()) > 0 { 107 hi = mid 108 } else { 109 lo = mid 110 } 111 } 112 baseFee := lo 113 114 // The excess blob gas at 2^27 translates into a blob fee higher than mainnet 115 // ether existence, use that as a cap for the tests. 116 lo = new(big.Int) 117 hi = new(big.Int).Exp(big.NewInt(2), big.NewInt(27), nil) 118 119 for new(big.Int).Add(lo, big.NewInt(1)).Cmp(hi) != 0 { 120 mid := new(big.Int).Add(lo, hi) 121 mid.Div(mid, big.NewInt(2)) 122 123 if eip4844.CalcBlobFee(mid.Uint64()).Cmp(bc.blobfee.ToBig()) > 0 { 124 hi = mid 125 } else { 126 lo = mid 127 } 128 } 129 excessBlobGas := lo.Uint64() 130 131 return &types.Header{ 132 Number: blockNumber, 133 Time: blockTime, 134 GasLimit: gasLimit, 135 BaseFee: baseFee, 136 ExcessBlobGas: &excessBlobGas, 137 } 138 } 139 140 func (bc *testBlockChain) CurrentFinalBlock() *types.Header { 141 return &types.Header{ 142 Number: big.NewInt(0), 143 } 144 } 145 146 func (bt *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block { 147 return nil 148 } 149 150 func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) { 151 return bc.statedb, nil 152 } 153 154 // makeAddressReserver is a utility method to sanity check that accounts are 155 // properly reserved by the blobpool (no duplicate reserves or unreserves). 156 func makeAddressReserver() txpool.AddressReserver { 157 var ( 158 reserved = make(map[common.Address]struct{}) 159 lock sync.Mutex 160 ) 161 return func(addr common.Address, reserve bool) error { 162 lock.Lock() 163 defer lock.Unlock() 164 165 _, exists := reserved[addr] 166 if reserve { 167 if exists { 168 panic("already reserved") 169 } 170 reserved[addr] = struct{}{} 171 return nil 172 } 173 if !exists { 174 panic("not reserved") 175 } 176 delete(reserved, addr) 177 return nil 178 } 179 } 180 181 // makeTx is a utility method to construct a random blob transaction and sign it 182 // with a valid key, only setting the interesting fields from the perspective of 183 // the blob pool. 184 func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction { 185 blobtx := makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap) 186 return types.MustSignNewTx(key, types.LatestSigner(testChainConfig), blobtx) 187 } 188 189 // makeUnsignedTx is a utility method to construct a random blob transaction 190 // without signing it. 191 func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx { 192 return &types.BlobTx{ 193 ChainID: uint256.MustFromBig(testChainConfig.ChainID), 194 Nonce: nonce, 195 GasTipCap: uint256.NewInt(gasTipCap), 196 GasFeeCap: uint256.NewInt(gasFeeCap), 197 Gas: 21000, 198 BlobFeeCap: uint256.NewInt(blobFeeCap), 199 BlobHashes: []common.Hash{emptyBlobVHash}, 200 Value: uint256.NewInt(100), 201 Sidecar: &types.BlobTxSidecar{ 202 Blobs: []kzg4844.Blob{*emptyBlob}, 203 Commitments: []kzg4844.Commitment{emptyBlobCommit}, 204 Proofs: []kzg4844.Proof{emptyBlobProof}, 205 }, 206 } 207 } 208 209 // verifyPoolInternals iterates over all the transactions in the pool and checks 210 // that sort orders, calculated fields, cumulated fields are correct. 211 func verifyPoolInternals(t *testing.T, pool *BlobPool) { 212 // Mark this method as a helper to remove from stack traces 213 t.Helper() 214 215 // Verify that all items in the index are present in the lookup and nothing more 216 seen := make(map[common.Hash]struct{}) 217 for addr, txs := range pool.index { 218 for _, tx := range txs { 219 if _, ok := seen[tx.hash]; ok { 220 t.Errorf("duplicate hash #%x in transaction index: address %s, nonce %d", tx.hash, addr, tx.nonce) 221 } 222 seen[tx.hash] = struct{}{} 223 } 224 } 225 for hash, id := range pool.lookup { 226 if _, ok := seen[hash]; !ok { 227 t.Errorf("lookup entry missing from transaction index: hash #%x, id %d", hash, id) 228 } 229 delete(seen, hash) 230 } 231 for hash := range seen { 232 t.Errorf("indexed transaction hash #%x missing from lookup table", hash) 233 } 234 // Verify that transactions are sorted per account and contain no nonce gaps 235 for addr, txs := range pool.index { 236 for i := 1; i < len(txs); i++ { 237 if txs[i].nonce != txs[i-1].nonce+1 { 238 t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].nonce, txs[i-1].nonce+1) 239 } 240 } 241 } 242 // Verify that calculated evacuation thresholds are correct 243 for addr, txs := range pool.index { 244 if !txs[0].evictionExecTip.Eq(txs[0].execTipCap) { 245 t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, 0, txs[0].evictionExecTip, txs[0].execTipCap) 246 } 247 if math.Abs(txs[0].evictionExecFeeJumps-txs[0].basefeeJumps) > 0.001 { 248 t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionExecFeeJumps, txs[0].basefeeJumps) 249 } 250 if math.Abs(txs[0].evictionBlobFeeJumps-txs[0].blobfeeJumps) > 0.001 { 251 t.Errorf("addr %v, tx %d eviction blob fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionBlobFeeJumps, txs[0].blobfeeJumps) 252 } 253 for i := 1; i < len(txs); i++ { 254 wantExecTip := txs[i-1].evictionExecTip 255 if wantExecTip.Gt(txs[i].execTipCap) { 256 wantExecTip = txs[i].execTipCap 257 } 258 if !txs[i].evictionExecTip.Eq(wantExecTip) { 259 t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, i, txs[i].evictionExecTip, wantExecTip) 260 } 261 262 wantExecFeeJumps := txs[i-1].evictionExecFeeJumps 263 if wantExecFeeJumps > txs[i].basefeeJumps { 264 wantExecFeeJumps = txs[i].basefeeJumps 265 } 266 if math.Abs(txs[i].evictionExecFeeJumps-wantExecFeeJumps) > 0.001 { 267 t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, i, txs[i].evictionExecFeeJumps, wantExecFeeJumps) 268 } 269 270 wantBlobFeeJumps := txs[i-1].evictionBlobFeeJumps 271 if wantBlobFeeJumps > txs[i].blobfeeJumps { 272 wantBlobFeeJumps = txs[i].blobfeeJumps 273 } 274 if math.Abs(txs[i].evictionBlobFeeJumps-wantBlobFeeJumps) > 0.001 { 275 t.Errorf("addr %v, tx %d eviction blob fee jumps mismatch: have %f, want %f", addr, i, txs[i].evictionBlobFeeJumps, wantBlobFeeJumps) 276 } 277 } 278 } 279 // Verify that account balance accumulations are correct 280 for addr, txs := range pool.index { 281 spent := new(uint256.Int) 282 for _, tx := range txs { 283 spent.Add(spent, tx.costCap) 284 } 285 if !pool.spent[addr].Eq(spent) { 286 t.Errorf("addr %v expenditure mismatch: have %d, want %d", addr, pool.spent[addr], spent) 287 } 288 } 289 // Verify that pool storage size is correct 290 var stored uint64 291 for _, txs := range pool.index { 292 for _, tx := range txs { 293 stored += uint64(tx.size) 294 } 295 } 296 if pool.stored != stored { 297 t.Errorf("pool storage mismatch: have %d, want %d", pool.stored, stored) 298 } 299 // Verify the price heap internals 300 verifyHeapInternals(t, pool.evict) 301 } 302 303 // Tests that transactions can be loaded from disk on startup and that they are 304 // correctly discarded if invalid. 305 // 306 // - 1. A transaction that cannot be decoded must be dropped 307 // - 2. A transaction that cannot be recovered (bad signature) must be dropped 308 // - 3. All transactions after a nonce gap must be dropped 309 // - 4. All transactions after an already included nonce must be dropped 310 // - 5. All transactions after an underpriced one (including it) must be dropped 311 // - 6. All transactions after an overdrafting sequence must be dropped 312 // - 7. All transactions exceeding the per-account limit must be dropped 313 // 314 // Furthermore, some strange corner-cases can also occur after a crash, as Billy's 315 // simplicity also allows it to resurrect past deleted entities: 316 // 317 // - 8. Fully duplicate transactions (matching hash) must be dropped 318 // - 9. Duplicate nonces from the same account must be dropped 319 func TestOpenDrops(t *testing.T) { 320 log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 321 322 // Create a temporary folder for the persistent backend 323 storage, _ := os.MkdirTemp("", "blobpool-") 324 defer os.RemoveAll(storage) 325 326 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 327 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) 328 329 // Insert a malformed transaction to verify that decoding errors (or format 330 // changes) are handled gracefully (case 1) 331 malformed, _ := store.Put([]byte("this is a badly encoded transaction")) 332 333 // Insert a transaction with a bad signature to verify that stale junk after 334 // potential hard-forks can get evicted (case 2) 335 tx := types.NewTx(&types.BlobTx{ 336 ChainID: uint256.MustFromBig(testChainConfig.ChainID), 337 GasTipCap: new(uint256.Int), 338 GasFeeCap: new(uint256.Int), 339 Gas: 0, 340 Value: new(uint256.Int), 341 Data: nil, 342 BlobFeeCap: new(uint256.Int), 343 V: new(uint256.Int), 344 R: new(uint256.Int), 345 S: new(uint256.Int), 346 }) 347 blob, _ := rlp.EncodeToBytes(tx) 348 badsig, _ := store.Put(blob) 349 350 // Insert a sequence of transactions with a nonce gap in between to verify 351 // that anything gapped will get evicted (case 3). 352 var ( 353 gapper, _ = crypto.GenerateKey() 354 355 valids = make(map[uint64]struct{}) 356 gapped = make(map[uint64]struct{}) 357 ) 358 for _, nonce := range []uint64{0, 1, 3, 4, 6, 7} { // first gap at #2, another at #5 359 tx := makeTx(nonce, 1, 1, 1, gapper) 360 blob, _ := rlp.EncodeToBytes(tx) 361 362 id, _ := store.Put(blob) 363 if nonce < 2 { 364 valids[id] = struct{}{} 365 } else { 366 gapped[id] = struct{}{} 367 } 368 } 369 // Insert a sequence of transactions with a gapped starting nonce to verify 370 // that the entire set will get dropped (case 3). 371 var ( 372 dangler, _ = crypto.GenerateKey() 373 dangling = make(map[uint64]struct{}) 374 ) 375 for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling 376 tx := makeTx(nonce, 1, 1, 1, dangler) 377 blob, _ := rlp.EncodeToBytes(tx) 378 379 id, _ := store.Put(blob) 380 dangling[id] = struct{}{} 381 } 382 // Insert a sequence of transactions with already passed nonces to veirfy 383 // that the entire set will get dropped (case 4). 384 var ( 385 filler, _ = crypto.GenerateKey() 386 filled = make(map[uint64]struct{}) 387 ) 388 for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled 389 tx := makeTx(nonce, 1, 1, 1, filler) 390 blob, _ := rlp.EncodeToBytes(tx) 391 392 id, _ := store.Put(blob) 393 filled[id] = struct{}{} 394 } 395 // Insert a sequence of transactions with partially passed nonces to verify 396 // that the included part of the set will get dropped (case 4). 397 var ( 398 overlapper, _ = crypto.GenerateKey() 399 overlapped = make(map[uint64]struct{}) 400 ) 401 for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled 402 tx := makeTx(nonce, 1, 1, 1, overlapper) 403 blob, _ := rlp.EncodeToBytes(tx) 404 405 id, _ := store.Put(blob) 406 if nonce >= 2 { 407 valids[id] = struct{}{} 408 } else { 409 overlapped[id] = struct{}{} 410 } 411 } 412 // Insert a sequence of transactions with an underpriced first to verify that 413 // the entire set will get dropped (case 5). 414 var ( 415 underpayer, _ = crypto.GenerateKey() 416 underpaid = make(map[uint64]struct{}) 417 ) 418 for i := 0; i < 5; i++ { // make #0 underpriced 419 var tx *types.Transaction 420 if i == 0 { 421 tx = makeTx(uint64(i), 0, 0, 0, underpayer) 422 } else { 423 tx = makeTx(uint64(i), 1, 1, 1, underpayer) 424 } 425 blob, _ := rlp.EncodeToBytes(tx) 426 427 id, _ := store.Put(blob) 428 underpaid[id] = struct{}{} 429 } 430 431 // Insert a sequence of transactions with an underpriced in between to verify 432 // that it and anything newly gapped will get evicted (case 5). 433 var ( 434 outpricer, _ = crypto.GenerateKey() 435 outpriced = make(map[uint64]struct{}) 436 ) 437 for i := 0; i < 5; i++ { // make #2 underpriced 438 var tx *types.Transaction 439 if i == 2 { 440 tx = makeTx(uint64(i), 0, 0, 0, outpricer) 441 } else { 442 tx = makeTx(uint64(i), 1, 1, 1, outpricer) 443 } 444 blob, _ := rlp.EncodeToBytes(tx) 445 446 id, _ := store.Put(blob) 447 if i < 2 { 448 valids[id] = struct{}{} 449 } else { 450 outpriced[id] = struct{}{} 451 } 452 } 453 // Insert a sequence of transactions fully overdrafted to verify that the 454 // entire set will get invalidated (case 6). 455 var ( 456 exceeder, _ = crypto.GenerateKey() 457 exceeded = make(map[uint64]struct{}) 458 ) 459 for _, nonce := range []uint64{0, 1, 2} { // nonce 0 overdrafts the account 460 var tx *types.Transaction 461 if nonce == 0 { 462 tx = makeTx(nonce, 1, 100, 1, exceeder) 463 } else { 464 tx = makeTx(nonce, 1, 1, 1, exceeder) 465 } 466 blob, _ := rlp.EncodeToBytes(tx) 467 468 id, _ := store.Put(blob) 469 exceeded[id] = struct{}{} 470 } 471 // Insert a sequence of transactions partially overdrafted to verify that part 472 // of the set will get invalidated (case 6). 473 var ( 474 overdrafter, _ = crypto.GenerateKey() 475 overdrafted = make(map[uint64]struct{}) 476 ) 477 for _, nonce := range []uint64{0, 1, 2} { // nonce 1 overdrafts the account 478 var tx *types.Transaction 479 if nonce == 1 { 480 tx = makeTx(nonce, 1, 100, 1, overdrafter) 481 } else { 482 tx = makeTx(nonce, 1, 1, 1, overdrafter) 483 } 484 blob, _ := rlp.EncodeToBytes(tx) 485 486 id, _ := store.Put(blob) 487 if nonce < 1 { 488 valids[id] = struct{}{} 489 } else { 490 overdrafted[id] = struct{}{} 491 } 492 } 493 // Insert a sequence of transactions overflowing the account cap to verify 494 // that part of the set will get invalidated (case 7). 495 var ( 496 overcapper, _ = crypto.GenerateKey() 497 overcapped = make(map[uint64]struct{}) 498 ) 499 for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ { 500 blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, overcapper)) 501 502 id, _ := store.Put(blob) 503 if nonce < maxTxsPerAccount { 504 valids[id] = struct{}{} 505 } else { 506 overcapped[id] = struct{}{} 507 } 508 } 509 // Insert a batch of duplicated transactions to verify that only one of each 510 // version will remain (case 8). 511 var ( 512 duplicater, _ = crypto.GenerateKey() 513 duplicated = make(map[uint64]struct{}) 514 ) 515 for _, nonce := range []uint64{0, 1, 2} { 516 blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, 1, 1, duplicater)) 517 518 for i := 0; i < int(nonce)+1; i++ { 519 id, _ := store.Put(blob) 520 if i == 0 { 521 valids[id] = struct{}{} 522 } else { 523 duplicated[id] = struct{}{} 524 } 525 } 526 } 527 // Insert a batch of duplicated nonces to verify that only one of each will 528 // remain (case 9). 529 var ( 530 repeater, _ = crypto.GenerateKey() 531 repeated = make(map[uint64]struct{}) 532 ) 533 for _, nonce := range []uint64{0, 1, 2} { 534 for i := 0; i < int(nonce)+1; i++ { 535 blob, _ := rlp.EncodeToBytes(makeTx(nonce, 1, uint64(i)+1 /* unique hashes */, 1, repeater)) 536 537 id, _ := store.Put(blob) 538 if i == 0 { 539 valids[id] = struct{}{} 540 } else { 541 repeated[id] = struct{}{} 542 } 543 } 544 } 545 store.Close() 546 547 // Create a blob pool out of the pre-seeded data 548 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) 549 statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 550 statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 551 statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 552 statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3) 553 statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 554 statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2) 555 statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 556 statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 557 statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 558 statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 559 statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), uint256.NewInt(10000000), tracing.BalanceChangeUnspecified) 560 statedb.AddBalance(crypto.PubkeyToAddress(duplicater.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 561 statedb.AddBalance(crypto.PubkeyToAddress(repeater.PublicKey), uint256.NewInt(1000000), tracing.BalanceChangeUnspecified) 562 statedb.Commit(0, true) 563 564 chain := &testBlockChain{ 565 config: testChainConfig, 566 basefee: uint256.NewInt(params.InitialBaseFee), 567 blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), 568 statedb: statedb, 569 } 570 pool := New(Config{Datadir: storage}, chain) 571 if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { 572 t.Fatalf("failed to create blob pool: %v", err) 573 } 574 defer pool.Close() 575 576 // Verify that the malformed (case 1), badly signed (case 2) and gapped (case 577 // 3) txs have been deleted from the pool 578 alive := make(map[uint64]struct{}) 579 for _, txs := range pool.index { 580 for _, tx := range txs { 581 switch tx.id { 582 case malformed: 583 t.Errorf("malformed RLP transaction remained in storage") 584 case badsig: 585 t.Errorf("invalidly signed transaction remained in storage") 586 default: 587 if _, ok := dangling[tx.id]; ok { 588 t.Errorf("dangling transaction remained in storage: %d", tx.id) 589 } else if _, ok := filled[tx.id]; ok { 590 t.Errorf("filled transaction remained in storage: %d", tx.id) 591 } else if _, ok := overlapped[tx.id]; ok { 592 t.Errorf("overlapped transaction remained in storage: %d", tx.id) 593 } else if _, ok := gapped[tx.id]; ok { 594 t.Errorf("gapped transaction remained in storage: %d", tx.id) 595 } else if _, ok := underpaid[tx.id]; ok { 596 t.Errorf("underpaid transaction remained in storage: %d", tx.id) 597 } else if _, ok := outpriced[tx.id]; ok { 598 t.Errorf("outpriced transaction remained in storage: %d", tx.id) 599 } else if _, ok := exceeded[tx.id]; ok { 600 t.Errorf("fully overdrafted transaction remained in storage: %d", tx.id) 601 } else if _, ok := overdrafted[tx.id]; ok { 602 t.Errorf("partially overdrafted transaction remained in storage: %d", tx.id) 603 } else if _, ok := overcapped[tx.id]; ok { 604 t.Errorf("overcapped transaction remained in storage: %d", tx.id) 605 } else if _, ok := duplicated[tx.id]; ok { 606 t.Errorf("duplicated transaction remained in storage: %d", tx.id) 607 } else if _, ok := repeated[tx.id]; ok { 608 t.Errorf("repeated nonce transaction remained in storage: %d", tx.id) 609 } else { 610 alive[tx.id] = struct{}{} 611 } 612 } 613 } 614 } 615 // Verify that the rest of the transactions remained alive 616 if len(alive) != len(valids) { 617 t.Errorf("valid transaction count mismatch: have %d, want %d", len(alive), len(valids)) 618 } 619 for id := range alive { 620 if _, ok := valids[id]; !ok { 621 t.Errorf("extra transaction %d", id) 622 } 623 } 624 for id := range valids { 625 if _, ok := alive[id]; !ok { 626 t.Errorf("missing transaction %d", id) 627 } 628 } 629 // Verify all the calculated pool internals. Interestingly, this is **not** 630 // a duplication of the above checks, this actually validates the verifier 631 // using the above already hard coded checks. 632 // 633 // Do not remove this, nor alter the above to be generic. 634 verifyPoolInternals(t, pool) 635 } 636 637 // Tests that transactions loaded from disk are indexed correctly. 638 // 639 // - 1. Transactions must be grouped by sender, sorted by nonce 640 // - 2. Eviction thresholds are calculated correctly for the sequences 641 // - 3. Balance usage of an account is totals across all transactions 642 func TestOpenIndex(t *testing.T) { 643 log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 644 645 // Create a temporary folder for the persistent backend 646 storage, _ := os.MkdirTemp("", "blobpool-") 647 defer os.RemoveAll(storage) 648 649 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 650 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) 651 652 // Insert a sequence of transactions with varying price points to check that 653 // the cumulative minimum will be maintained. 654 var ( 655 key, _ = crypto.GenerateKey() 656 addr = crypto.PubkeyToAddress(key.PublicKey) 657 658 txExecTipCaps = []uint64{10, 25, 5, 7, 1, 100} 659 txExecFeeCaps = []uint64{100, 90, 200, 10, 80, 300} 660 txBlobFeeCaps = []uint64{55, 66, 77, 33, 22, 11} 661 662 //basefeeJumps = []float64{39.098, 38.204, 44.983, 19.549, 37.204, 48.426} // log 1.125 (exec fee cap) 663 //blobfeeJumps = []float64{34.023, 35.570, 36.879, 29.686, 26.243, 20.358} // log 1.125 (blob fee cap) 664 665 evictExecTipCaps = []uint64{10, 10, 5, 5, 1, 1} 666 evictExecFeeJumps = []float64{39.098, 38.204, 38.204, 19.549, 19.549, 19.549} // min(log 1.125 (exec fee cap)) 667 evictBlobFeeJumps = []float64{34.023, 34.023, 34.023, 29.686, 26.243, 20.358} // min(log 1.125 (blob fee cap)) 668 669 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 670 ) 671 for _, i := range []int{5, 3, 4, 2, 0, 1} { // Randomize the tx insertion order to force sorting on load 672 tx := makeTx(uint64(i), txExecTipCaps[i], txExecFeeCaps[i], txBlobFeeCaps[i], key) 673 blob, _ := rlp.EncodeToBytes(tx) 674 store.Put(blob) 675 } 676 store.Close() 677 678 // Create a blob pool out of the pre-seeded data 679 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) 680 statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 681 statedb.Commit(0, true) 682 683 chain := &testBlockChain{ 684 config: testChainConfig, 685 basefee: uint256.NewInt(params.InitialBaseFee), 686 blobfee: uint256.NewInt(params.BlobTxMinBlobGasprice), 687 statedb: statedb, 688 } 689 pool := New(Config{Datadir: storage}, chain) 690 if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { 691 t.Fatalf("failed to create blob pool: %v", err) 692 } 693 defer pool.Close() 694 695 // Verify that the transactions have been sorted by nonce (case 1) 696 for i := 0; i < len(pool.index[addr]); i++ { 697 if pool.index[addr][i].nonce != uint64(i) { 698 t.Errorf("tx %d nonce mismatch: have %d, want %d", i, pool.index[addr][i].nonce, uint64(i)) 699 } 700 } 701 // Verify that the cumulative fee minimums have been correctly calculated (case 2) 702 for i, cap := range evictExecTipCaps { 703 if !pool.index[addr][i].evictionExecTip.Eq(uint256.NewInt(cap)) { 704 t.Errorf("eviction tip cap %d mismatch: have %d, want %d", i, pool.index[addr][i].evictionExecTip, cap) 705 } 706 } 707 for i, jumps := range evictExecFeeJumps { 708 if math.Abs(pool.index[addr][i].evictionExecFeeJumps-jumps) > 0.001 { 709 t.Errorf("eviction fee cap jumps %d mismatch: have %f, want %f", i, pool.index[addr][i].evictionExecFeeJumps, jumps) 710 } 711 } 712 for i, jumps := range evictBlobFeeJumps { 713 if math.Abs(pool.index[addr][i].evictionBlobFeeJumps-jumps) > 0.001 { 714 t.Errorf("eviction blob fee cap jumps %d mismatch: have %f, want %f", i, pool.index[addr][i].evictionBlobFeeJumps, jumps) 715 } 716 } 717 // Verify that the balance usage has been correctly calculated (case 3) 718 if !pool.spent[addr].Eq(totalSpent) { 719 t.Errorf("expenditure mismatch: have %d, want %d", pool.spent[addr], totalSpent) 720 } 721 // Verify all the calculated pool internals. Interestingly, this is **not** 722 // a duplication of the above checks, this actually validates the verifier 723 // using the above already hard coded checks. 724 // 725 // Do not remove this, nor alter the above to be generic. 726 verifyPoolInternals(t, pool) 727 } 728 729 // Tests that after indexing all the loaded transactions from disk, a price heap 730 // is correctly constructed based on the head basefee and blobfee. 731 func TestOpenHeap(t *testing.T) { 732 log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 733 734 // Create a temporary folder for the persistent backend 735 storage, _ := os.MkdirTemp("", "blobpool-") 736 defer os.RemoveAll(storage) 737 738 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 739 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) 740 741 // Insert a few transactions from a few accounts. To remove randomness from 742 // the heap initialization, use a deterministic account/tx/priority ordering. 743 var ( 744 key1, _ = crypto.GenerateKey() 745 key2, _ = crypto.GenerateKey() 746 key3, _ = crypto.GenerateKey() 747 748 addr1 = crypto.PubkeyToAddress(key1.PublicKey) 749 addr2 = crypto.PubkeyToAddress(key2.PublicKey) 750 addr3 = crypto.PubkeyToAddress(key3.PublicKey) 751 ) 752 if bytes.Compare(addr1[:], addr2[:]) > 0 { 753 key1, addr1, key2, addr2 = key2, addr2, key1, addr1 754 } 755 if bytes.Compare(addr1[:], addr3[:]) > 0 { 756 key1, addr1, key3, addr3 = key3, addr3, key1, addr1 757 } 758 if bytes.Compare(addr2[:], addr3[:]) > 0 { 759 key2, addr2, key3, addr3 = key3, addr3, key2, addr2 760 } 761 var ( 762 tx1 = makeTx(0, 1, 1000, 90, key1) 763 tx2 = makeTx(0, 1, 800, 70, key2) 764 tx3 = makeTx(0, 1, 1500, 110, key3) 765 766 blob1, _ = rlp.EncodeToBytes(tx1) 767 blob2, _ = rlp.EncodeToBytes(tx2) 768 blob3, _ = rlp.EncodeToBytes(tx3) 769 770 heapOrder = []common.Address{addr2, addr1, addr3} 771 heapIndex = map[common.Address]int{addr2: 0, addr1: 1, addr3: 2} 772 ) 773 store.Put(blob1) 774 store.Put(blob2) 775 store.Put(blob3) 776 store.Close() 777 778 // Create a blob pool out of the pre-seeded data 779 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) 780 statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 781 statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 782 statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 783 statedb.Commit(0, true) 784 785 chain := &testBlockChain{ 786 config: testChainConfig, 787 basefee: uint256.NewInt(1050), 788 blobfee: uint256.NewInt(105), 789 statedb: statedb, 790 } 791 pool := New(Config{Datadir: storage}, chain) 792 if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { 793 t.Fatalf("failed to create blob pool: %v", err) 794 } 795 defer pool.Close() 796 797 // Verify that the heap's internal state matches the expectations 798 for i, addr := range pool.evict.addrs { 799 if addr != heapOrder[i] { 800 t.Errorf("slot %d mismatch: have %v, want %v", i, addr, heapOrder[i]) 801 } 802 } 803 for addr, i := range pool.evict.index { 804 if i != heapIndex[addr] { 805 t.Errorf("index for %v mismatch: have %d, want %d", addr, i, heapIndex[addr]) 806 } 807 } 808 // Verify all the calculated pool internals. Interestingly, this is **not** 809 // a duplication of the above checks, this actually validates the verifier 810 // using the above already hard coded checks. 811 // 812 // Do not remove this, nor alter the above to be generic. 813 verifyPoolInternals(t, pool) 814 } 815 816 // Tests that after the pool's previous state is loaded back, any transactions 817 // over the new storage cap will get dropped. 818 func TestOpenCap(t *testing.T) { 819 log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 820 821 // Create a temporary folder for the persistent backend 822 storage, _ := os.MkdirTemp("", "blobpool-") 823 defer os.RemoveAll(storage) 824 825 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 826 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) 827 828 // Insert a few transactions from a few accounts 829 var ( 830 key1, _ = crypto.GenerateKey() 831 key2, _ = crypto.GenerateKey() 832 key3, _ = crypto.GenerateKey() 833 834 addr1 = crypto.PubkeyToAddress(key1.PublicKey) 835 addr2 = crypto.PubkeyToAddress(key2.PublicKey) 836 addr3 = crypto.PubkeyToAddress(key3.PublicKey) 837 838 tx1 = makeTx(0, 1, 1000, 100, key1) 839 tx2 = makeTx(0, 1, 800, 70, key2) 840 tx3 = makeTx(0, 1, 1500, 110, key3) 841 842 blob1, _ = rlp.EncodeToBytes(tx1) 843 blob2, _ = rlp.EncodeToBytes(tx2) 844 blob3, _ = rlp.EncodeToBytes(tx3) 845 846 keep = []common.Address{addr1, addr3} 847 drop = []common.Address{addr2} 848 size = uint64(2 * (txAvgSize + blobSize)) 849 ) 850 store.Put(blob1) 851 store.Put(blob2) 852 store.Put(blob3) 853 store.Close() 854 855 // Verify pool capping twice: first by reducing the data cap, then restarting 856 // with a high cap to ensure everything was persisted previously 857 for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} { 858 // Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction 859 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) 860 statedb.AddBalance(addr1, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 861 statedb.AddBalance(addr2, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 862 statedb.AddBalance(addr3, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 863 statedb.Commit(0, true) 864 865 chain := &testBlockChain{ 866 config: testChainConfig, 867 basefee: uint256.NewInt(1050), 868 blobfee: uint256.NewInt(105), 869 statedb: statedb, 870 } 871 pool := New(Config{Datadir: storage, Datacap: datacap}, chain) 872 if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { 873 t.Fatalf("failed to create blob pool: %v", err) 874 } 875 // Verify that enough transactions have been dropped to get the pool's size 876 // under the requested limit 877 if len(pool.index) != len(keep) { 878 t.Errorf("tracked account count mismatch: have %d, want %d", len(pool.index), len(keep)) 879 } 880 for _, addr := range keep { 881 if _, ok := pool.index[addr]; !ok { 882 t.Errorf("expected account %v missing from pool", addr) 883 } 884 } 885 for _, addr := range drop { 886 if _, ok := pool.index[addr]; ok { 887 t.Errorf("unexpected account %v present in pool", addr) 888 } 889 } 890 if pool.stored != size { 891 t.Errorf("pool stored size mismatch: have %v, want %v", pool.stored, size) 892 } 893 // Verify all the calculated pool internals. Interestingly, this is **not** 894 // a duplication of the above checks, this actually validates the verifier 895 // using the above already hard coded checks. 896 // 897 // Do not remove this, nor alter the above to be generic. 898 verifyPoolInternals(t, pool) 899 900 pool.Close() 901 } 902 } 903 904 // Tests that adding transaction will correctly store it in the persistent store 905 // and update all the indices. 906 // 907 // Note, this tests mostly checks the pool transaction shuffling logic or things 908 // specific to the blob pool. It does not do an exhaustive transaction validity 909 // check. 910 func TestAdd(t *testing.T) { 911 log.SetDefault(log.NewLogger(log.NewTerminalHandlerWithLevel(os.Stderr, log.LevelTrace, true))) 912 913 // seed is a helper tumpe to seed an initial state db and pool 914 type seed struct { 915 balance uint64 916 nonce uint64 917 txs []*types.BlobTx 918 } 919 920 // addtx is a helper sender/tx tuple to represent a new tx addition 921 type addtx struct { 922 from string 923 tx *types.BlobTx 924 err error 925 } 926 927 tests := []struct { 928 seeds map[string]seed 929 adds []addtx 930 }{ 931 // Transactions from new accounts should be accepted if their initial 932 // nonce matches the expected one from the statedb. Higher or lower must 933 // be rejected. 934 { 935 seeds: map[string]seed{ 936 "alice": {balance: 21100 + blobSize}, 937 "bob": {balance: 21100 + blobSize, nonce: 1}, 938 "claire": {balance: 21100 + blobSize}, 939 "dave": {balance: 21100 + blobSize, nonce: 1}, 940 }, 941 adds: []addtx{ 942 { // New account, no previous txs: accept nonce 0 943 from: "alice", 944 tx: makeUnsignedTx(0, 1, 1, 1), 945 err: nil, 946 }, 947 { // Old account, 1 tx in chain, 0 pending: accept nonce 1 948 from: "bob", 949 tx: makeUnsignedTx(1, 1, 1, 1), 950 err: nil, 951 }, 952 { // New account, no previous txs: reject nonce 1 953 from: "claire", 954 tx: makeUnsignedTx(1, 1, 1, 1), 955 err: core.ErrNonceTooHigh, 956 }, 957 { // Old account, 1 tx in chain, 0 pending: reject nonce 0 958 from: "dave", 959 tx: makeUnsignedTx(0, 1, 1, 1), 960 err: core.ErrNonceTooLow, 961 }, 962 { // Old account, 1 tx in chain, 0 pending: reject nonce 2 963 from: "dave", 964 tx: makeUnsignedTx(2, 1, 1, 1), 965 err: core.ErrNonceTooHigh, 966 }, 967 }, 968 }, 969 // Transactions from already pooled accounts should only be accepted if 970 // the nonces are contiguous (ignore prices for now, will check later) 971 { 972 seeds: map[string]seed{ 973 "alice": { 974 balance: 1000000, 975 txs: []*types.BlobTx{ 976 makeUnsignedTx(0, 1, 1, 1), 977 }, 978 }, 979 "bob": { 980 balance: 1000000, 981 nonce: 1, 982 txs: []*types.BlobTx{ 983 makeUnsignedTx(1, 1, 1, 1), 984 }, 985 }, 986 }, 987 adds: []addtx{ 988 { // New account, 1 tx pending: reject duplicate nonce 0 989 from: "alice", 990 tx: makeUnsignedTx(0, 1, 1, 1), 991 err: txpool.ErrAlreadyKnown, 992 }, 993 { // New account, 1 tx pending: reject replacement nonce 0 (ignore price for now) 994 from: "alice", 995 tx: makeUnsignedTx(0, 1, 1, 2), 996 err: txpool.ErrReplaceUnderpriced, 997 }, 998 { // New account, 1 tx pending: accept nonce 1 999 from: "alice", 1000 tx: makeUnsignedTx(1, 1, 1, 1), 1001 err: nil, 1002 }, 1003 { // New account, 2 txs pending: reject nonce 3 1004 from: "alice", 1005 tx: makeUnsignedTx(3, 1, 1, 1), 1006 err: core.ErrNonceTooHigh, 1007 }, 1008 { // New account, 2 txs pending: accept nonce 2 1009 from: "alice", 1010 tx: makeUnsignedTx(2, 1, 1, 1), 1011 err: nil, 1012 }, 1013 { // New account, 3 txs pending: accept nonce 3 now 1014 from: "alice", 1015 tx: makeUnsignedTx(3, 1, 1, 1), 1016 err: nil, 1017 }, 1018 { // Old account, 1 tx in chain, 1 tx pending: reject duplicate nonce 1 1019 from: "bob", 1020 tx: makeUnsignedTx(1, 1, 1, 1), 1021 err: txpool.ErrAlreadyKnown, 1022 }, 1023 { // Old account, 1 tx in chain, 1 tx pending: accept nonce 2 (ignore price for now) 1024 from: "bob", 1025 tx: makeUnsignedTx(2, 1, 1, 1), 1026 err: nil, 1027 }, 1028 }, 1029 }, 1030 // Transactions should only be accepted into the pool if the cumulative 1031 // expenditure doesn't overflow the account balance 1032 { 1033 seeds: map[string]seed{ 1034 "alice": {balance: 63299 + 3*blobSize}, // 3 tx - 1 wei 1035 }, 1036 adds: []addtx{ 1037 { // New account, no previous txs: accept nonce 0 with 21100 wei spend 1038 from: "alice", 1039 tx: makeUnsignedTx(0, 1, 1, 1), 1040 err: nil, 1041 }, 1042 { // New account, 1 pooled tx with 21100 wei spent: accept nonce 1 with 21100 wei spend 1043 from: "alice", 1044 tx: makeUnsignedTx(1, 1, 1, 1), 1045 err: nil, 1046 }, 1047 { // New account, 2 pooled tx with 42200 wei spent: reject nonce 2 with 21100 wei spend (1 wei overflow) 1048 from: "alice", 1049 tx: makeUnsignedTx(2, 1, 1, 1), 1050 err: core.ErrInsufficientFunds, 1051 }, 1052 }, 1053 }, 1054 // Transactions should only be accepted into the pool if the total count 1055 // from the same account doesn't overflow the pool limits 1056 { 1057 seeds: map[string]seed{ 1058 "alice": {balance: 10000000}, 1059 }, 1060 adds: []addtx{ 1061 { // New account, no previous txs, 16 slots left: accept nonce 0 1062 from: "alice", 1063 tx: makeUnsignedTx(0, 1, 1, 1), 1064 err: nil, 1065 }, 1066 { // New account, 1 pooled tx, 15 slots left: accept nonce 1 1067 from: "alice", 1068 tx: makeUnsignedTx(1, 1, 1, 1), 1069 err: nil, 1070 }, 1071 { // New account, 2 pooled tx, 14 slots left: accept nonce 2 1072 from: "alice", 1073 tx: makeUnsignedTx(2, 1, 1, 1), 1074 err: nil, 1075 }, 1076 { // New account, 3 pooled tx, 13 slots left: accept nonce 3 1077 from: "alice", 1078 tx: makeUnsignedTx(3, 1, 1, 1), 1079 err: nil, 1080 }, 1081 { // New account, 4 pooled tx, 12 slots left: accept nonce 4 1082 from: "alice", 1083 tx: makeUnsignedTx(4, 1, 1, 1), 1084 err: nil, 1085 }, 1086 { // New account, 5 pooled tx, 11 slots left: accept nonce 5 1087 from: "alice", 1088 tx: makeUnsignedTx(5, 1, 1, 1), 1089 err: nil, 1090 }, 1091 { // New account, 6 pooled tx, 10 slots left: accept nonce 6 1092 from: "alice", 1093 tx: makeUnsignedTx(6, 1, 1, 1), 1094 err: nil, 1095 }, 1096 { // New account, 7 pooled tx, 9 slots left: accept nonce 7 1097 from: "alice", 1098 tx: makeUnsignedTx(7, 1, 1, 1), 1099 err: nil, 1100 }, 1101 { // New account, 8 pooled tx, 8 slots left: accept nonce 8 1102 from: "alice", 1103 tx: makeUnsignedTx(8, 1, 1, 1), 1104 err: nil, 1105 }, 1106 { // New account, 9 pooled tx, 7 slots left: accept nonce 9 1107 from: "alice", 1108 tx: makeUnsignedTx(9, 1, 1, 1), 1109 err: nil, 1110 }, 1111 { // New account, 10 pooled tx, 6 slots left: accept nonce 10 1112 from: "alice", 1113 tx: makeUnsignedTx(10, 1, 1, 1), 1114 err: nil, 1115 }, 1116 { // New account, 11 pooled tx, 5 slots left: accept nonce 11 1117 from: "alice", 1118 tx: makeUnsignedTx(11, 1, 1, 1), 1119 err: nil, 1120 }, 1121 { // New account, 12 pooled tx, 4 slots left: accept nonce 12 1122 from: "alice", 1123 tx: makeUnsignedTx(12, 1, 1, 1), 1124 err: nil, 1125 }, 1126 { // New account, 13 pooled tx, 3 slots left: accept nonce 13 1127 from: "alice", 1128 tx: makeUnsignedTx(13, 1, 1, 1), 1129 err: nil, 1130 }, 1131 { // New account, 14 pooled tx, 2 slots left: accept nonce 14 1132 from: "alice", 1133 tx: makeUnsignedTx(14, 1, 1, 1), 1134 err: nil, 1135 }, 1136 { // New account, 15 pooled tx, 1 slots left: accept nonce 15 1137 from: "alice", 1138 tx: makeUnsignedTx(15, 1, 1, 1), 1139 err: nil, 1140 }, 1141 { // New account, 16 pooled tx, 0 slots left: accept nonce 15 replacement 1142 from: "alice", 1143 tx: makeUnsignedTx(15, 10, 10, 10), 1144 err: nil, 1145 }, 1146 { // New account, 16 pooled tx, 0 slots left: reject nonce 16 with overcap 1147 from: "alice", 1148 tx: makeUnsignedTx(16, 1, 1, 1), 1149 err: txpool.ErrAccountLimitExceeded, 1150 }, 1151 }, 1152 }, 1153 // Previously existing transactions should be allowed to be replaced iff 1154 // the new cumulative expenditure can be covered by the account and the 1155 // prices are bumped all around (no percentage check here). 1156 { 1157 seeds: map[string]seed{ 1158 "alice": {balance: 2*100 + 5*21000 + 3*blobSize}, 1159 }, 1160 adds: []addtx{ 1161 { // New account, no previous txs: reject nonce 0 with 341172 wei spend 1162 from: "alice", 1163 tx: makeUnsignedTx(0, 1, 20, 1), 1164 err: core.ErrInsufficientFunds, 1165 }, 1166 { // New account, no previous txs: accept nonce 0 with 173172 wei spend 1167 from: "alice", 1168 tx: makeUnsignedTx(0, 1, 2, 1), 1169 err: nil, 1170 }, 1171 { // New account, 1 pooled tx with 173172 wei spent: accept nonce 1 with 152172 wei spend 1172 from: "alice", 1173 tx: makeUnsignedTx(1, 1, 1, 1), 1174 err: nil, 1175 }, 1176 { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with 599684 wei spend (173072 extra) (would overflow balance at nonce 1) 1177 from: "alice", 1178 tx: makeUnsignedTx(0, 2, 5, 2), 1179 err: core.ErrInsufficientFunds, 1180 }, 1181 { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-gastip-bump 1182 from: "alice", 1183 tx: makeUnsignedTx(0, 1, 3, 2), 1184 err: txpool.ErrReplaceUnderpriced, 1185 }, 1186 { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-gascap-bump 1187 from: "alice", 1188 tx: makeUnsignedTx(0, 2, 2, 2), 1189 err: txpool.ErrReplaceUnderpriced, 1190 }, 1191 { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-blobcap-bump 1192 from: "alice", 1193 tx: makeUnsignedTx(0, 2, 4, 1), 1194 err: txpool.ErrReplaceUnderpriced, 1195 }, 1196 { // New account, 2 pooled tx with 325344 wei spent: accept nonce 0 with 84100 wei spend (42000 extra) 1197 from: "alice", 1198 tx: makeUnsignedTx(0, 2, 4, 2), 1199 err: nil, 1200 }, 1201 }, 1202 }, 1203 // Previously existing transactions should be allowed to be replaced iff 1204 // the new prices are bumped by a sufficient amount. 1205 { 1206 seeds: map[string]seed{ 1207 "alice": {balance: 100 + 8*21000 + 4*blobSize}, 1208 }, 1209 adds: []addtx{ 1210 { // New account, no previous txs: accept nonce 0 1211 from: "alice", 1212 tx: makeUnsignedTx(0, 2, 4, 2), 1213 err: nil, 1214 }, 1215 { // New account, 1 pooled tx: reject nonce 0 with low-gastip-bump 1216 from: "alice", 1217 tx: makeUnsignedTx(0, 3, 8, 4), 1218 err: txpool.ErrReplaceUnderpriced, 1219 }, 1220 { // New account, 1 pooled tx: reject nonce 0 with low-gascap-bump 1221 from: "alice", 1222 tx: makeUnsignedTx(0, 4, 6, 4), 1223 err: txpool.ErrReplaceUnderpriced, 1224 }, 1225 { // New account, 1 pooled tx: reject nonce 0 with low-blobcap-bump 1226 from: "alice", 1227 tx: makeUnsignedTx(0, 4, 8, 3), 1228 err: txpool.ErrReplaceUnderpriced, 1229 }, 1230 { // New account, 1 pooled tx: accept nonce 0 with all-bumps 1231 from: "alice", 1232 tx: makeUnsignedTx(0, 4, 8, 4), 1233 err: nil, 1234 }, 1235 }, 1236 }, 1237 // Blob transactions that don't meet the min blob gas price should be rejected 1238 { 1239 seeds: map[string]seed{ 1240 "alice": {balance: 10000000}, 1241 }, 1242 adds: []addtx{ 1243 { // New account, no previous txs, nonce 0, but blob fee cap too low 1244 from: "alice", 1245 tx: makeUnsignedTx(0, 1, 1, 0), 1246 err: txpool.ErrUnderpriced, 1247 }, 1248 { // Same as above but blob fee cap equals minimum, should be accepted 1249 from: "alice", 1250 tx: makeUnsignedTx(0, 1, 1, params.BlobTxMinBlobGasprice), 1251 err: nil, 1252 }, 1253 }, 1254 }, 1255 } 1256 for i, tt := range tests { 1257 // Create a temporary folder for the persistent backend 1258 storage, _ := os.MkdirTemp("", "blobpool-") 1259 defer os.RemoveAll(storage) // late defer, still ok 1260 1261 os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700) 1262 store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil) 1263 1264 // Insert the seed transactions for the pool startup 1265 var ( 1266 keys = make(map[string]*ecdsa.PrivateKey) 1267 addrs = make(map[string]common.Address) 1268 ) 1269 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) 1270 for acc, seed := range tt.seeds { 1271 // Generate a new random key/address for the seed account 1272 keys[acc], _ = crypto.GenerateKey() 1273 addrs[acc] = crypto.PubkeyToAddress(keys[acc].PublicKey) 1274 1275 // Seed the state database with this account 1276 statedb.AddBalance(addrs[acc], new(uint256.Int).SetUint64(seed.balance), tracing.BalanceChangeUnspecified) 1277 statedb.SetNonce(addrs[acc], seed.nonce) 1278 1279 // Sign the seed transactions and store them in the data store 1280 for _, tx := range seed.txs { 1281 signed := types.MustSignNewTx(keys[acc], types.LatestSigner(testChainConfig), tx) 1282 blob, _ := rlp.EncodeToBytes(signed) 1283 store.Put(blob) 1284 } 1285 } 1286 statedb.Commit(0, true) 1287 store.Close() 1288 1289 // Create a blob pool out of the pre-seeded dats 1290 chain := &testBlockChain{ 1291 config: testChainConfig, 1292 basefee: uint256.NewInt(1050), 1293 blobfee: uint256.NewInt(105), 1294 statedb: statedb, 1295 } 1296 pool := New(Config{Datadir: storage}, chain) 1297 if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { 1298 t.Fatalf("test %d: failed to create blob pool: %v", i, err) 1299 } 1300 verifyPoolInternals(t, pool) 1301 1302 // Add each transaction one by one, verifying the pool internals in between 1303 for j, add := range tt.adds { 1304 signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(testChainConfig), add.tx) 1305 if err := pool.add(signed); !errors.Is(err, add.err) { 1306 t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err) 1307 } 1308 verifyPoolInternals(t, pool) 1309 } 1310 // Verify the pool internals and close down the test 1311 verifyPoolInternals(t, pool) 1312 pool.Close() 1313 } 1314 } 1315 1316 // Benchmarks the time it takes to assemble the lazy pending transaction list 1317 // from the pool contents. 1318 func BenchmarkPoolPending100Mb(b *testing.B) { benchmarkPoolPending(b, 100_000_000) } 1319 func BenchmarkPoolPending1GB(b *testing.B) { benchmarkPoolPending(b, 1_000_000_000) } 1320 func BenchmarkPoolPending10GB(b *testing.B) { benchmarkPoolPending(b, 10_000_000_000) } 1321 1322 func benchmarkPoolPending(b *testing.B, datacap uint64) { 1323 // Calculate the maximum number of transaction that would fit into the pool 1324 // and generate a set of random accounts to seed them with. 1325 capacity := datacap / params.BlobTxBlobGasPerBlob 1326 1327 var ( 1328 basefee = uint64(1050) 1329 blobfee = uint64(105) 1330 signer = types.LatestSigner(testChainConfig) 1331 statedb, _ = state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil) 1332 chain = &testBlockChain{ 1333 config: testChainConfig, 1334 basefee: uint256.NewInt(basefee), 1335 blobfee: uint256.NewInt(blobfee), 1336 statedb: statedb, 1337 } 1338 pool = New(Config{Datadir: ""}, chain) 1339 ) 1340 1341 if err := pool.Init(1, chain.CurrentBlock(), makeAddressReserver()); err != nil { 1342 b.Fatalf("failed to create blob pool: %v", err) 1343 } 1344 // Fill the pool up with one random transaction from each account with the 1345 // same price and everything to maximize the worst case scenario 1346 for i := 0; i < int(capacity); i++ { 1347 blobtx := makeUnsignedTx(0, 10, basefee+10, blobfee) 1348 blobtx.R = uint256.NewInt(1) 1349 blobtx.S = uint256.NewInt(uint64(100 + i)) 1350 blobtx.V = uint256.NewInt(0) 1351 tx := types.NewTx(blobtx) 1352 addr, err := types.Sender(signer, tx) 1353 if err != nil { 1354 b.Fatal(err) 1355 } 1356 statedb.AddBalance(addr, uint256.NewInt(1_000_000_000), tracing.BalanceChangeUnspecified) 1357 pool.add(tx) 1358 } 1359 statedb.Commit(0, true) 1360 defer pool.Close() 1361 1362 // Benchmark assembling the pending 1363 b.ResetTimer() 1364 b.ReportAllocs() 1365 1366 for i := 0; i < b.N; i++ { 1367 p := pool.Pending(txpool.PendingFilter{ 1368 MinTip: uint256.NewInt(1), 1369 BaseFee: chain.basefee, 1370 BlobFee: chain.blobfee, 1371 }) 1372 if len(p) != int(capacity) { 1373 b.Fatalf("have %d want %d", len(p), capacity) 1374 } 1375 } 1376 }