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