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  }