From d1e4b59eecc548b4fa69eb746880cd423b001888 Mon Sep 17 00:00:00 2001 From: vyzo Date: Wed, 27 Mar 2024 17:31:51 +0200 Subject: [PATCH] fix: Check L1DataFee in txpool promoteExecutables and demoteUnexecutables (#627) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 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 * 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 Co-authored-by: georgehao --- core/tx_list.go | 47 ++++++++++++++++++++++++++++++++++++++++++-- core/tx_list_test.go | 4 ++-- core/tx_pool.go | 34 +++++++++++++++++++++++++++----- params/version.go | 2 +- 4 files changed, 77 insertions(+), 10 deletions(-) diff --git a/core/tx_list.go b/core/tx_list.go index 36b48cf538..3742d7416e 100644 --- a/core/tx_list.go +++ b/core/tx_list.go @@ -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 { diff --git a/core/tx_list_test.go b/core/tx_list_test.go index fe401f2c36..bd3fee86d8 100644 --- a/core/tx_list_test.go +++ b/core/tx_list_test.go @@ -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) } } diff --git a/core/tx_pool.go b/core/tx_pool.go index a81ebe17d7..063686e342 100644 --- a/core/tx_pool.go +++ b/core/tx_pool.go @@ -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) diff --git a/params/version.go b/params/version.go index bedb0d47e4..5ab93b3369 100644 --- a/params/version.go +++ b/params/version.go @@ -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 )