diff --git a/core/tx_list.go b/core/tx_list.go index 75bfdaedac..c26d4881bc 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -494,11 +494,11 @@ func (l *txPricedList) Underpriced(tx *types.Transaction, local *accountSet) boo // Discard finds a number of most underpriced transactions, removes them from the // priced list and returns them for further removal from the entire pool. -func (l *txPricedList) Discard(count int, local *accountSet) types.Transactions { - drop := make(types.Transactions, 0, count) // Remote underpriced transactions to drop - save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep +func (l *txPricedList) Discard(numSlots int, local *accountSet) types.Transactions { + drop := make(types.Transactions, 0, numSlots) // Remote underpriced transactions to drop + save := make(types.Transactions, 0, 64) // Local underpriced transactions to keep - for len(*l.items) > 0 && count > 0 { + for len(*l.items) > 0 && numSlots > 0 { // Discard stale transactions if found during cleanup tx := heap.Pop(l.items).(*types.Transaction) if l.all.Get(tx.Hash()) == nil { @@ -510,7 +510,7 @@ func (l *txPricedList) Discard(count int, local *accountSet) types.Transactions save = append(save, tx) } else { drop = append(drop, tx) - count-- + numSlots -= NumSlots(tx) } } for _, tx := range save { diff --git a/core/tx_pool.go b/core/tx_pool.go index 6cfd5513d9..d148559984 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -584,7 +584,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e return false, ErrUnderpriced } // New transaction is better than our worse ones, make room for it - drop := pool.priced.Discard(pool.all.Count()-int(pool.config.GlobalSlots+pool.config.GlobalQueue-1), pool.locals) + drop := pool.priced.Discard(pool.all.SlotsUsed()-int(pool.config.GlobalSlots+pool.config.GlobalQueue)+NumSlots(tx), pool.locals) for _, tx := range drop { log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice()) underpricedTxMeter.Mark(1) @@ -1143,8 +1143,8 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { pool.pendingNonces = newTxNoncer(statedb) pool.currentMaxGas = newHead.GasLimit - // Heuristic limit, reject transactions over 32KB to prevent DOS attacks - pool.maxTxSize = 32 * 1024 + // Heuristic limit, reject transactions over 4 slots to prevent DOS attacks + pool.maxTxSize = 4 * slotSize // Inject any transactions discarded due to reorgs log.Debug("Reinjecting stale transactions", "count", len(reinject)) @@ -1497,14 +1497,16 @@ func (as *accountSet) merge(other *accountSet) { // peeking into the pool in TxPool.Get without having to acquire the widely scoped // TxPool.mu mutex. type txLookup struct { - all map[common.Hash]*types.Transaction - lock sync.RWMutex + all map[common.Hash]*types.Transaction + numUsedSlots int + lock sync.RWMutex } // newTxLookup returns a new txLookup structure. func newTxLookup() *txLookup { return &txLookup{ - all: make(map[common.Hash]*types.Transaction), + all: make(map[common.Hash]*types.Transaction), + numUsedSlots: 0, } } @@ -1536,11 +1538,32 @@ func (t *txLookup) Count() int { return len(t.all) } +// SlotsUsed returns the current number of slots used in the lookup. +func (t *txLookup) SlotsUsed() int { + t.lock.RLock() + defer t.lock.RUnlock() + + return t.numUsedSlots +} + +const ( + // Transactions are kept in slots. + // Each slot is defined to be 32KB. + // This slots mechanism provides protection against + // resources over consumption. + slotSize = 32 * 1024 +) + +func NumSlots(tx *types.Transaction) int { + return int(math.Ceil(float64(tx.Size()) / slotSize)) +} + // Add adds a transaction to the lookup. func (t *txLookup) Add(tx *types.Transaction) { t.lock.Lock() defer t.lock.Unlock() + t.numUsedSlots += NumSlots(tx) t.all[tx.Hash()] = tx } @@ -1549,5 +1572,6 @@ func (t *txLookup) Remove(hash common.Hash) { t.lock.Lock() defer t.lock.Unlock() + t.numUsedSlots -= NumSlots(t.all[hash]) delete(t.all, hash) } diff --git a/core/tx_pool_test.go b/core/tx_pool_test.go index 9981f4ced3..f57e9e82a1 100644 --- a/core/tx_pool_test.go +++ b/core/tx_pool_test.go @@ -1808,6 +1808,25 @@ func TestTransactionStatusCheck(t *testing.T) { } } +// Test the transaction slots consumption is computed correctly +func TestNumSlots(t *testing.T) { + t.Parallel() + + key, _ := crypto.GenerateKey() + + tinyTx := pricedDataTransaction(0, 0, big.NewInt(0), key, 0) + if NumSlots(tinyTx) != 1 { + t.Fatalf("Small transactions are expected to consume a single slot.") + } + + numAdditionalSlots := rand.Intn(10) + dataLen := uint64(slotSize * numAdditionalSlots) + severalSlotsTx := pricedDataTransaction(0, 0, big.NewInt(0), key, dataLen) + if actual := NumSlots(severalSlotsTx); actual != 1+numAdditionalSlots { + t.Fatalf("Unexpected slots consumptions: expected %d, actual %d", 1+numAdditionalSlots, actual) + } +} + // Benchmarks the speed of validating the contents of the pending queue of the // transaction pool. func BenchmarkPendingDemotion100(b *testing.B) { benchmarkPendingDemotion(b, 100) }