fix: Check L1DataFee in txpool promoteExecutables and demoteUnexecutables (#627)

* Check L1DataFee in txpool promoteExecutables

* bump version

* implement L1 data fee in demoteUnexecutables as well

* Update core/tx_list.go

Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com>

* Update core/tx_pool.go

Co-authored-by: Péter Garamvölgyi <peter@scroll.io>

* feat: consider l1 data fee in txpool costcap (#681)

* feat(txpool): consider l1 data fee in costcap

* fix CI

* simplify logic

* remove one db read op

* bump version

---------

Co-authored-by: colin <102356659+colinlyguo@users.noreply.github.com>
Co-authored-by: Péter Garamvölgyi <peter@scroll.io>
Co-authored-by: georgehao <haohongfan@gmail.com>
This commit is contained in:
vyzo 2024-03-27 17:31:51 +02:00 committed by GitHub
parent 61e8de9c3f
commit d1e4b59eec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 77 additions and 10 deletions

View file

@ -26,7 +26,10 @@ import (
"time" "time"
"github.com/scroll-tech/go-ethereum/common" "github.com/scroll-tech/go-ethereum/common"
"github.com/scroll-tech/go-ethereum/core/state"
"github.com/scroll-tech/go-ethereum/core/types" "github.com/scroll-tech/go-ethereum/core/types"
"github.com/scroll-tech/go-ethereum/log"
"github.com/scroll-tech/go-ethereum/rollup/fees"
) )
// nonceHeap is a heap.Interface implementation over 64bit unsigned integers for // nonceHeap is a heap.Interface implementation over 64bit unsigned integers for
@ -278,7 +281,7 @@ func (l *txList) Overlaps(tx *types.Transaction) bool {
// //
// If the new transaction is accepted into the list, the lists' cost and gas // If the new transaction is accepted into the list, the lists' cost and gas
// thresholds are also potentially updated. // thresholds are also potentially updated.
func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Transaction) { func (l *txList) Add(tx *types.Transaction, state *state.StateDB, priceBump uint64) (bool, *types.Transaction) {
// If there's an older better transaction, abort // If there's an older better transaction, abort
old := l.txs.Get(tx.Nonce()) old := l.txs.Get(tx.Nonce())
if old != nil { if old != nil {
@ -303,8 +306,17 @@ func (l *txList) Add(tx *types.Transaction, priceBump uint64) (bool, *types.Tran
} }
} }
// Otherwise overwrite the old transaction with the current one // Otherwise overwrite the old transaction with the current one
l1DataFee := big.NewInt(0)
if state != nil {
var err error
l1DataFee, err = fees.CalculateL1DataFee(tx, state)
if err != nil {
log.Error("Failed to calculate L1 data fee", "err", err, "tx", tx)
return false, nil
}
}
l.txs.Put(tx) l.txs.Put(tx)
if cost := tx.Cost(); l.costcap.Cmp(cost) < 0 { if cost := new(big.Int).Add(tx.Cost(), l1DataFee); l.costcap.Cmp(cost) < 0 {
l.costcap = cost l.costcap = cost
} }
if gas := tx.Gas(); l.gascap < gas { if gas := tx.Gas(); l.gascap < gas {
@ -360,6 +372,37 @@ func (l *txList) Filter(costLimit *big.Int, gasLimit uint64) (types.Transactions
return removed, invalids return removed, invalids
} }
// FilterF removes all transactions from the list that satisfy a predicate.
// Every removed transaction is returned for any post-removal maintenance.
// Strict-mode invalidated transactions are also returned.
func (l *txList) FilterF(costLimit *big.Int, gasLimit uint64, f func(tx *types.Transaction) bool) (types.Transactions, types.Transactions) {
// If all transactions are below the threshold, short circuit
if l.costcap.Cmp(costLimit) <= 0 && l.gascap <= gasLimit {
return nil, nil
}
l.costcap = new(big.Int).Set(costLimit) // Lower the caps to the thresholds
l.gascap = gasLimit
removed := l.txs.Filter(f)
if len(removed) == 0 {
return nil, nil
}
var invalids types.Transactions
// If the list was strict, filter anything above the lowest nonce
if l.strict {
lowest := uint64(math.MaxUint64)
for _, tx := range removed {
if nonce := tx.Nonce(); lowest > nonce {
lowest = nonce
}
}
invalids = l.txs.filter(func(tx *types.Transaction) bool { return tx.Nonce() > lowest })
}
l.txs.reheap()
return removed, invalids
}
// Cap places a hard limit on the number of items, returning all transactions // Cap places a hard limit on the number of items, returning all transactions
// exceeding that limit. // exceeding that limit.
func (l *txList) Cap(threshold int) types.Transactions { func (l *txList) Cap(threshold int) types.Transactions {

View file

@ -38,7 +38,7 @@ func TestStrictTxListAdd(t *testing.T) {
// Insert the transactions in a random order // Insert the transactions in a random order
list := newTxList(true) list := newTxList(true)
for _, v := range rand.Perm(len(txs)) { for _, v := range rand.Perm(len(txs)) {
list.Add(txs[v], DefaultTxPoolConfig.PriceBump) list.Add(txs[v], nil, DefaultTxPoolConfig.PriceBump)
} }
// Verify internal state // Verify internal state
if len(list.txs.items) != len(txs) { if len(list.txs.items) != len(txs) {
@ -65,7 +65,7 @@ func BenchmarkTxListAdd(b *testing.B) {
for i := 0; i < b.N; i++ { for i := 0; i < b.N; i++ {
list := newTxList(true) list := newTxList(true)
for _, v := range rand.Perm(len(txs)) { for _, v := range rand.Perm(len(txs)) {
list.Add(txs[v], DefaultTxPoolConfig.PriceBump) list.Add(txs[v], nil, DefaultTxPoolConfig.PriceBump)
list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump) list.Filter(priceLimit, DefaultTxPoolConfig.PriceBump)
} }
} }

View file

@ -754,7 +754,7 @@ func (pool *TxPool) add(tx *types.Transaction, local bool) (replaced bool, err e
from, _ := types.Sender(pool.signer, tx) // already validated from, _ := types.Sender(pool.signer, tx) // already validated
if list := pool.pending[from]; list != nil && list.Overlaps(tx) { if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
// Nonce already pending, check if required price bump is met // Nonce already pending, check if required price bump is met
inserted, old := list.Add(tx, pool.config.PriceBump) inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump)
if !inserted { if !inserted {
pendingDiscardMeter.Mark(1) pendingDiscardMeter.Mark(1)
return false, ErrReplaceUnderpriced return false, ErrReplaceUnderpriced
@ -804,7 +804,8 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo
if pool.queue[from] == nil { if pool.queue[from] == nil {
pool.queue[from] = newTxList(false) pool.queue[from] = newTxList(false)
} }
inserted, old := pool.queue[from].Add(tx, pool.config.PriceBump)
inserted, old := pool.queue[from].Add(tx, pool.currentState, pool.config.PriceBump)
if !inserted { if !inserted {
// An older transaction was better, discard this // An older transaction was better, discard this
queuedDiscardMeter.Mark(1) queuedDiscardMeter.Mark(1)
@ -858,7 +859,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
} }
list := pool.pending[addr] list := pool.pending[addr]
inserted, old := list.Add(tx, pool.config.PriceBump) inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump)
if !inserted { if !inserted {
// An older transaction was better, discard this // An older transaction was better, discard this
pool.all.Remove(hash) pool.all.Remove(hash)
@ -1382,8 +1383,10 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
pool.all.Remove(hash) pool.all.Remove(hash)
} }
log.Trace("Removed old queued transactions", "count", len(forwards)) log.Trace("Removed old queued transactions", "count", len(forwards))
// Drop all transactions that are too costly (low balance or out of gas) // Drop all transactions that are too costly (low balance or out of gas)
drops, _ := list.Filter(pool.currentState.GetBalance(addr), pool.currentMaxGas) costLimit := pool.currentState.GetBalance(addr)
drops, _ := list.FilterF(costLimit, pool.currentMaxGas, pool.executableTxFilter(costLimit))
for _, tx := range drops { for _, tx := range drops {
hash := tx.Hash() hash := tx.Hash()
pool.all.Remove(hash) pool.all.Remove(hash)
@ -1428,6 +1431,26 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
return promoted return promoted
} }
func (pool *TxPool) executableTxFilter(costLimit *big.Int) func(tx *types.Transaction) bool {
return func(tx *types.Transaction) bool {
if tx.Gas() > pool.currentMaxGas || tx.Cost().Cmp(costLimit) > 0 {
return true
}
if pool.chainconfig.Scroll.FeeVaultEnabled() {
// recheck L1 data fee, as the oracle price may have changed
l1DataFee, err := fees.CalculateL1DataFee(tx, pool.currentState)
if err != nil {
log.Error("Failed to calculate L1 data fee", "err", err, "tx", tx)
return false
}
return costLimit.Cmp(new(big.Int).Add(tx.Cost(), l1DataFee)) < 0
}
return false
}
}
// truncatePending removes transactions from the pending queue if the pool is above the // truncatePending removes transactions from the pending queue if the pool is above the
// pending limit. The algorithm tries to reduce transaction counts by an approximately // pending limit. The algorithm tries to reduce transaction counts by an approximately
// equal number for all for accounts with many pending transactions. // equal number for all for accounts with many pending transactions.
@ -1582,7 +1605,8 @@ func (pool *TxPool) demoteUnexecutables() {
log.Trace("Removed old pending transaction", "hash", hash) 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 // 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) costLimit := pool.currentState.GetBalance(addr)
drops, invalids := list.FilterF(costLimit, pool.currentMaxGas, pool.executableTxFilter(costLimit))
for _, tx := range drops { for _, tx := range drops {
hash := tx.Hash() hash := tx.Hash()
log.Trace("Removed unpayable pending transaction", "hash", hash) log.Trace("Removed unpayable pending transaction", "hash", hash)

View file

@ -24,7 +24,7 @@ import (
const ( const (
VersionMajor = 5 // Major version component of the current release VersionMajor = 5 // Major version component of the current release
VersionMinor = 1 // Minor version component of the current release VersionMinor = 1 // Minor version component of the current release
VersionPatch = 27 // Patch version component of the current release VersionPatch = 28 // Patch version component of the current release
VersionMeta = "mainnet" // Version metadata to append to the version string VersionMeta = "mainnet" // Version metadata to append to the version string
) )