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"
"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/log"
"github.com/scroll-tech/go-ethereum/rollup/fees"
)
// 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
// 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
old := l.txs.Get(tx.Nonce())
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
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)
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
}
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
}
// 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
// exceeding that limit.
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
list := newTxList(true)
for _, v := range rand.Perm(len(txs)) {
list.Add(txs[v], DefaultTxPoolConfig.PriceBump)
list.Add(txs[v], nil, DefaultTxPoolConfig.PriceBump)
}
// Verify internal state
if len(list.txs.items) != len(txs) {
@ -65,7 +65,7 @@ func BenchmarkTxListAdd(b *testing.B) {
for i := 0; i < b.N; i++ {
list := newTxList(true)
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)
}
}

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
if list := pool.pending[from]; list != nil && list.Overlaps(tx) {
// 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 {
pendingDiscardMeter.Mark(1)
return false, ErrReplaceUnderpriced
@ -804,7 +804,8 @@ func (pool *TxPool) enqueueTx(hash common.Hash, tx *types.Transaction, local boo
if pool.queue[from] == nil {
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 {
// An older transaction was better, discard this
queuedDiscardMeter.Mark(1)
@ -858,7 +859,7 @@ func (pool *TxPool) promoteTx(addr common.Address, hash common.Hash, tx *types.T
}
list := pool.pending[addr]
inserted, old := list.Add(tx, pool.config.PriceBump)
inserted, old := list.Add(tx, pool.currentState, pool.config.PriceBump)
if !inserted {
// An older transaction was better, discard this
pool.all.Remove(hash)
@ -1382,8 +1383,10 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
pool.all.Remove(hash)
}
log.Trace("Removed old queued transactions", "count", len(forwards))
// 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 {
hash := tx.Hash()
pool.all.Remove(hash)
@ -1428,6 +1431,26 @@ func (pool *TxPool) promoteExecutables(accounts []common.Address) []*types.Trans
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
// pending limit. The algorithm tries to reduce transaction counts by an approximately
// 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)
}
// 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 {
hash := tx.Hash()
log.Trace("Removed unpayable pending transaction", "hash", hash)

View file

@ -24,7 +24,7 @@ import (
const (
VersionMajor = 5 // Major 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
)