github.com/ethereum/go-ethereum@v1.16.1/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 17 package legacypool 18 19 import ( 20 "crypto/ecdsa" 21 "math/big" 22 "testing" 23 24 "github.com/ethereum/go-ethereum/common" 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.NewDatabaseForTesting()) 84 blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) 85 86 config := testTxPoolConfig 87 config.GlobalQueue = 100 88 config.GlobalSlots = 100 89 pool := New(config, blockchain) 90 pool.Init(config.PriceLimit, blockchain.CurrentBlock(), newReserver()) 91 defer pool.Close() 92 93 fillPool(t, pool) 94 pending, _ := pool.Stats() 95 // Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops 96 { 97 key, _ := crypto.GenerateKey() 98 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) 99 futureTxs := types.Transactions{} 100 for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { 101 futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 100000, big.NewInt(500), key)) 102 } 103 for i := 0; i < 5; i++ { 104 pool.addRemotesSync(futureTxs) 105 newPending, newQueued := count(t, pool) 106 t.Logf("pending: %d queued: %d, all: %d\n", newPending, newQueued, pool.all.Slots()) 107 } 108 } 109 newPending, _ := pool.Stats() 110 // Pending should not have been touched 111 if have, want := newPending, pending; have < want { 112 t.Errorf("wrong pending-count, have %d, want %d (GlobalSlots: %d)", 113 have, want, pool.config.GlobalSlots) 114 } 115 } 116 117 // Tests that if a batch high-priced of non-executables arrive, they do not kick out 118 // executable transactions 119 func TestTransactionFuture1559(t *testing.T) { 120 t.Parallel() 121 // Create the pool to test the pricing enforcement with 122 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 123 blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) 124 pool := New(testTxPoolConfig, blockchain) 125 pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), newReserver()) 126 defer pool.Close() 127 128 // Create a number of test accounts, fund them and make transactions 129 fillPool(t, pool) 130 pending, _ := pool.Stats() 131 132 // Now, future transaction attack starts, let's add a bunch of expensive non-executables, and see if the pending-count drops 133 { 134 key, _ := crypto.GenerateKey() 135 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) 136 futureTxs := types.Transactions{} 137 for j := 0; j < int(pool.config.GlobalSlots+pool.config.GlobalQueue); j++ { 138 futureTxs = append(futureTxs, dynamicFeeTx(1000+uint64(j), 100000, big.NewInt(200), big.NewInt(101), key)) 139 } 140 pool.addRemotesSync(futureTxs) 141 } 142 newPending, _ := pool.Stats() 143 // Pending should not have been touched 144 if have, want := newPending, pending; have != want { 145 t.Errorf("Wrong pending-count, have %d, want %d (GlobalSlots: %d)", 146 have, want, pool.config.GlobalSlots) 147 } 148 } 149 150 // Tests that if a batch of balance-overdraft txs arrive, they do not kick out 151 // executable transactions 152 func TestTransactionZAttack(t *testing.T) { 153 t.Parallel() 154 // Create the pool to test the pricing enforcement with 155 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 156 blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) 157 pool := New(testTxPoolConfig, blockchain) 158 pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), newReserver()) 159 defer pool.Close() 160 // Create a number of test accounts, fund them and make transactions 161 fillPool(t, pool) 162 163 countInvalidPending := func() int { 164 t.Helper() 165 var ivpendingNum int 166 pendingtxs, _ := pool.Content() 167 for account, txs := range pendingtxs { 168 curBalance := new(big.Int).Set(pool.currentState.GetBalance(account).ToBig()) 169 for _, tx := range txs { 170 if curBalance.Cmp(tx.Value()) <= 0 { 171 ivpendingNum++ 172 } else { 173 curBalance.Sub(curBalance, tx.Value()) 174 } 175 } 176 } 177 if err := validatePoolInternals(pool); err != nil { 178 t.Fatalf("pool internal state corrupted: %v", err) 179 } 180 return ivpendingNum 181 } 182 ivPending := countInvalidPending() 183 t.Logf("invalid pending: %d\n", ivPending) 184 185 // Now, DETER-Z attack starts, let's add a bunch of expensive non-executables 186 // (from N accounts) along with balance-overdraft txs (from one account), and 187 // see if the pending-count drops 188 for j := 0; j < int(pool.config.GlobalQueue); j++ { 189 futureTxs := types.Transactions{} 190 key, _ := crypto.GenerateKey() 191 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) 192 futureTxs = append(futureTxs, pricedTransaction(1000+uint64(j), 21000, big.NewInt(500), key)) 193 pool.addRemotesSync(futureTxs) 194 } 195 196 overDraftTxs := types.Transactions{} 197 { 198 key, _ := crypto.GenerateKey() 199 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) 200 for j := 0; j < int(pool.config.GlobalSlots); j++ { 201 overDraftTxs = append(overDraftTxs, pricedValuedTransaction(uint64(j), 600000000000, 21000, big.NewInt(500), key)) 202 } 203 } 204 pool.addRemotesSync(overDraftTxs) 205 pool.addRemotesSync(overDraftTxs) 206 pool.addRemotesSync(overDraftTxs) 207 pool.addRemotesSync(overDraftTxs) 208 pool.addRemotesSync(overDraftTxs) 209 210 newPending, newQueued := count(t, pool) 211 newIvPending := countInvalidPending() 212 t.Logf("pool.all.Slots(): %d\n", pool.all.Slots()) 213 t.Logf("pending: %d queued: %d, all: %d\n", newPending, newQueued, pool.all.Slots()) 214 t.Logf("invalid pending: %d\n", newIvPending) 215 216 // Pending should not have been touched 217 if newIvPending != ivPending { 218 t.Errorf("Wrong invalid pending-count, have %d, want %d (GlobalSlots: %d, queued: %d)", 219 newIvPending, ivPending, pool.config.GlobalSlots, newQueued) 220 } 221 } 222 223 func BenchmarkFutureAttack(b *testing.B) { 224 // Create the pool to test the limit enforcement with 225 statedb, _ := state.New(types.EmptyRootHash, state.NewDatabaseForTesting()) 226 blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed)) 227 config := testTxPoolConfig 228 config.GlobalQueue = 100 229 config.GlobalSlots = 100 230 pool := New(config, blockchain) 231 pool.Init(testTxPoolConfig.PriceLimit, blockchain.CurrentBlock(), newReserver()) 232 defer pool.Close() 233 fillPool(b, pool) 234 235 key, _ := crypto.GenerateKey() 236 pool.currentState.AddBalance(crypto.PubkeyToAddress(key.PublicKey), uint256.NewInt(100000000000), tracing.BalanceChangeUnspecified) 237 futureTxs := types.Transactions{} 238 239 for n := 0; n < b.N; n++ { 240 futureTxs = append(futureTxs, pricedTransaction(1000+uint64(n), 100000, big.NewInt(500), key)) 241 } 242 b.ResetTimer() 243 for i := 0; i < 5; i++ { 244 pool.addRemotesSync(futureTxs) 245 } 246 }