fix trx pool gas limits; cleanup commitTransactions()
This commit is contained in:
+18
-8
@@ -223,8 +223,9 @@ type txList struct {
|
||||
strict bool // Whether nonces are strictly continuous or not
|
||||
txs *txSortedMap // Heap indexed sorted hash map of the transactions
|
||||
|
||||
costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
|
||||
gascap uint64 // Gas limit of the highest spending transaction (reset only if exceeds block limit)
|
||||
costcap *big.Int // Price of the highest costing transaction (reset only if exceeds balance)
|
||||
legacyGasCap uint64 // Gas limit of the highest spending legacy transaction (reset only if exceeds block limit)
|
||||
eip1559GasCap uint64 // Gas limit of the highest spending eip1559 transaction
|
||||
}
|
||||
|
||||
// newTxList create a new transaction list for maintaining nonce-indexable fast,
|
||||
@@ -250,6 +251,7 @@ func (l *txList) Overlaps(tx *types.Transaction) bool {
|
||||
// thresholds are also potentially updated.
|
||||
func (l *txList) Add(tx *types.Transaction, priceBump uint64, baseFee *big.Int) (bool, *types.Transaction) {
|
||||
// If there's an older better transaction, abort
|
||||
eip1559 := false
|
||||
old := l.txs.Get(tx.Nonce())
|
||||
if old != nil {
|
||||
// Have to ensure that the new gas price is higher than the old gas
|
||||
@@ -262,6 +264,7 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64, baseFee *big.Int)
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
eip1559 = true
|
||||
newGasPrice := new(big.Int).Add(baseFee, tx.GasPremium())
|
||||
if newGasPrice.Cmp(tx.FeeCap()) > 0 {
|
||||
newGasPrice.Set(tx.FeeCap())
|
||||
@@ -281,6 +284,7 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64, baseFee *big.Int)
|
||||
return false, nil
|
||||
}
|
||||
} else {
|
||||
eip1559 = true
|
||||
newGasPrice := new(big.Int).Add(baseFee, tx.GasPremium())
|
||||
if newGasPrice.Cmp(tx.FeeCap()) > 0 {
|
||||
newGasPrice.Set(tx.FeeCap())
|
||||
@@ -296,8 +300,11 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64, baseFee *big.Int)
|
||||
if cost := tx.Cost(baseFee); l.costcap.Cmp(cost) < 0 {
|
||||
l.costcap = cost
|
||||
}
|
||||
if gas := tx.Gas(); l.gascap < gas {
|
||||
l.gascap = gas
|
||||
if eip1559 && l.eip1559GasCap < tx.Gas() {
|
||||
l.eip1559GasCap = tx.Gas()
|
||||
}
|
||||
if !eip1559 && l.legacyGasCap < tx.Gas() {
|
||||
l.legacyGasCap = tx.Gas()
|
||||
}
|
||||
return true, old
|
||||
}
|
||||
@@ -318,16 +325,19 @@ func (l *txList) Forward(threshold uint64) types.Transactions {
|
||||
// a point in calculating all the costs or if the balance covers all. If the threshold
|
||||
// is lower than the costgas cap, the caps will be reset to a new high after removing
|
||||
// the newly invalidated transactions.
|
||||
func (l *txList) Filter(costLimit *big.Int, gasLimit uint64, baseFee *big.Int) (types.Transactions, types.Transactions) {
|
||||
func (l *txList) Filter(costLimit *big.Int, legacyGasLimit, eip1559GasLimit uint64, baseFee *big.Int) (types.Transactions, types.Transactions) {
|
||||
// If all transactions are below the threshold, short circuit
|
||||
if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit {
|
||||
if l.costcap.Cmp(costLimit) <= 0 && l.legacyGasCap <= legacyGasLimit && l.eip1559GasCap <= eip1559GasLimit {
|
||||
return nil, nil
|
||||
}
|
||||
l.costcap = new(big.Int).Set(costLimit) // Lower the caps to the thresholds
|
||||
l.gascap = gasLimit
|
||||
l.legacyGasCap = legacyGasLimit
|
||||
l.eip1559GasCap = eip1559GasLimit
|
||||
|
||||
// Filter out all the transactions above the account's funds
|
||||
removed := l.txs.Filter(func(tx *types.Transaction) bool { return tx.Cost(baseFee).Cmp(costLimit) > 0 || tx.Gas() > gasLimit })
|
||||
removed := l.txs.Filter(func(tx *types.Transaction) bool {
|
||||
return tx.Cost(baseFee).Cmp(costLimit) > 0 || (tx.GasPrice() != nil && tx.Gas() > legacyGasLimit) || (tx.GasPremium()) != nil && tx.Gas() > eip1559GasLimit
|
||||
})
|
||||
|
||||
// If the list was strict, filter anything above the lowest nonce
|
||||
var invalids types.Transactions
|
||||
|
||||
+26
-11
@@ -67,9 +67,13 @@ var (
|
||||
// with a different one without the required price bump.
|
||||
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
|
||||
|
||||
// ErrGasLimit is returned if a transaction's requested gas limit exceeds the
|
||||
// maximum allowance of the current block.
|
||||
ErrGasLimit = errors.New("exceeds block gas limit")
|
||||
// ErrLegacyGasLimit is returned if a transaction's requested gas limit exceeds the
|
||||
// maximum allowance of the current block for legacy transactions.
|
||||
ErrLegacyGasLimit = errors.New("exceeds block gas limit for legacy transactions")
|
||||
|
||||
// ErrEIP1559GasLimit is returned if a transaction's requested gas limit exceeds the
|
||||
// maximum allowance of the current block for EIP1559 transactions.
|
||||
ErrEIP1559GasLimit = errors.New("exceeds block gas limit for EIP1559 transactions")
|
||||
|
||||
// ErrNegativeValue is a sanity error to ensure no one is able to specify a
|
||||
// transaction with a negative value.
|
||||
@@ -243,9 +247,10 @@ type TxPool struct {
|
||||
|
||||
istanbul bool // Fork indicator whether we are in the istanbul stage.
|
||||
|
||||
currentState *state.StateDB // Current state in the blockchain head
|
||||
pendingNonces *txNoncer // Pending state tracking virtual nonces
|
||||
currentMaxGas uint64 // Current gas limit for transaction caps
|
||||
currentState *state.StateDB // Current state in the blockchain head
|
||||
pendingNonces *txNoncer // Pending state tracking virtual nonces
|
||||
currentLegacyMaxGas uint64 // Current gas limit for legacy transaction caps
|
||||
currentEIP1559MaxGas uint64 // Current gas limit for EIP1559 transaction caps
|
||||
|
||||
locals *accountSet // Set of local transaction to exempt from eviction rules
|
||||
journal *txJournal // Journal of local transaction to back up to disk
|
||||
@@ -571,8 +576,11 @@ func (pool *TxPool) validateTx(tx *types.Transaction, local bool) error {
|
||||
return ErrNegativeValue
|
||||
}
|
||||
// Ensure the transaction doesn't exceed the current block limit gas.
|
||||
if pool.currentMaxGas < tx.Gas() {
|
||||
return ErrGasLimit
|
||||
if tx.GasPrice() != nil && pool.currentLegacyMaxGas < tx.Gas() {
|
||||
return ErrLegacyGasLimit
|
||||
}
|
||||
if tx.GasPrice() == nil && pool.currentEIP1559MaxGas < tx.Gas() {
|
||||
return ErrEIP1559GasLimit
|
||||
}
|
||||
// Make sure the transaction is signed properly
|
||||
from, err := types.Sender(pool.signer, tx)
|
||||
@@ -1200,7 +1208,14 @@ func (pool *TxPool) reset(oldHead, newHead *types.Header) {
|
||||
}
|
||||
pool.currentState = statedb
|
||||
pool.pendingNonces = newTxNoncer(statedb)
|
||||
pool.currentMaxGas = newHead.GasLimit
|
||||
|
||||
if pool.chainconfig.IsEIP1559(newHead.Number) {
|
||||
pool.currentLegacyMaxGas = params.MaxGasEIP1559 - newHead.GasLimit
|
||||
pool.currentEIP1559MaxGas = newHead.GasLimit
|
||||
} else {
|
||||
pool.currentLegacyMaxGas = newHead.GasLimit
|
||||
pool.currentEIP1559MaxGas = 0
|
||||
}
|
||||
|
||||
// Inject any transactions discarded due to reorgs
|
||||
log.Debug("Reinjecting stale transactions", "count", len(reinject))
|
||||
@@ -1233,7 +1248,7 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
|
||||
log.Trace("Removed old queued transaction", "hash", hash)
|
||||
}
|
||||
// Drop all transactions that are too costly (low balance or out of gas)
|
||||
drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas, pool.chain.CurrentBlock().BaseFee())
|
||||
drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentLegacyMaxGas, pool.currentEIP1559MaxGas, pool.chain.CurrentBlock().BaseFee())
|
||||
for _, tx := range drops {
|
||||
hash := tx.Hash()
|
||||
pool.all.Remove(hash)
|
||||
@@ -1425,7 +1440,7 @@ func (pool *TxPool) demoteUnexecutables() {
|
||||
log.Trace("Removed old pending transaction", "hash", hash)
|
||||
}
|
||||
// Drop all transactions that are too costly (low balance or out of gas), and queue any invalids back for later
|
||||
drops, invalids := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas, pool.chain.CurrentBlock().BaseFee())
|
||||
drops, invalids := list.Filter(pool.currentState.GetBalance(addr), pool.currentLegacyMaxGas, pool.currentEIP1559MaxGas, pool.chain.CurrentBlock().BaseFee())
|
||||
for _, tx := range drops {
|
||||
hash := tx.Hash()
|
||||
log.Trace("Removed unpayable pending transaction", "hash", hash)
|
||||
|
||||
@@ -1374,14 +1374,16 @@ func TestTransactionDroppingEIP1559(t *testing.T) {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 4)
|
||||
}
|
||||
// Reduce the block gas limit, check that invalidated transactions are dropped
|
||||
// After EIP1559 initialization, the legacy gas limit is `params.MaxGasEIP1559 - gasLimit` and the EIP1559 gas limit is `gasLimit`
|
||||
// As such by reducing the `gasLimit` we increase the legacy gas limit, this must be accounted for in these tests (tx1 is not over-gased)
|
||||
pool.chain.(*testBlockChain).gasLimit = 100
|
||||
<-pool.requestReset(nil, nil)
|
||||
|
||||
if _, ok := pool.pending[account].txs.items[tx0.Nonce()]; !ok {
|
||||
t.Errorf("funded pending transaction missing: %v", tx0)
|
||||
}
|
||||
if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; ok {
|
||||
t.Errorf("over-gased pending transaction present: %v", tx1)
|
||||
if _, ok := pool.pending[account].txs.items[tx1.Nonce()]; !ok {
|
||||
t.Errorf("funded pending transaction missing: %v", tx1)
|
||||
}
|
||||
if _, ok := pool.queue[account].txs.items[tx10.Nonce()]; !ok {
|
||||
t.Errorf("funded queued transaction missing: %v", tx10)
|
||||
@@ -1389,8 +1391,8 @@ func TestTransactionDroppingEIP1559(t *testing.T) {
|
||||
if _, ok := pool.queue[account].txs.items[tx11.Nonce()]; ok {
|
||||
t.Errorf("over-gased queued transaction present: %v", tx11)
|
||||
}
|
||||
if pool.all.Count() != 2 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 2)
|
||||
if pool.all.Count() != 3 {
|
||||
t.Errorf("total transaction mismatch: have %d, want %d", pool.all.Count(), 3)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-8
@@ -343,14 +343,16 @@ func (pool *TxPool) Stats() (pending int) {
|
||||
|
||||
// validateTx checks whether a transaction is valid according to the consensus rules.
|
||||
func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error {
|
||||
header := pool.chain.GetHeaderByHash(pool.head)
|
||||
|
||||
// EIP1559 guards
|
||||
if pool.config.IsEIP1559(pool.chain.CurrentHeader().Number) && pool.chain.CurrentHeader().BaseFee == nil {
|
||||
if pool.config.IsEIP1559(header.Number) && header.BaseFee == nil {
|
||||
return core.ErrNoBaseFee
|
||||
}
|
||||
if pool.config.IsEIP1559Finalized(pool.chain.CurrentHeader().Number) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) {
|
||||
if pool.config.IsEIP1559Finalized(header.Number) && (tx.GasPremium() == nil || tx.FeeCap() == nil || tx.GasPrice() != nil) {
|
||||
return core.ErrTxNotEIP1559
|
||||
}
|
||||
if !pool.config.IsEIP1559(pool.chain.CurrentHeader().Number) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) {
|
||||
if !pool.config.IsEIP1559(header.Number) && (tx.GasPremium() != nil || tx.FeeCap() != nil || tx.GasPrice() == nil) {
|
||||
return core.ErrTxIsEIP1559
|
||||
}
|
||||
if tx.GasPrice() != nil && (tx.GasPremium() != nil || tx.FeeCap() != nil) {
|
||||
@@ -379,9 +381,18 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
|
||||
|
||||
// Check the transaction doesn't exceed the current
|
||||
// block limit gas.
|
||||
header := pool.chain.GetHeaderByHash(pool.head)
|
||||
if header.GasLimit < tx.Gas() {
|
||||
return core.ErrGasLimit
|
||||
var legacyGasLimit, eip1559GasLimit uint64
|
||||
if pool.config.IsEIP1559(header.Number) {
|
||||
legacyGasLimit = params.MaxGasEIP1559 - header.GasLimit
|
||||
eip1559GasLimit = header.GasLimit
|
||||
} else {
|
||||
legacyGasLimit = header.GasLimit
|
||||
}
|
||||
if tx.GasPrice() != nil && legacyGasLimit < tx.Gas() {
|
||||
return core.ErrLegacyGasLimit
|
||||
}
|
||||
if tx.GasPremium() != nil && eip1559GasLimit < tx.Gas() {
|
||||
return core.ErrEIP1559GasLimit
|
||||
}
|
||||
|
||||
// Transactions can't be negative. This may never happen
|
||||
@@ -393,7 +404,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error
|
||||
|
||||
// Transactor should have enough funds to cover the costs
|
||||
// cost == V + GP * GL
|
||||
if b := currentState.GetBalance(from); b.Cmp(tx.Cost(pool.chain.CurrentHeader().BaseFee)) < 0 {
|
||||
if b := currentState.GetBalance(from); b.Cmp(tx.Cost(header.BaseFee)) < 0 {
|
||||
return core.ErrInsufficientFunds
|
||||
}
|
||||
|
||||
@@ -414,7 +425,7 @@ func (pool *TxPool) add(ctx context.Context, tx *types.Transaction) error {
|
||||
hash := tx.Hash()
|
||||
|
||||
if pool.pending[hash] != nil {
|
||||
return fmt.Errorf("Known transaction (%x)", hash[:4])
|
||||
return fmt.Errorf("known transaction (%x)", hash[:4])
|
||||
}
|
||||
err := pool.validateTx(ctx, tx)
|
||||
if err != nil {
|
||||
|
||||
+21
-31
@@ -487,7 +487,7 @@ func (w *worker) mainLoop() {
|
||||
if !w.chainConfig.IsEIP1559(w.current.header.Number) && legacyGasPool != nil && legacyGasPool.Gas() < params.TxGas {
|
||||
continue
|
||||
}
|
||||
// When we are between EIP1559 activation and finalization we can received transactions of both types
|
||||
// When we are between EIP1559 activation and finalization we can receive transactions of both types
|
||||
// and one pool could be exhausted while the other is not
|
||||
// If both pools are exhausted we know the block is full but if only one is we could still accept transactions
|
||||
// of the other type so we need to proceed into commitTransactions()
|
||||
@@ -506,7 +506,7 @@ func (w *worker) mainLoop() {
|
||||
txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, w.current.header.BaseFee)
|
||||
tcount := w.current.tcount
|
||||
w.commitTransactions(txset, coinbase, nil)
|
||||
// Only update the snapshot if any new transactons were added
|
||||
// Only update the snapshot if any new transactions were added
|
||||
// to the pending block
|
||||
if tcount != w.current.tcount {
|
||||
w.updateSnapshot()
|
||||
@@ -769,11 +769,6 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
|
||||
w.current.gasPool = new(core.GasPool).AddGas(legacyGasLimit)
|
||||
}
|
||||
|
||||
oneTxType := false
|
||||
if w.chainConfig.IsEIP1559Finalized(w.current.header.Number) || !w.chainConfig.IsEIP1559(w.current.header.Number) {
|
||||
oneTxType = true
|
||||
}
|
||||
|
||||
var coalescedLogs []*types.Log
|
||||
|
||||
for {
|
||||
@@ -782,41 +777,20 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
|
||||
if tx == nil {
|
||||
break
|
||||
}
|
||||
|
||||
// Set which gasPool to use based on the type of transaction
|
||||
eip1559 := false
|
||||
var gp *core.GasPool
|
||||
if w.chainConfig.IsEIP1559(w.current.header.Number) && tx.GasPrice() == nil && tx.GasPremium() != nil && tx.FeeCap() != nil {
|
||||
if w.current.gp1559 != nil && tx.GasPrice() == nil && tx.GasPremium() != nil && tx.FeeCap() != nil {
|
||||
gp = w.current.gp1559
|
||||
eip1559 = true
|
||||
} else if !w.chainConfig.IsEIP1559Finalized(w.current.header.Number) && tx.GasPremium() == nil && tx.FeeCap() == nil && tx.GasPrice() != nil {
|
||||
} else if w.current.gasPool != nil && tx.GasPremium() == nil && tx.FeeCap() == nil && tx.GasPrice() != nil {
|
||||
gp = w.current.gasPool
|
||||
} else {
|
||||
log.Error("Transaction does not conform with expected format (legacy or EIP1559)")
|
||||
continue
|
||||
}
|
||||
|
||||
// If we processing both types of transactions then we can break if both pools are exhausted
|
||||
if w.current.gasPool != nil && w.current.gasPool.Gas() < params.TxGas && w.current.gp1559 != nil && w.current.gp1559.Gas() < params.TxGas {
|
||||
log.Trace("Not enough gas for further transactions", "have legacy pool", w.current.gasPool, "and eip1559 pool", w.current.gp1559, "want", params.TxGas)
|
||||
break
|
||||
}
|
||||
|
||||
// If we don't have enough gas for any further transactions of this type
|
||||
if gp.Gas() < params.TxGas {
|
||||
if eip1559 {
|
||||
log.Trace("Not enough gas for further EIP1559 transactions", "have", gp, "want", params.TxGas)
|
||||
} else {
|
||||
log.Trace("Not enough gas for further legacy transactions", "have", gp, "want", params.TxGas)
|
||||
}
|
||||
// and this is the only type we are processing, then we're done
|
||||
if oneTxType {
|
||||
break
|
||||
}
|
||||
// Otherwise if only the current pool is exhausted we need to continue
|
||||
// in case some of the subsequent transactions are for the non-exhausted pool
|
||||
continue
|
||||
}
|
||||
|
||||
// In the following three cases, we will interrupt the execution of the transaction.
|
||||
// (1) new head block event arrival, the interrupt signal is 1
|
||||
// (2) worker start or restart, the interrupt signal is 1
|
||||
@@ -843,6 +817,22 @@ func (w *worker) commitTransactions(txs *types.TransactionsByPriceAndNonce, coin
|
||||
return atomic.LoadInt32(interrupt) == commitInterruptNewHead
|
||||
}
|
||||
|
||||
// If all available gas pools are empty, break
|
||||
if (w.current.gasPool == nil || w.current.gasPool.Gas() < params.TxGas) && (w.current.gp1559 == nil || w.current.gp1559.Gas() < params.TxGas) {
|
||||
log.Trace("Not enough gas for further transactions", "have legacy pool", w.current.gasPool, "and eip1559 pool", w.current.gp1559, "want", params.TxGas)
|
||||
break
|
||||
}
|
||||
|
||||
// If just the current gas pool is empty, continue
|
||||
if gp.Gas() < params.TxGas {
|
||||
if eip1559 {
|
||||
log.Trace("Not enough gas for further EIP1559 transactions", "have", gp, "want", params.TxGas)
|
||||
} else {
|
||||
log.Trace("Not enough gas for further legacy transactions", "have", gp, "want", params.TxGas)
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// Error may be ignored here. The error has already been checked
|
||||
// during transaction acceptance is the transaction pool.
|
||||
//
|
||||
|
||||
Reference in New Issue
Block a user