Unverified Commit 966ee3ae authored by Felföldi Zsolt's avatar Felföldi Zsolt Committed by GitHub

all: EIP-1559 tx pool support (#22898)

This pull request implements EIP-1559 compatible transaction pool with dual heap eviction ordering.
It is based on #22791
The eviction ordering scheme and the reasoning behind it is described here: https://gist.github.com/zsfelfoldi/9607ad248707a925b701f49787904fd6
parent ee35ddc8
This diff is collapsed.
...@@ -83,6 +83,10 @@ var ( ...@@ -83,6 +83,10 @@ var (
// than some meaningful limit a user might use. This is not a consensus error // than some meaningful limit a user might use. This is not a consensus error
// making the transaction invalid, rather a DOS protection. // making the transaction invalid, rather a DOS protection.
ErrOversizedData = errors.New("oversized data") ErrOversizedData = errors.New("oversized data")
// ErrTipAboveFeeCap is a sanity error to ensure no one is able to specify a
// transaction with a tip higher than the total fee cap.
ErrTipAboveFeeCap = errors.New("tip higher than fee cap")
) )
var ( var (
...@@ -115,6 +119,8 @@ var ( ...@@ -115,6 +119,8 @@ var (
queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil) queuedGauge = metrics.NewRegisteredGauge("txpool/queued", nil)
localGauge = metrics.NewRegisteredGauge("txpool/local", nil) localGauge = metrics.NewRegisteredGauge("txpool/local", nil)
slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil) slotsGauge = metrics.NewRegisteredGauge("txpool/slots", nil)
reheapTimer = metrics.NewRegisteredTimer("txpool/reheap", nil)
) )
// TxStatus is the current status of a transaction as seen by the pool. // TxStatus is the current status of a transaction as seen by the pool.
...@@ -165,7 +171,7 @@ var DefaultTxPoolConfig = TxPoolConfig{ ...@@ -165,7 +171,7 @@ var DefaultTxPoolConfig = TxPoolConfig{
PriceBump: 10, PriceBump: 10,
AccountSlots: 16, AccountSlots: 16,
GlobalSlots: 4096, GlobalSlots: 4096 + 1024, // urgent + floating queue capacity with 4:1 ratio
AccountQueue: 64, AccountQueue: 64,
GlobalQueue: 1024, GlobalQueue: 1024,
...@@ -230,6 +236,7 @@ type TxPool struct { ...@@ -230,6 +236,7 @@ type TxPool struct {
istanbul bool // Fork indicator whether we are in the istanbul stage. istanbul bool // Fork indicator whether we are in the istanbul stage.
eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions. eip2718 bool // Fork indicator whether we are using EIP-2718 type transactions.
eip1559 bool // Fork indicator whether we are using EIP-1559 type transactions.
currentState *state.StateDB // Current state in the blockchain head currentState *state.StateDB // Current state in the blockchain head
pendingNonces *txNoncer // Pending state tracking virtual nonces pendingNonces *txNoncer // Pending state tracking virtual nonces
...@@ -426,10 +433,18 @@ func (pool *TxPool) SetGasPrice(price *big.Int) { ...@@ -426,10 +433,18 @@ func (pool *TxPool) SetGasPrice(price *big.Int) {
pool.mu.Lock() pool.mu.Lock()
defer pool.mu.Unlock() defer pool.mu.Unlock()
old := pool.gasPrice
pool.gasPrice = price pool.gasPrice = price
for _, tx := range pool.priced.Cap(price) { // if the min miner fee increased, remove transactions below the new threshold
pool.removeTx(tx.Hash(), false) if price.Cmp(old) > 0 {
// pool.priced is sorted by FeeCap, so we have to iterate through pool.all instead
drop := pool.all.RemotesBelowTip(price)
for _, tx := range drop {
pool.removeTx(tx.Hash(), false)
}
pool.priced.Removed(len(drop))
} }
log.Info("Transaction pool price threshold updated", "price", price) log.Info("Transaction pool price threshold updated", "price", price)
} }
...@@ -527,6 +542,10 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { ...@@ -527,6 +542,10 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if !pool.eip2718 && tx.Type() != types.LegacyTxType { if !pool.eip2718 && tx.Type() != types.LegacyTxType {
return ErrTxTypeNotSupported return ErrTxTypeNotSupported
} }
// Reject dynamic fee transactions until EIP-1559 activates.
if !pool.eip1559 && tx.Type() == types.DynamicFeeTxType {
return ErrTxTypeNotSupported
}
// Reject transactions over defined size to prevent DOS attacks // Reject transactions over defined size to prevent DOS attacks
if uint64(tx.Size()) > txMaxSize { if uint64(tx.Size()) > txMaxSize {
return ErrOversizedData return ErrOversizedData
...@@ -540,13 +559,17 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error { ...@@ -540,13 +559,17 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
if pool.currentMaxGas < tx.Gas() { if pool.currentMaxGas < tx.Gas() {
return ErrGasLimit return ErrGasLimit
} }
// Ensure feeCap is less than or equal to tip.
if tx.FeeCapIntCmp(tx.Tip()) < 0 {
return ErrTipAboveFeeCap
}
// Make sure the transaction is signed properly. // Make sure the transaction is signed properly.
from, err := types.Sender(pool.signer, tx) from, err := types.Sender(pool.signer, tx)
if err != nil { if err != nil {
return ErrInvalidSender return ErrInvalidSender
} }
// Drop non-local transactions under our own minimal accepted gas price // Drop non-local transactions under our own minimal accepted gas price or tip
if !local && tx.GasPriceIntCmp(pool.gasPrice) < 0 { if !local && tx.TipIntCmp(pool.gasPrice) < 0 {
return ErrUnderpriced return ErrUnderpriced
} }
// Ensure the transaction adheres to nonce ordering // Ensure the transaction adheres to nonce ordering
...@@ -598,7 +621,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e ...@@ -598,7 +621,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue { if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue {
// If the new transaction is underpriced, don't accept it // If the new transaction is underpriced, don't accept it
if !isLocal && pool.priced.Underpriced(tx) { if !isLocal && pool.priced.Underpriced(tx) {
log.Trace("Discarding underpriced transaction", "hash", hash, "price", tx.GasPrice()) log.Trace("Discarding underpriced transaction", "hash", hash, "tip", tx.Tip(), "feeCap", tx.FeeCap())
underpricedTxMeter.Mark(1) underpricedTxMeter.Mark(1)
return false, ErrUnderpriced return false, ErrUnderpriced
} }
...@@ -615,7 +638,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e ...@@ -615,7 +638,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
} }
// Kick out the underpriced remote transactions. // Kick out the underpriced remote transactions.
for _, tx := range drop { for _, tx := range drop {
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "price", tx.GasPrice()) log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "tip", tx.Tip(), "feeCap", tx.FeeCap())
underpricedTxMeter.Mark(1) underpricedTxMeter.Mark(1)
pool.removeTx(tx.Hash(), false) pool.removeTx(tx.Hash(), false)
} }
...@@ -1088,6 +1111,9 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt ...@@ -1088,6 +1111,9 @@ func (pool *TxPool) runReorg(done chan struct{}, reset *txpoolResetRequest, dirt
// because of another transaction (e.g. higher gas price). // because of another transaction (e.g. higher gas price).
if reset != nil { if reset != nil {
pool.demoteUnexecutables() pool.demoteUnexecutables()
if reset.newHead != nil {
pool.priced.SetBaseFee(reset.newHead.BaseFee)
}
} }
// Ensure pool.queue and pool.pending sizes stay within the configured limits. // Ensure pool.queue and pool.pending sizes stay within the configured limits.
pool.truncatePending() pool.truncatePending()
...@@ -1205,6 +1231,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) { ...@@ -1205,6 +1231,7 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
next := new(big.Int).Add(newHead.Number, big.NewInt(1)) next := new(big.Int).Add(newHead.Number, big.NewInt(1))
pool.istanbul = pool.chainconfig.IsIstanbul(next) pool.istanbul = pool.chainconfig.IsIstanbul(next)
pool.eip2718 = pool.chainconfig.IsBerlin(next) pool.eip2718 = pool.chainconfig.IsBerlin(next)
pool.eip1559 = pool.chainconfig.IsLondon(next)
} }
// promoteExecutables moves transactions that have become processable from the // promoteExecutables moves transactions that have become processable from the
...@@ -1408,6 +1435,10 @@ func (pool *TxPool) truncateQueue() { ...@@ -1408,6 +1435,10 @@ func (pool *TxPool) truncateQueue() {
// demoteUnexecutables removes invalid and processed transactions from the pools // demoteUnexecutables removes invalid and processed transactions from the pools
// executable/pending queue and any subsequent transactions that become unexecutable // executable/pending queue and any subsequent transactions that become unexecutable
// are moved back into the future queue. // are moved back into the future queue.
//
// Note: transactions are not marked as removed in the priced list because re-heaping
// is always explicitly triggered by SetBaseFee and it would be unnecessary and wasteful
// to trigger a re-heap is this function
func (pool *TxPool) demoteUnexecutables() { func (pool *TxPool) demoteUnexecutables() {
// Iterate over all accounts and demote any non-executable transactions // Iterate over all accounts and demote any non-executable transactions
for addr, list := range pool.pending { for addr, list := range pool.pending {
...@@ -1427,7 +1458,6 @@ func (pool *TxPool) demoteUnexecutables() { ...@@ -1427,7 +1458,6 @@ func (pool *TxPool) demoteUnexecutables() {
log.Trace("Removed unpayable pending transaction", "hash", hash) log.Trace("Removed unpayable pending transaction", "hash", hash)
pool.all.Remove(hash) pool.all.Remove(hash)
} }
pool.priced.Removed(len(olds) + len(drops))
pendingNofundsMeter.Mark(int64(len(drops))) pendingNofundsMeter.Mark(int64(len(drops)))
for _, tx := range invalids { for _, tx := range invalids {
...@@ -1709,6 +1739,18 @@ func (t *txLookup) RemoteToLocals(locals *accountSet) int { ...@@ -1709,6 +1739,18 @@ func (t *txLookup) RemoteToLocals(locals *accountSet) int {
return migrated return migrated
} }
// RemotesBelowTip finds all remote transactions below the given tip threshold.
func (t *txLookup) RemotesBelowTip(threshold *big.Int) types.Transactions {
found := make(types.Transactions, 0, 128)
t.Range(func(hash common.Hash, tx *types.Transaction, local bool) bool {
if tx.TipIntCmp(threshold) < 0 {
found = append(found, tx)
}
return true
}, false, true) // Only iterate remotes
return found
}
// numSlots calculates the number of slots needed for a single transaction. // numSlots calculates the number of slots needed for a single transaction.
func numSlots(tx *types.Transaction) int { func numSlots(tx *types.Transaction) int {
return int((tx.Size() + txSlotSize - 1) / txSlotSize) return int((tx.Size() + txSlotSize - 1) / txSlotSize)
......
This diff is collapsed.
...@@ -300,33 +300,60 @@ func (tx *Transaction) Cost() *big.Int { ...@@ -300,33 +300,60 @@ func (tx *Transaction) Cost() *big.Int {
return total return total
} }
// RawSignatureValues returns the V, R, S signature values of the transaction.
// The return values should not be modified by the caller.
func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) {
return tx.inner.rawSignatureValues()
}
// FeeCapCmp compares the fee cap of two transactions.
func (tx *Transaction) FeeCapCmp(other *Transaction) int {
return tx.inner.feeCap().Cmp(other.inner.feeCap())
}
// FeeCapIntCmp compares the fee cap of the transaction against the given fee cap.
func (tx *Transaction) FeeCapIntCmp(other *big.Int) int {
return tx.inner.feeCap().Cmp(other)
}
// TipCmp compares the tip of two transactions.
func (tx *Transaction) TipCmp(other *Transaction) int {
return tx.inner.tip().Cmp(other.inner.tip())
}
// TipIntCmp compares the tip of the transaction against the given tip.
func (tx *Transaction) TipIntCmp(other *big.Int) int {
return tx.inner.tip().Cmp(other)
}
// EffectiveTip returns the effective miner tip for the given base fee. // EffectiveTip returns the effective miner tip for the given base fee.
// Returns error in case of a negative effective miner tip. // Note: if the effective tip is negative, this method returns both error
// the actual negative value, _and_ ErrFeeCapTooLow
func (tx *Transaction) EffectiveTip(baseFee *big.Int) (*big.Int, error) { func (tx *Transaction) EffectiveTip(baseFee *big.Int) (*big.Int, error) {
if baseFee == nil { if baseFee == nil {
return tx.Tip(), nil return tx.Tip(), nil
} }
var err error
feeCap := tx.FeeCap() feeCap := tx.FeeCap()
if feeCap.Cmp(baseFee) == -1 { if feeCap.Cmp(baseFee) == -1 {
return nil, ErrFeeCapTooLow err = ErrFeeCapTooLow
} }
return math.BigMin(tx.Tip(), feeCap.Sub(feeCap, baseFee)), nil return math.BigMin(tx.Tip(), feeCap.Sub(feeCap, baseFee)), err
}
// RawSignatureValues returns the V, R, S signature values of the transaction.
// The return values should not be modified by the caller.
func (tx *Transaction) RawSignatureValues() (v, r, s *big.Int) {
return tx.inner.rawSignatureValues()
} }
// GasPriceCmp compares the gas prices of two transactions. // EffectiveTipValue is identical to EffectiveTip, but does not return an
func (tx *Transaction) GasPriceCmp(other *Transaction) int { // error in case the effective tip is negative
return tx.inner.gasPrice().Cmp(other.inner.gasPrice()) func (tx *Transaction) EffectiveTipValue(baseFee *big.Int) *big.Int {
effectiveTip, _ := tx.EffectiveTip(baseFee)
return effectiveTip
} }
// GasPriceIntCmp compares the gas price of the transaction against the given price. // EffectiveTipCmp compares the effective tip of two transactions assuming the given base fee.
func (tx *Transaction) GasPriceIntCmp(other *big.Int) int { func (tx *Transaction) EffectiveTipCmp(other *Transaction, baseFee *big.Int) int {
return tx.inner.gasPrice().Cmp(other) if baseFee == nil {
return tx.TipCmp(other)
}
return tx.EffectiveTipValue(baseFee).Cmp(other.EffectiveTipValue(baseFee))
} }
// Hash returns the transaction hash. // Hash returns the transaction hash.
......
...@@ -188,7 +188,7 @@ type transactionsByGasPrice []*types.Transaction ...@@ -188,7 +188,7 @@ type transactionsByGasPrice []*types.Transaction
func (t transactionsByGasPrice) Len() int { return len(t) } func (t transactionsByGasPrice) Len() int { return len(t) }
func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] } func (t transactionsByGasPrice) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].GasPriceCmp(t[j]) < 0 } func (t transactionsByGasPrice) Less(i, j int) bool { return t[i].FeeCapCmp(t[j]) < 0 }
// getBlockPrices calculates the lowest transaction gas price in a given block // getBlockPrices calculates the lowest transaction gas price in a given block
// and sends it to the result channel. If the block is empty or all transactions // and sends it to the result channel. If the block is empty or all transactions
...@@ -210,7 +210,7 @@ func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, bloc ...@@ -210,7 +210,7 @@ func (gpo *Oracle) getBlockPrices(ctx context.Context, signer types.Signer, bloc
var prices []*big.Int var prices []*big.Int
for _, tx := range txs { for _, tx := range txs {
if ignoreUnder != nil && tx.GasPriceIntCmp(ignoreUnder) == -1 { if ignoreUnder != nil && tx.GasPrice().Cmp(ignoreUnder) == -1 {
continue continue
} }
sender, err := types.Sender(signer, tx) sender, err := types.Sender(signer, tx)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment