diff --git a/core/tx_pool.go b/core/tx_pool.go index f7032dbd1e..81f8099ac1 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -223,6 +223,7 @@ type TxPool struct { currentState *state.StateDB // Current state in the blockchain head pendingNonces *txNoncer // Pending state tracking virtual nonces currentMaxGas uint64 // Current gas limit for transaction caps + maxTxSize uint64 // Maximal size of allowed transaction in bytes locals *accountSet // Set of local transaction to exempt from eviction rules journal *txJournal // Journal of local transaction to back up to disk @@ -270,6 +271,9 @@ func NewTxPool(config TxPoolConfig, chainconfig *params.ChainConfig, chain block reorgDoneCh: make(chan chan struct{}), reorgShutdownCh: make(chan struct{}), gasPrice: new(big.Int).SetUint64(config.PriceLimit), + + // Heuristic limit, reject transactions over 32KB to prevent DOS attacks + maxTxSize: 32 * 1024, } pool.locals = newAccountSet(pool.signer) for _, addr := range config.Locals { @@ -510,8 +514,8 @@ func (pool *TxPool) local() map[common.Address]types.Transactions { // validateTx checks whether a transaction is valid according to the consensus // rules and adheres to some heuristic limits of the local node (price and size). func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { - // Heuristic limit, reject transactions over 32KB to prevent DOS attacks - if tx.Size() > 32*1024 { + // Reject transactions over defined size to prevent DOS attacks + if uint64(tx.Size()) > pool.maxTxSize { return ErrOversizedData } // Transactions can't be negative. This may never happen using RLP decoded diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 0f1e7ac8f8..9981f4ced3 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -77,6 +77,13 @@ func pricedTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ec return tx } +func pricedDataTransaction(nonce uint64, gaslimit uint64, gasprice *big.Int, key *ecdsa.PrivateKey, numDataBytes uint64) *types.Transaction { + data := make([]byte, numDataBytes) + rand.Read(data) + tx, _ := types.SignTx(types.NewTransaction(nonce, common.Address{}, big.NewInt(0), gaslimit, gasprice, data), types.HomesteadSigner{}, key) + return tx +} + func setupTxPool() (*TxPool, *ecdsa.PrivateKey) { statedb, _ := state.New(common.Hash{}, state.NewDatabase(rawdb.NewMemoryDatabase())) blockchain := &testBlockChain{statedb, 1000000, new(event.Feed)} @@ -754,6 +761,55 @@ func TestTransactionQueueAccountLimiting(t *testing.T) { } } +// Test the limit on transaction size is enforced correctly. +// This test verifies every transaction having allowed size +// is added to the pool, and longer transactions are rejected. +func TestAllowedTxSize(t *testing.T) { + t.Parallel() + + // Create a test account and fund it + pool, key := setupTxPool() + defer pool.Stop() + + account, _ := deriveSender(transaction(0, 0, key)) + pool.currentState.AddBalance(account, big.NewInt(1024*int64(pool.currentMaxGas))) + + // Compute maximal data size for transactions (lower bound). + // It is assumed the fields in the transaction (except of the data) are: + // nonce : at most 32 bytes + // gasPrice : at most 32 bytes + // gasLimit : at most 32 bytes + // To address : 20 bytes + // value : at most 32 bytes + // signature : 65 bytes + // All those fields are summed up to at most 213 bytes. + txSizeWithoutData := uint64(213) + maxDataSize := pool.maxTxSize - txSizeWithoutData + + // Increase block gas limit to infinity + pool.currentMaxGas = 2 << 60 + + // Try adding a transaction with maximal allowed size + if err := pool.validateTx(pricedDataTransaction(0, pool.currentMaxGas, big.NewInt(0), key, maxDataSize), true); err != nil { + t.Fatalf("failed to add transaction of size close to maximal: %d, %v", int(pricedDataTransaction(0, pool.currentMaxGas, big.NewInt(1), key, maxDataSize).Size()), err) + } + + // Try adding a transaction with random allowed size + if err := pool.validateTx(pricedDataTransaction(0, pool.currentMaxGas, big.NewInt(0), key, uint64(rand.Intn(int(maxDataSize)))), true); err != nil { + t.Fatalf("failed to add transaction of random allowed size: %v", err) + } + + // Try adding a transaction of minimal not allowed size + if pool.validateTx(pricedDataTransaction(0, pool.currentMaxGas, big.NewInt(0), key, pool.maxTxSize), true) == nil { + t.Fatalf("expected rejection on slightly oversize transaction") + } + + // Try adding a transaction of random not allowed size + if pool.validateTx(pricedDataTransaction(0, pool.currentMaxGas, big.NewInt(1), key, maxDataSize+1+uint64(rand.Intn(int(10*pool.maxTxSize)))), true) == nil { + t.Fatalf("expected rejection on oversize transaction") + } +} + // Tests that if the transaction count belonging to multiple accounts go above // some threshold, the higher transactions are dropped to prevent DOS attacks. //