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