github.com/ethereum/go-ethereum@v1.14.4-0.20240516095835-473ee8fc07a3/core/txpool/legacypool/legacypool2_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 package legacypool 17 18 import ( 19 "crypto/ecdsa" 20 "math/big" 21 "testing" 22 23 "github.com/ethereum/go-ethereum/common" 24 "github.com/ethereum/go-ethereum/core/rawdb" 25 "github.com/ethereum/go-ethereum/core/state" 26 "github.com/ethereum/go-ethereum/core/tracing" 27 "github.com/ethereum/go-ethereum/core/types" 28 "github.com/ethereum/go-ethereum/crypto" 29 "github.com/ethereum/go-ethereum/event" 30 "github.com/holiman/uint256" 31 ) 32 33 func pricedValuedTransaction(nonce uint64, value int64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey) *types.Transaction { 34 tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(value), gaslimit, gasprice, nil), types.HomesteadSigner{}, key) 35 return tx 36 } 37 38 func count(t *testing.T, pool *LegacyPool) (pending int, queued int) { 39 t.Helper() 40 pending, queued = pool.stats() 41 if err := validatePoolInternals(pool); err != nil { 42 t.Fatalf("pool internal state corrupted: %v", err) 43 } 44 return pending, queued 45 } 46 47 func fillPool(t testing.TB, pool *LegacyPool) { 48 t.Helper() 49 // Create a number of test accounts, fund them and make transactions 50 executableTxs := types.Transactions{} 51 nonExecutableTxs := types.Transactions{} 52 for i := 0; i < 384; i++ { 53 key, _ := crypto.GenerateKey() 54 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(10000000000), tracing.BalanceChangeUnspecified) 55 // Add executable ones 56 for j := 0; j < int(pool.config.AccountSlots); j++ { 57 executableTxs = append(executableTxs, pricedTransaction(uint64(j), 100000, big.NewInt(300), key)) 58 } 59 } 60 // Import the batch and verify that limits have been enforced 61 pool.addRemotesSync(executableTxs) 62 pool.addRemotesSync(nonExecutableTxs) 63 pending, queued := pool.Stats() 64 slots := pool.all.Slots() 65 // sanity-check that the test prerequisites are ok (pending full) 66 if have, want := pending, slots; have != want { 67 t.Fatalf("have %d, want %d", have, want) 68 } 69 if have, want := queued, 0; have != want { 70 t.Fatalf("have %d, want %d", have, want) 71 } 72 73 t.Logf("pool.config: GlobalSlots=%d, GlobalQueue=%d\n", pool.config.GlobalSlots, pool.config.GlobalQueue) 74 t.Logf("pending: %d queued: %d, all: %d\n", pending, queued, slots) 75 } 76 77 // Tests that if a batch high-priced of non-executables arrive, they do not kick out 78 // executable transactions 79 func TestTransactionFutureAttack(t *testing.T) { 80 t.Parallel() 81 82 // Create the pool to test the limit enforcement with 83 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 84 blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) 85 config := testTxPoolConfig 86 config.GlobalQueue = 100 87 config.GlobalSlots = 100 88 pool := New(config, blockchain) 89 pool.Init(config.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) 90 defer pool.Close() 91 fillPool(t, pool) 92 pending, _ := pool.Stats() 93 // Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops 94 { 95 key, _ := crypto.GenerateKey() 96 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) 97 futureTxs := types.Transactions{} 98 for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { 99 futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 100000, big.NewInt(500), key)) 100 } 101 for i := 0; i < 5; i++ { 102 pool.addRemotesSync(futureTxs) 103 newPending, newQueued := count(t, pool) 104 t.Logf("pending: %d queued: %d, all: %d\n", newPending, newQueued, pool.all.Slots()) 105 } 106 } 107 newPending, _ := pool.Stats() 108 // Pending should not have been touched 109 if have, want := newPending, pending; have < want { 110 t.Errorf("wrong pending-count, have %d, want %d (GlobalSlots: %d)", 111 have, want, pool.config.GlobalSlots) 112 } 113 } 114 115 // Tests that if a batch high-priced of non-executables arrive, they do not kick out 116 // executable transactions 117 func TestTransactionFuture1559(t *testing.T) { 118 t.Parallel() 119 // Create the pool to test the pricing enforcement with 120 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 121 blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) 122 pool := New(testTxPoolConfig, blockchain) 123 pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) 124 defer pool.Close() 125 126 // Create a number of test accounts, fund them and make transactions 127 fillPool(t, pool) 128 pending, _ := pool.Stats() 129 130 // Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops 131 { 132 key, _ := crypto.GenerateKey() 133 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) 134 futureTxs := types.Transactions{} 135 for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { 136 futureTxs = append(futureTxs, dynamicFeeTx(1000+uint64(j), 100000, big.NewInt(200), big.NewInt(101), key)) 137 } 138 pool.addRemotesSync(futureTxs) 139 } 140 newPending, _ := pool.Stats() 141 // Pending should not have been touched 142 if have, want := newPending, pending; have != want { 143 t.Errorf("Wrong pending-count, have %d, want %d (GlobalSlots: %d)", 144 have, want, pool.config.GlobalSlots) 145 } 146 } 147 148 // Tests that if a batch of balance-overdraft txs arrive, they do not kick out 149 // executable transactions 150 func TestTransactionZAttack(t *testing.T) { 151 t.Parallel() 152 // Create the pool to test the pricing enforcement with 153 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 154 blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) 155 pool := New(testTxPoolConfig, blockchain) 156 pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) 157 defer pool.Close() 158 // Create a number of test accounts, fund them and make transactions 159 fillPool(t, pool) 160 161 countInvalidPending := func() int { 162 t.Helper() 163 var ivpendingNum int 164 pendingtxs, _ := pool.Content() 165 for account, txs := range pendingtxs { 166 cur_balance := new(big.Int).Set(pool.currentState.GetBalance(account).ToBig()) 167 for _, tx := range txs { 168 if cur_balance.Cmp(tx.Value()) <= 0 { 169 ivpendingNum++ 170 } else { 171 cur_balance.Sub(cur_balance, tx.Value()) 172 } 173 } 174 } 175 if err := validatePoolInternals(pool); err != nil { 176 t.Fatalf("pool internal state corrupted: %v", err) 177 } 178 return ivpendingNum 179 } 180 ivPending := countInvalidPending() 181 t.Logf("invalid pending: %d\n", ivPending) 182 183 // Now, DETER-Z attack starts, let's add a bunch of expensive non-executables (from N accounts) along with balance-overdraft txs (from one account), and see if the pending-count drops 184 for j := 0; j < int(pool.config.GlobalQueue); j++ { 185 futureTxs := types.Transactions{} 186 key, _ := crypto.GenerateKey() 187 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) 188 futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 21000, big.NewInt(500), key)) 189 pool.addRemotesSync(futureTxs) 190 } 191 192 overDraftTxs := types.Transactions{} 193 { 194 key, _ := crypto.GenerateKey() 195 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) 196 for j := 0; j < int(pool.config.GlobalSlots); j++ { 197 overDraftTxs = append(overDraftTxs, pricedValuedTransaction(uint64(j), 600000000000, 21000, big.NewInt(500), key)) 198 } 199 } 200 pool.addRemotesSync(overDraftTxs) 201 pool.addRemotesSync(overDraftTxs) 202 pool.addRemotesSync(overDraftTxs) 203 pool.addRemotesSync(overDraftTxs) 204 pool.addRemotesSync(overDraftTxs) 205 206 newPending, newQueued := count(t, pool) 207 newIvPending := countInvalidPending() 208 t.Logf("pool.all.Slots(): %d\n", pool.all.Slots()) 209 t.Logf("pending: %d queued: %d, all: %d\n", newPending, newQueued, pool.all.Slots()) 210 t.Logf("invalid pending: %d\n", newIvPending) 211 212 // Pending should not have been touched 213 if newIvPending != ivPending { 214 t.Errorf("Wrong invalid pending-count, have %d, want %d (GlobalSlots: %d, queued: %d)", 215 newIvPending, ivPending, pool.config.GlobalSlots, newQueued) 216 } 217 } 218 219 func BenchmarkFutureAttack(b *testing.B) { 220 // Create the pool to test the limit enforcement with 221 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil) 222 blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) 223 config := testTxPoolConfig 224 config.GlobalQueue = 100 225 config.GlobalSlots = 100 226 pool := New(config, blockchain) 227 pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), makeAddressReserver()) 228 defer pool.Close() 229 fillPool(b, pool) 230 231 key, _ := crypto.GenerateKey() 232 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) 233 futureTxs := types.Transactions{} 234 235 for n := 0; n < b.N; n++ { 236 futureTxs = append(futureTxs, pricedTransaction(1000+uint64(n), 100000, big.NewInt(500), key)) 237 } 238 b.ResetTimer() 239 for i := 0; i < 5; i++ { 240 pool.addRemotesSync(futureTxs) 241 } 242 }