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  }