From e971c8c497b194a3bae3406395507229714f06de Mon Sep 17 00:00:00 2001
From: devopsbo3 <69951731+devopsbo3@users.noreply.github.com>
Date: Fri, 10 Nov 2023 11:21:03 -0600
Subject: [PATCH] Revert "core/txpool/blobpool: 4844 blob transaction pool
(#26940)"
This reverts commit 16fd9c786e5bffa005eb5545dbd119636a3b35a8.
---
cmd/geth/main.go | 3 -
cmd/utils/flags.go | 19 -
core/txpool/blobpool/blobpool.go | 1533 --------------------
core/txpool/blobpool/blobpool_test.go | 1247 ----------------
core/txpool/blobpool/config.go | 50 -
core/txpool/blobpool/evictheap.go | 146 --
core/txpool/blobpool/evictheap_test.go | 320 ----
core/txpool/blobpool/interface.go | 44 -
core/txpool/blobpool/limbo.go | 258 ----
core/txpool/blobpool/metrics.go | 78 -
core/txpool/blobpool/priority.go | 90 --
core/txpool/blobpool/priority_test.go | 87 --
core/txpool/blobpool/slotter.go | 38 -
core/txpool/blobpool/slotter_test.go | 58 -
core/txpool/errors.go | 4 -
core/txpool/legacypool/legacypool.go | 109 +-
core/txpool/legacypool/legacypool2_test.go | 8 +-
core/txpool/legacypool/legacypool_test.go | 64 +-
core/txpool/subpool.go | 31 +-
core/txpool/txpool.go | 80 +-
core/txpool/validation.go | 11 -
core/types/transaction.go | 131 +-
core/types/transaction_test.go | 148 ++
eth/api_backend.go | 6 +-
eth/backend.go | 8 +-
eth/ethconfig/config.go | 5 +-
eth/ethconfig/gen_config.go | 7 -
eth/handler.go | 2 +-
eth/handler_test.go | 16 +-
eth/sync.go | 25 +-
go.mod | 5 +-
go.sum | 12 +-
miner/ordering.go | 147 --
miner/ordering_test.go | 188 ---
miner/worker.go | 50 +-
35 files changed, 363 insertions(+), 4665 deletions(-)
delete mode 100644 core/txpool/blobpool/blobpool.go
delete mode 100644 core/txpool/blobpool/blobpool_test.go
delete mode 100644 core/txpool/blobpool/config.go
delete mode 100644 core/txpool/blobpool/evictheap.go
delete mode 100644 core/txpool/blobpool/evictheap_test.go
delete mode 100644 core/txpool/blobpool/interface.go
delete mode 100644 core/txpool/blobpool/limbo.go
delete mode 100644 core/txpool/blobpool/metrics.go
delete mode 100644 core/txpool/blobpool/priority.go
delete mode 100644 core/txpool/blobpool/priority_test.go
delete mode 100644 core/txpool/blobpool/slotter.go
delete mode 100644 core/txpool/blobpool/slotter_test.go
delete mode 100644 miner/ordering.go
delete mode 100644 miner/ordering_test.go
diff --git a/cmd/geth/main.go b/cmd/geth/main.go
index 0dcb38358f687..8ada24829cf4d 100644
--- a/cmd/geth/main.go
+++ b/cmd/geth/main.go
@@ -81,9 +81,6 @@ var (
utils.TxPoolAccountQueueFlag,
utils.TxPoolGlobalQueueFlag,
utils.TxPoolLifetimeFlag,
- utils.BlobPoolDataDirFlag,
- utils.BlobPoolDataCapFlag,
- utils.BlobPoolPriceBumpFlag,
utils.SyncModeFlag,
utils.SyncTargetFlag,
utils.ExitWhenSyncedFlag,
diff --git a/cmd/utils/flags.go b/cmd/utils/flags.go
index e0c7a42670e15..8961e350ae5a8 100644
--- a/cmd/utils/flags.go
+++ b/cmd/utils/flags.go
@@ -368,25 +368,6 @@ var (
Value: ethconfig.Defaults.TxPool.Lifetime,
Category: flags.TxPoolCategory,
}
- // Blob transaction pool settings
- BlobPoolDataDirFlag = &cli.StringFlag{
- Name: "blobpool.datadir",
- Usage: "Data directory to store blob transactions in",
- Value: ethconfig.Defaults.BlobPool.Datadir,
- Category: flags.BlobPoolCategory,
- }
- BlobPoolDataCapFlag = &cli.Uint64Flag{
- Name: "blobpool.datacap",
- Usage: "Disk space to allocate for pending blob transactions (soft limit)",
- Value: ethconfig.Defaults.BlobPool.Datacap,
- Category: flags.BlobPoolCategory,
- }
- BlobPoolPriceBumpFlag = &cli.Uint64Flag{
- Name: "blobpool.pricebump",
- Usage: "Price bump percentage to replace an already existing blob transaction",
- Value: ethconfig.Defaults.BlobPool.PriceBump,
- Category: flags.BlobPoolCategory,
- }
// Performance tuning settings
CacheFlag = &cli.IntFlag{
Name: "cache",
diff --git a/core/txpool/blobpool/blobpool.go b/core/txpool/blobpool/blobpool.go
deleted file mode 100644
index baf5e54667e36..0000000000000
--- a/core/txpool/blobpool/blobpool.go
+++ /dev/null
@@ -1,1533 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-// Package blobpool implements the EIP-4844 blob transaction pool.
-package blobpool
-
-import (
- "container/heap"
- "fmt"
- "math"
- "math/big"
- "os"
- "path/filepath"
- "sort"
- "sync"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus/misc"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto/kzg4844"
- "github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/holiman/billy"
- "github.com/holiman/uint256"
-)
-
-const (
- // blobSize is the protocol constrained byte size of a single blob in a
- // transaction. There can be multiple of these embedded into a single tx.
- blobSize = params.BlobTxFieldElementsPerBlob * params.BlobTxBytesPerFieldElement
-
- // maxBlobsPerTransaction is the maximum number of blobs a single transaction
- // is allowed to contain. Whilst the spec states it's unlimited, the block
- // data slots are protocol bound, which implicitly also limit this.
- maxBlobsPerTransaction = params.BlobTxMaxDataGasPerBlock / params.BlobTxDataGasPerBlob
-
- // txAvgSize is an approximate byte size of a transaction metadata to avoid
- // tiny overflows causing all txs to move a shelf higher, wasting disk space.
- txAvgSize = 4 * 1024
-
- // txMaxSize is the maximum size a single transaction can have, outside
- // the included blobs. Since blob transactions are pulled instead of pushed,
- // and only a small metadata is kept in ram, the rest is on disk, there is
- // no critical limit that should be enforced. Still, capping it to some sane
- // limit can never hurt.
- txMaxSize = 1024 * 1024
-
- // maxTxsPerAccount is the maximum number of blob transactions admitted from
- // a single account. The limit is enforced to minimize the DoS potential of
- // a private tx cancelling publicly propagated blobs.
- //
- // Note, transactions resurrected by a reorg are also subject to this limit,
- // so pushing it down too aggressively might make resurrections non-functional.
- maxTxsPerAccount = 16
-
- // pendingTransactionStore is the subfolder containing the currently queued
- // blob transactions.
- pendingTransactionStore = "queue"
-
- // limboedTransactionStore is the subfolder containing the currently included
- // but not yet finalized transaction blobs.
- limboedTransactionStore = "limbo"
-)
-
-// blobTx is a wrapper around types.BlobTx which also contains the literal blob
-// data along with all the transaction metadata.
-type blobTx struct {
- Tx *types.Transaction
-
- Blobs []kzg4844.Blob
- Commits []kzg4844.Commitment
- Proofs []kzg4844.Proof
-}
-
-// blobTxMeta is the minimal subset of types.BlobTx necessary to validate and
-// schedule the blob transactions into the following blocks. Only ever add the
-// bare minimum needed fields to keep the size down (and thus number of entries
-// larger with the same memory consumption).
-type blobTxMeta struct {
- hash common.Hash // Transaction hash to maintain the lookup table
- id uint64 // Storage ID in the pool's persistent store
- size uint32 // Byte size in the pool's persistent store
-
- nonce uint64 // Needed to prioritize inclusion order within an account
- costCap *uint256.Int // Needed to validate cumulative balance sufficiency
- execTipCap *uint256.Int // Needed to prioritize inclusion order across accounts and validate replacement price bump
- execFeeCap *uint256.Int // Needed to validate replacement price bump
- blobFeeCap *uint256.Int // Needed to validate replacement price bump
-
- basefeeJumps float64 // Absolute number of 1559 fee adjustments needed to reach the tx's fee cap
- blobfeeJumps float64 // Absolute number of 4844 fee adjustments needed to reach the tx's blob fee cap
-
- evictionExecTip *uint256.Int // Worst gas tip across all previous nonces
- evictionExecFeeJumps float64 // Worst base fee (converted to fee jumps) across all previous nonces
- evictionBlobFeeJumps float64 // Worse blob fee (converted to fee jumps) across all previous nonces
-}
-
-// newBlobTxMeta retrieves the indexed metadata fields from a blob transaction
-// and assembles a helper struct to track in memory.
-func newBlobTxMeta(id uint64, size uint32, tx *types.Transaction) *blobTxMeta {
- meta := &blobTxMeta{
- hash: tx.Hash(),
- id: id,
- size: size,
- nonce: tx.Nonce(),
- costCap: uint256.MustFromBig(tx.Cost()),
- execTipCap: uint256.MustFromBig(tx.GasTipCap()),
- execFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
- blobFeeCap: uint256.MustFromBig(tx.BlobGasFeeCap()),
- }
- meta.basefeeJumps = dynamicFeeJumps(meta.execFeeCap)
- meta.blobfeeJumps = dynamicFeeJumps(meta.blobFeeCap)
-
- return meta
-}
-
-// BlobPool is the transaction pool dedicated to EIP-4844 blob transactions.
-//
-// Blob transactions are special snowflakes that are designed for a very specific
-// purpose (rollups) and are expected to adhere to that specific use case. These
-// behavioural expectations allow us to design a transaction pool that is more robust
-// (i.e. resending issues) and more resilient to DoS attacks (e.g. replace-flush
-// attacks) than the generic tx pool. These improvements will also mean, however,
-// that we enforce a significantly more aggressive strategy on entering and exiting
-// the pool:
-//
-// - Blob transactions are large. With the initial design aiming for 128KB blobs,
-// we must ensure that these only traverse the network the absolute minimum
-// number of times. Broadcasting to sqrt(peers) is out of the question, rather
-// these should only ever be announced and the remote side should request it if
-// it wants to.
-//
-// - Block blob-space is limited. With blocks being capped to a few blob txs, we
-// can make use of the very low expected churn rate within the pool. Notably,
-// we should be able to use a persistent disk backend for the pool, solving
-// the tx resend issue that plagues the generic tx pool, as long as there's no
-// artificial churn (i.e. pool wars).
-//
-// - Purpose of blobs are layer-2s. Layer-2s are meant to use blob transactions to
-// commit to their own current state, which is independent of Ethereum mainnet
-// (state, txs). This means that there's no reason for blob tx cancellation or
-// replacement, apart from a potential basefee / miner tip adjustment.
-//
-// - Replacements are expensive. Given their size, propagating a replacement
-// blob transaction to an existing one should be aggressively discouraged.
-// Whilst generic transactions can start at 1 Wei gas cost and require a 10%
-// fee bump to replace, we suggest requiring a higher min cost (e.g. 1 gwei)
-// and a more aggressive bump (100%).
-//
-// - Cancellation is prohibitive. Evicting an already propagated blob tx is a huge
-// DoS vector. As such, a) replacement (higher-fee) blob txs mustn't invalidate
-// already propagated (future) blob txs (cumulative fee); b) nonce-gapped blob
-// txs are disallowed; c) the presence of blob transactions exclude non-blob
-// transactions.
-//
-// - Malicious cancellations are possible. Although the pool might prevent txs
-// that cancel blobs, blocks might contain such transaction (malicious miner
-// or flashbotter). The pool should cap the total number of blob transactions
-// per account as to prevent propagating too much data before cancelling it
-// via a normal transaction. It should nonetheless be high enough to support
-// resurrecting reorged transactions. Perhaps 4-16.
-//
-// - Local txs are meaningless. Mining pools historically used local transactions
-// for payouts or for backdoor deals. With 1559 in place, the basefee usually
-// dominates the final price, so 0 or non-0 tip doesn't change much. Blob txs
-// retain the 1559 2D gas pricing (and introduce on top a dynamic data gas fee),
-// so locality is moot. With a disk backed blob pool avoiding the resend issue,
-// there's also no need to save own transactions for later.
-//
-// - No-blob blob-txs are bad. Theoretically there's no strong reason to disallow
-// blob txs containing 0 blobs. In practice, admitting such txs into the pool
-// breaks the low-churn invariant as blob constraints don't apply anymore. Even
-// though we could accept blocks containing such txs, a reorg would require moving
-// them back into the blob pool, which can break invariants.
-//
-// - Dropping blobs needs delay. When normal transactions are included, they
-// are immediately evicted from the pool since they are contained in the
-// including block. Blobs however are not included in the execution chain,
-// so a mini reorg cannot re-pool "lost" blob transactions. To support reorgs,
-// blobs are retained on disk until they are finalised.
-//
-// - Blobs can arrive via flashbots. Blocks might contain blob transactions we
-// have never seen on the network. Since we cannot recover them from blocks
-// either, the engine_newPayload needs to give them to us, and we cache them
-// until finality to support reorgs without tx losses.
-//
-// Whilst some constraints above might sound overly aggressive, the general idea is
-// that the blob pool should work robustly for its intended use case and whilst
-// anyone is free to use blob transactions for arbitrary non-rollup use cases,
-// they should not be allowed to run amok the network.
-//
-// Implementation wise there are a few interesting design choices:
-//
-// - Adding a transaction to the pool blocks until persisted to disk. This is
-// viable because TPS is low (2-4 blobs per block initially, maybe 8-16 at
-// peak), so natural churn is a couple MB per block. Replacements doing O(n)
-// updates are forbidden and transaction propagation is pull based (i.e. no
-// pileup of pending data).
-//
-// - When transactions are chosen for inclusion, the primary criteria is the
-// signer tip (and having a basefee/data fee high enough of course). However,
-// same-tip transactions will be split by their basefee/datafee, preferring
-// those that are closer to the current network limits. The idea being that
-// very relaxed ones can be included even if the fees go up, when the closer
-// ones could already be invalid.
-//
-// When the pool eventually reaches saturation, some old transactions - that may
-// never execute - will need to be evicted in favor of newer ones. The eviction
-// strategy is quite complex:
-//
-// - Exceeding capacity evicts the highest-nonce of the account with the lowest
-// paying blob transaction anywhere in the pooled nonce-sequence, as that tx
-// would be executed the furthest in the future and is thus blocking anything
-// after it. The smallest is deliberately not evicted to avoid a nonce-gap.
-//
-// - Analogously, if the pool is full, the consideration price of a new tx for
-// evicting an old one is the smallest price in the entire nonce-sequence of
-// the account. This avoids malicious users DoSing the pool with seemingly
-// high paying transactions hidden behind a low-paying blocked one.
-//
-// - Since blob transactions have 3 price parameters: execution tip, execution
-// fee cap and data fee cap, there's no singular parameter to create a total
-// price ordering on. What's more, since the base fee and blob fee can move
-// independently of one another, there's no pre-defined way to combine them
-// into a stable order either. This leads to a multi-dimensional problem to
-// solve after every block.
-//
-// - The first observation is that comparing 1559 base fees or 4844 blob fees
-// needs to happen in the context of their dynamism. Since these fees jump
-// up or down in ~1.125 multipliers (at max) across blocks, comparing fees
-// in two transactions should be based on log1.125(fee) to eliminate noise.
-//
-// - The second observation is that the basefee and blobfee move independently,
-// so there's no way to split mixed txs on their own (A has higher base fee,
-// B has higher blob fee). Rather than look at the absolute fees, the useful
-// metric is the max time it can take to exceed the transaction's fee caps.
-// Specifically, we're interested in the number of jumps needed to go from
-// the current fee to the transaction's cap:
-//
-// jumps = log1.125(txfee) - log1.125(basefee)
-//
-// - The third observation is that the base fee tends to hover around rather
-// than swing wildly. The number of jumps needed from the current fee starts
-// to get less relevant the higher it is. To remove the noise here too, the
-// pool will use log(jumps) as the delta for comparing transactions.
-//
-// delta = sign(jumps) * log(abs(jumps))
-//
-// - To establish a total order, we need to reduce the dimensionality of the
-// two base fees (log jumps) to a single value. The interesting aspect from
-// the pool's perspective is how fast will a tx get executable (fees going
-// down, crossing the smaller negative jump counter) or non-executable (fees
-// going up, crossing the smaller positive jump counter). As such, the pool
-// cares only about the min of the two delta values for eviction priority.
-//
-// priority = min(delta-basefee, delta-blobfee)
-//
-// - The above very aggressive dimensionality and noise reduction should result
-// in transaction being grouped into a small number of buckets, the further
-// the fees the larger the buckets. This is good because it allows us to use
-// the miner tip meaningfully as a splitter.
-//
-// - For the scenario where the pool does not contain non-executable blob txs
-// anymore, it does not make sense to grant a later eviction priority to txs
-// with high fee caps since it could enable pool wars. As such, any positive
-// priority will be grouped together.
-//
-// priority = min(delta-basefee, delta-blobfee, 0)
-//
-// Optimisation tradeoffs:
-//
-// - Eviction relies on 3 fee minimums per account (exec tip, exec cap and blob
-// cap). Maintaining these values across all transactions from the account is
-// problematic as each transaction replacement or inclusion would require a
-// rescan of all other transactions to recalculate the minimum. Instead, the
-// pool maintains a rolling minimum across the nonce range. Updating all the
-// minimums will need to be done only starting at the swapped in/out nonce
-// and leading up to the first no-change.
-type BlobPool struct {
- config Config // Pool configuration
- reserve txpool.AddressReserver // Address reserver to ensure exclusivity across subpools
-
- store billy.Database // Persistent data store for the tx metadata and blobs
- stored uint64 // Useful data size of all transactions on disk
- limbo *limbo // Persistent data store for the non-finalized blobs
-
- signer types.Signer // Transaction signer to use for sender recovery
- chain BlockChain // Chain object to access the state through
-
- head *types.Header // Current head of the chain
- state *state.StateDB // Current state at the head of the chain
- gasTip *uint256.Int // Currently accepted minimum gas tip
-
- lookup map[common.Hash]uint64 // Lookup table mapping hashes to tx billy entries
- index map[common.Address][]*blobTxMeta // Blob transactions grouped by accounts, sorted by nonce
- spent map[common.Address]*uint256.Int // Expenditure tracking for individual accounts
- evict *evictHeap // Heap of cheapest accounts for eviction when full
-
- eventFeed event.Feed // Event feed to send out new tx events on pool inclusion
- eventScope event.SubscriptionScope // Event scope to track and mass unsubscribe on termination
-
- lock sync.RWMutex // Mutex protecting the pool during reorg handling
-}
-
-// New creates a new blob transaction pool to gather, sort and filter inbound
-// blob transactions from the network.
-func New(config Config, chain BlockChain) *BlobPool {
- // Sanitize the input to ensure no vulnerable gas prices are set
- config = (&config).sanitize()
-
- // Create the transaction pool with its initial settings
- return &BlobPool{
- config: config,
- signer: types.LatestSigner(chain.Config()),
- chain: chain,
- lookup: make(map[common.Hash]uint64),
- index: make(map[common.Address][]*blobTxMeta),
- spent: make(map[common.Address]*uint256.Int),
- }
-}
-
-// Filter returns whether the given transaction can be consumed by the blob pool.
-func (p *BlobPool) Filter(tx *types.Transaction) bool {
- return tx.Type() == types.BlobTxType
-}
-
-// Init sets the gas price needed to keep a transaction in the pool and the chain
-// head to allow balance / nonce checks. The transaction journal will be loaded
-// from disk and filtered based on the provided starting settings.
-func (p *BlobPool) Init(gasTip *big.Int, head *types.Header, reserve txpool.AddressReserver) error {
- p.reserve = reserve
-
- var (
- queuedir string
- limbodir string
- )
- if p.config.Datadir != "" {
- queuedir = filepath.Join(p.config.Datadir, pendingTransactionStore)
- if err := os.MkdirAll(queuedir, 0700); err != nil {
- return err
- }
- limbodir = filepath.Join(p.config.Datadir, limboedTransactionStore)
- if err := os.MkdirAll(limbodir, 0700); err != nil {
- return err
- }
- }
- state, err := p.chain.StateAt(head.Root)
- if err != nil {
- return err
- }
- p.head, p.state = head, state
-
- // Index all transactions on disk and delete anything inprocessable
- var fails []uint64
- index := func(id uint64, size uint32, blob []byte) {
- if p.parseTransaction(id, size, blob) != nil {
- fails = append(fails, id)
- }
- }
- store, err := billy.Open(billy.Options{Path: queuedir}, newSlotter(), index)
- if err != nil {
- return err
- }
- p.store = store
-
- if len(fails) > 0 {
- log.Warn("Dropping invalidated blob transactions", "ids", fails)
- for _, id := range fails {
- if err := p.store.Delete(id); err != nil {
- p.Close()
- return err
- }
- }
- }
- // Sort the indexed transactions by nonce and delete anything gapped, create
- // the eviction heap of anyone still standing
- for addr := range p.index {
- p.recheck(addr, nil)
- }
- var (
- basefee = uint256.MustFromBig(misc.CalcBaseFee(p.chain.Config(), p.head))
- blobfee = uint256.MustFromBig(big.NewInt(params.BlobTxMinDataGasprice))
- )
- if p.head.ExcessDataGas != nil {
- blobfee = uint256.MustFromBig(misc.CalcBlobFee(*p.head.ExcessDataGas))
- }
- p.evict = newPriceHeap(basefee, blobfee, &p.index)
-
- // Pool initialized, attach the blob limbo to it to track blobs included
- // recently but not yet finalized
- p.limbo, err = newLimbo(limbodir)
- if err != nil {
- p.Close()
- return err
- }
- // Set the configured gas tip, triggering a filtering of anything just loaded
- basefeeGauge.Update(int64(basefee.Uint64()))
- blobfeeGauge.Update(int64(blobfee.Uint64()))
-
- p.SetGasTip(gasTip)
-
- // Since the user might have modified their pool's capacity, evict anything
- // above the current allowance
- for p.stored > p.config.Datacap {
- p.drop()
- }
- // Update the metrics and return the constructed pool
- datacapGauge.Update(int64(p.config.Datacap))
- p.updateStorageMetrics()
- return nil
-}
-
-// Close closes down the underlying persistent store.
-func (p *BlobPool) Close() error {
- var errs []error
- if err := p.limbo.Close(); err != nil {
- errs = append(errs, err)
- }
- if err := p.store.Close(); err != nil {
- errs = append(errs, err)
- }
- p.eventScope.Close()
-
- switch {
- case errs == nil:
- return nil
- case len(errs) == 1:
- return errs[0]
- default:
- return fmt.Errorf("%v", errs)
- }
-}
-
-// parseTransaction is a callback method on pool creation that gets called for
-// each transaction on disk to create the in-memory metadata index.
-func (p *BlobPool) parseTransaction(id uint64, size uint32, blob []byte) error {
- item := new(blobTx)
- if err := rlp.DecodeBytes(blob, item); err != nil {
- // This path is impossible unless the disk data representation changes
- // across restarts. For that ever unprobable case, recover gracefully
- // by ignoring this data entry.
- log.Error("Failed to decode blob pool entry", "id", id, "err", err)
- return err
- }
- meta := newBlobTxMeta(id, size, item.Tx)
-
- sender, err := p.signer.Sender(item.Tx)
- if err != nil {
- // This path is impossible unless the signature validity changes across
- // restarts. For that ever unprobable case, recover gracefully by ignoring
- // this data entry.
- log.Error("Failed to recover blob tx sender", "id", id, "hash", item.Tx.Hash(), "err", err)
- return err
- }
- if _, ok := p.index[sender]; !ok {
- if err := p.reserve(sender, true); err != nil {
- return err
- }
- p.index[sender] = []*blobTxMeta{}
- p.spent[sender] = new(uint256.Int)
- }
- p.index[sender] = append(p.index[sender], meta)
- p.spent[sender] = new(uint256.Int).Add(p.spent[sender], meta.costCap)
-
- p.lookup[meta.hash] = meta.id
- p.stored += uint64(meta.size)
-
- return nil
-}
-
-// recheck verifies the pool's content for a specific account and drops anything
-// that does not fit anymore (dangling or filled nonce, overdraft).
-func (p *BlobPool) recheck(addr common.Address, inclusions map[common.Hash]uint64) {
- // Sort the transactions belonging to the account so reinjects can be simpler
- txs := p.index[addr]
- if inclusions != nil && txs == nil { // during reorgs, we might find new accounts
- return
- }
- sort.Slice(txs, func(i, j int) bool {
- return txs[i].nonce < txs[j].nonce
- })
- // If there is a gap between the chain state and the blob pool, drop
- // all the transactions as they are non-executable. Similarly, if the
- // entire tx range was included, drop all.
- var (
- next = p.state.GetNonce(addr)
- gapped = txs[0].nonce > next
- filled = txs[len(txs)-1].nonce < next
- )
- if gapped || filled {
- var (
- ids []uint64
- nonces []uint64
- )
- for i := 0; i < len(txs); i++ {
- ids = append(ids, txs[i].id)
- nonces = append(nonces, txs[i].nonce)
-
- p.stored -= uint64(txs[i].size)
- delete(p.lookup, txs[i].hash)
-
- // Included transactions blobs need to be moved to the limbo
- if filled && inclusions != nil {
- p.offload(addr, txs[i].nonce, txs[i].id, inclusions)
- }
- }
- delete(p.index, addr)
- delete(p.spent, addr)
- if inclusions != nil { // only during reorgs will the heap will be initialized
- heap.Remove(p.evict, p.evict.index[addr])
- }
- p.reserve(addr, false)
-
- if gapped {
- log.Warn("Dropping dangling blob transactions", "from", addr, "missing", next, "drop", nonces, "ids", ids)
- } else {
- log.Trace("Dropping filled blob transactions", "from", addr, "filled", nonces, "ids", ids)
- }
- for _, id := range ids {
- if err := p.store.Delete(id); err != nil {
- log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err)
- }
- }
- return
- }
- // If there is overlap between the chain state and the blob pool, drop
- // anything below the current state
- if txs[0].nonce < next {
- var (
- ids []uint64
- nonces []uint64
- )
- for txs[0].nonce < next {
- ids = append(ids, txs[0].id)
- nonces = append(nonces, txs[0].nonce)
-
- p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[0].costCap)
- p.stored -= uint64(txs[0].size)
- delete(p.lookup, txs[0].hash)
-
- // Included transactions blobs need to be moved to the limbo
- if inclusions != nil {
- p.offload(addr, txs[0].nonce, txs[0].id, inclusions)
- }
- txs = txs[1:]
- }
- log.Trace("Dropping overlapped blob transactions", "from", addr, "overlapped", nonces, "ids", ids, "left", len(txs))
- for _, id := range ids {
- if err := p.store.Delete(id); err != nil {
- log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err)
- }
- }
- p.index[addr] = txs
- }
- // Iterate over the transactions to initialize their eviction thresholds
- // and to detect any nonce gaps
- txs[0].evictionExecTip = txs[0].execTipCap
- txs[0].evictionExecFeeJumps = txs[0].basefeeJumps
- txs[0].evictionBlobFeeJumps = txs[0].blobfeeJumps
-
- for i := 1; i < len(txs); i++ {
- // If there's no nonce gap, initialize the evicion thresholds as the
- // minimum between the cumulative thresholds and the current tx fees
- if txs[i].nonce == txs[i-1].nonce+1 {
- txs[i].evictionExecTip = txs[i-1].evictionExecTip
- if txs[i].evictionExecTip.Cmp(txs[i].execTipCap) > 0 {
- txs[i].evictionExecTip = txs[i].execTipCap
- }
- txs[i].evictionExecFeeJumps = txs[i-1].evictionExecFeeJumps
- if txs[i].evictionExecFeeJumps > txs[i].basefeeJumps {
- txs[i].evictionExecFeeJumps = txs[i].basefeeJumps
- }
- txs[i].evictionBlobFeeJumps = txs[i-1].evictionBlobFeeJumps
- if txs[i].evictionBlobFeeJumps > txs[i].blobfeeJumps {
- txs[i].evictionBlobFeeJumps = txs[i].blobfeeJumps
- }
- continue
- }
- // Sanity check that there's no double nonce. This case would be a coding
- // error, but better know about it
- if txs[i].nonce == txs[i-1].nonce {
- log.Error("Duplicate nonce blob transaction", "from", addr, "nonce", txs[i].nonce)
- }
- // Otherwise if there's a nonce gap evict all later transactions
- var (
- ids []uint64
- nonces []uint64
- )
- for j := i; j < len(txs); j++ {
- ids = append(ids, txs[j].id)
- nonces = append(nonces, txs[j].nonce)
-
- p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[j].costCap)
- p.stored -= uint64(txs[j].size)
- delete(p.lookup, txs[j].hash)
- }
- txs = txs[:i]
-
- log.Error("Dropping gapped blob transactions", "from", addr, "missing", txs[i-1].nonce+1, "drop", nonces, "ids", ids)
- for _, id := range ids {
- if err := p.store.Delete(id); err != nil {
- log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err)
- }
- }
- p.index[addr] = txs
- break
- }
- // Ensure that there's no over-draft, this is expected to happen when some
- // transactions get included without publishing on the network
- var (
- balance = uint256.MustFromBig(p.state.GetBalance(addr))
- spent = p.spent[addr]
- )
- if spent.Cmp(balance) > 0 {
- // Evict the highest nonce transactions until the pending set falls under
- // the account's available balance
- var (
- ids []uint64
- nonces []uint64
- )
- for p.spent[addr].Cmp(balance) > 0 {
- last := txs[len(txs)-1]
- txs[len(txs)-1] = nil
- txs = txs[:len(txs)-1]
-
- ids = append(ids, last.id)
- nonces = append(nonces, last.nonce)
-
- p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
- p.stored -= uint64(last.size)
- delete(p.lookup, last.hash)
- }
- if len(txs) == 0 {
- delete(p.index, addr)
- delete(p.spent, addr)
- if inclusions != nil { // only during reorgs will the heap will be initialized
- heap.Remove(p.evict, p.evict.index[addr])
- }
- p.reserve(addr, false)
- } else {
- p.index[addr] = txs
- }
- log.Warn("Dropping overdrafted blob transactions", "from", addr, "balance", balance, "spent", spent, "drop", nonces, "ids", ids)
- for _, id := range ids {
- if err := p.store.Delete(id); err != nil {
- log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err)
- }
- }
- }
- // Sanity check that no account can have more queued transactions than the
- // DoS protection threshold.
- if len(txs) > maxTxsPerAccount {
- // Evict the highest nonce transactions until the pending set falls under
- // the account's transaction cap
- var (
- ids []uint64
- nonces []uint64
- )
- for len(txs) > maxTxsPerAccount {
- last := txs[len(txs)-1]
- txs[len(txs)-1] = nil
- txs = txs[:len(txs)-1]
-
- ids = append(ids, last.id)
- nonces = append(nonces, last.nonce)
-
- p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], last.costCap)
- p.stored -= uint64(last.size)
- delete(p.lookup, last.hash)
- }
- p.index[addr] = txs
-
- log.Warn("Dropping overcapped blob transactions", "from", addr, "kept", len(txs), "drop", nonces, "ids", ids)
- for _, id := range ids {
- if err := p.store.Delete(id); err != nil {
- log.Error("Failed to delete blob transaction", "from", addr, "id", id, "err", err)
- }
- }
- }
- // Included cheap transactions might have left the remaining ones better from
- // an eviction point, fix any potential issues in the heap.
- if _, ok := p.index[addr]; ok && inclusions != nil {
- heap.Fix(p.evict, p.evict.index[addr])
- }
-}
-
-// offload removes a tracked blob transaction from the pool and moves it into the
-// limbo for tracking until finality.
-//
-// The method may log errors for various unexpcted scenarios but will not return
-// any of it since there's no clear error case. Some errors may be due to coding
-// issues, others caused by signers mining MEV stuff or swapping transactions. In
-// all cases, the pool needs to continue operating.
-func (p *BlobPool) offload(addr common.Address, nonce uint64, id uint64, inclusions map[common.Hash]uint64) {
- data, err := p.store.Get(id)
- if err != nil {
- log.Error("Blobs missing for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
- return
- }
- item := new(blobTx)
- if err = rlp.DecodeBytes(data, item); err != nil {
- log.Error("Blobs corrupted for included transaction", "from", addr, "nonce", nonce, "id", id, "err", err)
- return
- }
- block, ok := inclusions[item.Tx.Hash()]
- if !ok {
- log.Warn("Blob transaction swapped out by signer", "from", addr, "nonce", nonce, "id", id)
- return
- }
- if err := p.limbo.push(item.Tx.Hash(), block, item.Blobs, item.Commits, item.Proofs); err != nil {
- log.Warn("Failed to offload blob tx into limbo", "err", err)
- return
- }
-}
-
-// Reset implements txpool.SubPool, allowing the blob pool's internal state to be
-// kept in sync with the main transacion pool's internal state.
-func (p *BlobPool) Reset(oldHead, newHead *types.Header) {
- waitStart := time.Now()
- p.lock.Lock()
- resetwaitHist.Update(time.Since(waitStart).Nanoseconds())
- defer p.lock.Unlock()
-
- defer func(start time.Time) {
- resettimeHist.Update(time.Since(start).Nanoseconds())
- }(time.Now())
-
- statedb, err := p.chain.StateAt(newHead.Root)
- if err != nil {
- log.Error("Failed to reset blobpool state", "err", err)
- return
- }
- p.head = newHead
- p.state = statedb
-
- // Run the reorg between the old and new head and figure out which accounts
- // need to be rechecked and which transactions need to be readded
- if reinject, inclusions := p.reorg(oldHead, newHead); reinject != nil {
- for addr, txs := range reinject {
- // Blindly push all the lost transactions back into the pool
- for _, tx := range txs {
- p.reinject(addr, tx)
- }
- // Recheck the account's pooled transactions to drop included and
- // invalidated one
- p.recheck(addr, inclusions)
- }
- }
- // Flush out any blobs from limbo that are older than the latest finality
- p.limbo.finalize(p.chain.CurrentFinalBlock())
-
- // Reset the price heap for the new set of basefee/blobfee pairs
- var (
- basefee = uint256.MustFromBig(misc.CalcBaseFee(p.chain.Config(), newHead))
- blobfee = uint256.MustFromBig(big.NewInt(params.BlobTxMinDataGasprice))
- )
- if newHead.ExcessDataGas != nil {
- blobfee = uint256.MustFromBig(misc.CalcBlobFee(*newHead.ExcessDataGas))
- }
- p.evict.reinit(basefee, blobfee, false)
-
- basefeeGauge.Update(int64(basefee.Uint64()))
- blobfeeGauge.Update(int64(blobfee.Uint64()))
- p.updateStorageMetrics()
-}
-
-// reorg assembles all the transactors and missing transactions between an old
-// and new head to figure out which account's tx set needs to be rechecked and
-// which transactions need to be requeued.
-//
-// The transactionblock inclusion infos are also returned to allow tracking any
-// just-included blocks by block number in the limbo.
-func (p *BlobPool) reorg(oldHead, newHead *types.Header) (map[common.Address][]*types.Transaction, map[common.Hash]uint64) {
- // If the pool was not yet initialized, don't do anything
- if oldHead == nil {
- return nil, nil
- }
- // If the reorg is too deep, avoid doing it (will happen during snap sync)
- oldNum := oldHead.Number.Uint64()
- newNum := newHead.Number.Uint64()
-
- if depth := uint64(math.Abs(float64(oldNum) - float64(newNum))); depth > 64 {
- return nil, nil
- }
- // Reorg seems shallow enough to pull in all transactions into memory
- var (
- transactors = make(map[common.Address]struct{})
- discarded = make(map[common.Address][]*types.Transaction)
- included = make(map[common.Address][]*types.Transaction)
- inclusions = make(map[common.Hash]uint64)
-
- rem = p.chain.GetBlock(oldHead.Hash(), oldHead.Number.Uint64())
- add = p.chain.GetBlock(newHead.Hash(), newHead.Number.Uint64())
- )
- if add == nil {
- // if the new head is nil, it means that something happened between
- // the firing of newhead-event and _now_: most likely a
- // reorg caused by sync-reversion or explicit sethead back to an
- // earlier block.
- log.Warn("Blobpool reset with missing new head", "number", newHead.Number, "hash", newHead.Hash())
- return nil, nil
- }
- if rem == nil {
- // This can happen if a setHead is performed, where we simply discard
- // the old head from the chain. If that is the case, we don't have the
- // lost transactions anymore, and there's nothing to add.
- if newNum >= oldNum {
- // If we reorged to a same or higher number, then it's not a case
- // of setHead
- log.Warn("Blobpool reset with missing old head",
- "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
- return nil, nil
- }
- // If the reorg ended up on a lower number, it's indicative of setHead
- // being the cause
- log.Debug("Skipping blobpool reset caused by setHead",
- "old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
- return nil, nil
- }
- // Both old and new blocks exist, traverse through the progression chain
- // and accumulate the transactors and transactions
- for rem.NumberU64() > add.NumberU64() {
- for _, tx := range rem.Transactions() {
- from, _ := p.signer.Sender(tx)
-
- discarded[from] = append(discarded[from], tx)
- transactors[from] = struct{}{}
- }
- if rem = p.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
- log.Error("Unrooted old chain seen by blobpool", "block", oldHead.Number, "hash", oldHead.Hash())
- return nil, nil
- }
- }
- for add.NumberU64() > rem.NumberU64() {
- for _, tx := range add.Transactions() {
- from, _ := p.signer.Sender(tx)
-
- included[from] = append(included[from], tx)
- inclusions[tx.Hash()] = add.NumberU64()
- transactors[from] = struct{}{}
- }
- if add = p.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
- log.Error("Unrooted new chain seen by blobpool", "block", newHead.Number, "hash", newHead.Hash())
- return nil, nil
- }
- }
- for rem.Hash() != add.Hash() {
- for _, tx := range rem.Transactions() {
- from, _ := p.signer.Sender(tx)
-
- discarded[from] = append(discarded[from], tx)
- transactors[from] = struct{}{}
- }
- if rem = p.chain.GetBlock(rem.ParentHash(), rem.NumberU64()-1); rem == nil {
- log.Error("Unrooted old chain seen by blobpool", "block", oldHead.Number, "hash", oldHead.Hash())
- return nil, nil
- }
- for _, tx := range add.Transactions() {
- from, _ := p.signer.Sender(tx)
-
- included[from] = append(included[from], tx)
- inclusions[tx.Hash()] = add.NumberU64()
- transactors[from] = struct{}{}
- }
- if add = p.chain.GetBlock(add.ParentHash(), add.NumberU64()-1); add == nil {
- log.Error("Unrooted new chain seen by blobpool", "block", newHead.Number, "hash", newHead.Hash())
- return nil, nil
- }
- }
- // Generate the set of transactions per address to pull back into the pool,
- // also updating the rest along the way
- reinject := make(map[common.Address][]*types.Transaction)
- for addr := range transactors {
- // Generate the set that was lost to reinject into the pool
- lost := make([]*types.Transaction, 0, len(discarded[addr]))
- for _, tx := range types.TxDifference(discarded[addr], included[addr]) {
- if p.Filter(tx) {
- lost = append(lost, tx)
- }
- }
- reinject[addr] = lost
-
- // Update the set that was already reincluded to track the blocks in limbo
- for _, tx := range types.TxDifference(included[addr], discarded[addr]) {
- if p.Filter(tx) {
- p.limbo.update(tx.Hash(), inclusions[tx.Hash()])
- }
- }
- }
- return reinject, inclusions
-}
-
-// reinject blindly pushes a transaction previously included in the chain - and
-// just reorged out - into the pool. The transaction is assumed valid (having
-// been in the chain), thus the only validation needed is nonce sorting and over-
-// draft checks after injection.
-//
-// Note, the method will not initialize the eviction cache values as those will
-// be done once for all transactions belonging to an account after all individual
-// transactions are injected back into the pool.
-func (p *BlobPool) reinject(addr common.Address, tx *types.Transaction) {
- // Retrieve the associated blob from the limbo. Without the blobs, we cannot
- // add the transaction back into the pool as it is not mineable.
- blobs, commits, proofs, err := p.limbo.pull(tx.Hash())
- if err != nil {
- log.Error("Blobs unavailable, dropping reorged tx", "err", err)
- return
- }
- // Serialize the transaction back into the primary datastore
- blob, err := rlp.EncodeToBytes(&blobTx{Tx: tx, Blobs: blobs, Commits: commits, Proofs: proofs})
- if err != nil {
- log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
- return
- }
- id, err := p.store.Put(blob)
- if err != nil {
- log.Error("Failed to write transaction into storage", "hash", tx.Hash(), "err", err)
- return
- }
- // Update the indixes and metrics
- meta := newBlobTxMeta(id, p.store.Size(id), tx)
-
- if _, ok := p.index[addr]; !ok {
- if err := p.reserve(addr, true); err != nil {
- log.Warn("Failed to reserve account for blob pool", "tx", tx.Hash(), "from", addr, "err", err)
- return
- }
- p.index[addr] = []*blobTxMeta{meta}
- p.spent[addr] = meta.costCap
- p.evict.Push(addr)
- } else {
- p.index[addr] = append(p.index[addr], meta)
- p.spent[addr] = new(uint256.Int).Add(p.spent[addr], meta.costCap)
- }
- p.lookup[meta.hash] = meta.id
- p.stored += uint64(meta.size)
-}
-
-// SetGasTip implements txpool.SubPool, allowing the blob pool's gas requirements
-// to be kept in sync with the main transacion pool's gas requirements.
-func (p *BlobPool) SetGasTip(tip *big.Int) {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- // Store the new minimum gas tip
- old := p.gasTip
- p.gasTip = uint256.MustFromBig(tip)
-
- // If the min miner fee increased, remove transactions below the new threshold
- if old == nil || p.gasTip.Cmp(old) > 0 {
- for addr, txs := range p.index {
- for i, tx := range txs {
- if tx.execTipCap.Cmp(p.gasTip) < 0 {
- // Drop the offending transaction
- var (
- ids = []uint64{tx.id}
- nonces = []uint64{tx.nonce}
- )
- p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], txs[i].costCap)
- p.stored -= uint64(tx.size)
- delete(p.lookup, tx.hash)
- txs[i] = nil
-
- // Drop everything afterwards, no gaps allowed
- for j, tx := range txs[i+1:] {
- ids = append(ids, tx.id)
- nonces = append(nonces, tx.nonce)
-
- p.spent[addr] = new(uint256.Int).Sub(p.spent[addr], tx.costCap)
- p.stored -= uint64(tx.size)
- delete(p.lookup, tx.hash)
- txs[i+1+j] = nil
- }
- // Clear out the dropped transactions from the index
- if i > 0 {
- p.index[addr] = txs[:i]
- heap.Fix(p.evict, p.evict.index[addr])
- } else {
- delete(p.index, addr)
- delete(p.spent, addr)
-
- heap.Remove(p.evict, p.evict.index[addr])
- p.reserve(addr, false)
- }
- // Clear out the transactions from the data store
- log.Warn("Dropping underpriced blob transaction", "from", addr, "rejected", tx.nonce, "tip", tx.execTipCap, "want", tip, "drop", nonces, "ids", ids)
- for _, id := range ids {
- if err := p.store.Delete(id); err != nil {
- log.Error("Failed to delete dropped transaction", "id", id, "err", err)
- }
- }
- break
- }
- }
- }
- }
- log.Debug("Blobpool tip threshold updated", "tip", tip)
- pooltipGauge.Update(tip.Int64())
- p.updateStorageMetrics()
-}
-
-// validateTx checks whether a transaction is valid according to the consensus
-// rules and adheres to some heuristic limits of the local node (price and size).
-func (p *BlobPool) validateTx(tx *types.Transaction, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error {
- // Ensure the transaction adheres to basic pool filters (type, size, tip) and
- // consensus rules
- baseOpts := &txpool.ValidationOptions{
- Config: p.chain.Config(),
- Accept: 1 << types.BlobTxType,
- MaxSize: txMaxSize,
- MinTip: p.gasTip.ToBig(),
- }
- if err := txpool.ValidateTransaction(tx, blobs, commits, proofs, p.head, p.signer, baseOpts); err != nil {
- return err
- }
- // Ensure the transaction adheres to the stateful pool filters (nonce, balance)
- stateOpts := &txpool.ValidationOptionsWithState{
- State: p.state,
-
- FirstNonceGap: func(addr common.Address) uint64 {
- // Nonce gaps are not permitted in the blob pool, the first gap will
- // be the next nonce shifted by however many transactions we already
- // have pooled.
- return p.state.GetNonce(addr) + uint64(len(p.index[addr]))
- },
- UsedAndLeftSlots: func(addr common.Address) (int, int) {
- have := len(p.index[addr])
- if have >= maxTxsPerAccount {
- return have, 0
- }
- return have, maxTxsPerAccount - have
- },
- ExistingExpenditure: func(addr common.Address) *big.Int {
- if spent := p.spent[addr]; spent != nil {
- return spent.ToBig()
- }
- return new(big.Int)
- },
- ExistingCost: func(addr common.Address, nonce uint64) *big.Int {
- next := p.state.GetNonce(addr)
- if uint64(len(p.index[addr])) > nonce-next {
- return p.index[addr][int(tx.Nonce()-next)].costCap.ToBig()
- }
- return nil
- },
- }
- if err := txpool.ValidateTransactionWithState(tx, p.signer, stateOpts); err != nil {
- return err
- }
- // If the transaction replaces an existing one, ensure that price bumps are
- // adhered to.
- var (
- from, _ = p.signer.Sender(tx) // already validated above
- next = p.state.GetNonce(from)
- )
- if uint64(len(p.index[from])) > tx.Nonce()-next {
- // Account can support the replacement, but the price bump must also be met
- prev := p.index[from][int(tx.Nonce()-next)]
- switch {
- case tx.GasFeeCapIntCmp(prev.execFeeCap.ToBig()) <= 0:
- return fmt.Errorf("%w: new tx gas fee cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.GasFeeCap(), prev.execFeeCap)
- case tx.GasTipCapIntCmp(prev.execTipCap.ToBig()) <= 0:
- return fmt.Errorf("%w: new tx gas tip cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.GasTipCap(), prev.execTipCap)
- case tx.BlobGasFeeCapIntCmp(prev.blobFeeCap.ToBig()) <= 0:
- return fmt.Errorf("%w: new tx blob gas fee cap %v <= %v queued", txpool.ErrReplaceUnderpriced, tx.BlobGasFeeCap(), prev.blobFeeCap)
- }
- var (
- multiplier = uint256.NewInt(100 + p.config.PriceBump)
- onehundred = uint256.NewInt(100)
-
- minGasFeeCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.execFeeCap), onehundred)
- minGasTipCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.execTipCap), onehundred)
- minBlobGasFeeCap = new(uint256.Int).Div(new(uint256.Int).Mul(multiplier, prev.blobFeeCap), onehundred)
- )
- switch {
- case tx.GasFeeCapIntCmp(minGasFeeCap.ToBig()) < 0:
- return fmt.Errorf("%w: new tx gas fee cap %v <= %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.GasFeeCap(), prev.execFeeCap, p.config.PriceBump)
- case tx.GasTipCapIntCmp(minGasTipCap.ToBig()) < 0:
- return fmt.Errorf("%w: new tx gas tip cap %v <= %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.GasTipCap(), prev.execTipCap, p.config.PriceBump)
- case tx.BlobGasFeeCapIntCmp(minBlobGasFeeCap.ToBig()) < 0:
- return fmt.Errorf("%w: new tx blob gas fee cap %v <= %v queued + %d%% replacement penalty", txpool.ErrReplaceUnderpriced, tx.BlobGasFeeCap(), prev.blobFeeCap, p.config.PriceBump)
- }
- }
- return nil
-}
-
-// Has returns an indicator whether subpool has a transaction cached with the
-// given hash.
-func (p *BlobPool) Has(hash common.Hash) bool {
- p.lock.RLock()
- defer p.lock.RUnlock()
-
- _, ok := p.lookup[hash]
- return ok
-}
-
-// Get returns a transaction if it is contained in the pool, or nil otherwise.
-func (p *BlobPool) Get(hash common.Hash) *txpool.Transaction {
- // Track the amount of time waiting to retrieve a fully resolved blob tx from
- // the pool and the amount of time actually spent on pulling the data from disk.
- getStart := time.Now()
- p.lock.RLock()
- getwaitHist.Update(time.Since(getStart).Nanoseconds())
- defer p.lock.RUnlock()
-
- defer func(start time.Time) {
- gettimeHist.Update(time.Since(start).Nanoseconds())
- }(time.Now())
-
- // Pull the blob from disk and return an assembled response
- id, ok := p.lookup[hash]
- if !ok {
- return nil
- }
- data, err := p.store.Get(id)
- if err != nil {
- log.Error("Tracked blob transaction missing from store", "hash", hash, "id", id, "err", err)
- return nil
- }
- item := new(blobTx)
- if err = rlp.DecodeBytes(data, item); err != nil {
- log.Error("Blobs corrupted for traced transaction", "hash", hash, "id", id, "err", err)
- return nil
- }
- return &txpool.Transaction{
- Tx: item.Tx,
- BlobTxBlobs: item.Blobs,
- BlobTxCommits: item.Commits,
- BlobTxProofs: item.Proofs,
- }
-}
-
-// Add inserts a set of blob transactions into the pool if they pass validation (both
-// consensus validity and pool restictions).
-func (p *BlobPool) Add(txs []*txpool.Transaction, local bool, sync bool) []error {
- errs := make([]error, len(txs))
- for i, tx := range txs {
- errs[i] = p.add(tx.Tx, tx.BlobTxBlobs, tx.BlobTxCommits, tx.BlobTxProofs)
- }
- return errs
-}
-
-// Add inserts a new blob transaction into the pool if it passes validation (both
-// consensus validity and pool restictions).
-func (p *BlobPool) add(tx *types.Transaction, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) (err error) {
- // The blob pool blocks on adding a transaction. This is because blob txs are
- // only even pulled form the network, so this method will act as the overload
- // protection for fetches.
- waitStart := time.Now()
- p.lock.Lock()
- addwaitHist.Update(time.Since(waitStart).Nanoseconds())
- defer p.lock.Unlock()
-
- defer func(start time.Time) {
- addtimeHist.Update(time.Since(start).Nanoseconds())
- }(time.Now())
-
- // Ensure the transaction is valid from all perspectives
- if err := p.validateTx(tx, blobs, commits, proofs); err != nil {
- log.Trace("Transaction validation failed", "hash", tx.Hash(), "err", err)
- return err
- }
- // If the address is not yet known, request exclusivity to track the account
- // only by this subpool until all transactions are evicted
- from, _ := types.Sender(p.signer, tx) // already validated above
- if _, ok := p.index[from]; !ok {
- if err := p.reserve(from, true); err != nil {
- return err
- }
- defer func() {
- // If the transaction is rejected by some post-validation check, remove
- // the lock on the reservation set.
- //
- // Note, `err` here is the named error return, which will be initialized
- // by a return statement before running deferred methods. Take care with
- // removing or subscoping err as it will break this clause.
- if err != nil {
- p.reserve(from, false)
- }
- }()
- }
- // Transaction permitted into the pool from a nonce and cost perspective,
- // insert it into the database and update the indices
- blob, err := rlp.EncodeToBytes(&blobTx{Tx: tx, Blobs: blobs, Commits: commits, Proofs: proofs})
- if err != nil {
- log.Error("Failed to encode transaction for storage", "hash", tx.Hash(), "err", err)
- return err
- }
- id, err := p.store.Put(blob)
- if err != nil {
- return err
- }
- meta := newBlobTxMeta(id, p.store.Size(id), tx)
-
- var (
- next = p.state.GetNonce(from)
- offset = int(tx.Nonce() - next)
- newacc = false
- )
- var oldEvictionExecFeeJumps, oldEvictionBlobFeeJumps float64
- if txs, ok := p.index[from]; ok {
- oldEvictionExecFeeJumps = txs[len(txs)-1].evictionExecFeeJumps
- oldEvictionBlobFeeJumps = txs[len(txs)-1].evictionBlobFeeJumps
- }
- if len(p.index[from]) > offset {
- // Transaction replaces a previously queued one
- prev := p.index[from][offset]
- if err := p.store.Delete(prev.id); err != nil {
- // Shitty situation, but try to recover gracefully instead of going boom
- log.Error("Failed to delete replaced transaction", "id", prev.id, "err", err)
- }
- // Update the transaction index
- p.index[from][offset] = meta
- p.spent[from] = new(uint256.Int).Sub(p.spent[from], prev.costCap)
- p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap)
-
- delete(p.lookup, prev.hash)
- p.lookup[meta.hash] = meta.id
- p.stored += uint64(meta.size) - uint64(prev.size)
- } else {
- // Transaction extends previously scheduled ones
- p.index[from] = append(p.index[from], meta)
- if _, ok := p.spent[from]; !ok {
- p.spent[from] = new(uint256.Int)
- newacc = true
- }
- p.spent[from] = new(uint256.Int).Add(p.spent[from], meta.costCap)
- p.lookup[meta.hash] = meta.id
- p.stored += uint64(meta.size)
- }
- // Recompute the rolling eviction fields. In case of a replacement, this will
- // recompute all subsequent fields. In case of an append, this will only do
- // the fresh calculation.
- txs := p.index[from]
-
- for i := offset; i < len(txs); i++ {
- // The first transaction will always use itself
- if i == 0 {
- txs[0].evictionExecTip = txs[0].execTipCap
- txs[0].evictionExecFeeJumps = txs[0].basefeeJumps
- txs[0].evictionBlobFeeJumps = txs[0].blobfeeJumps
-
- continue
- }
- // Subsequent transactions will use a rolling calculation
- txs[i].evictionExecTip = txs[i-1].evictionExecTip
- if txs[i].evictionExecTip.Cmp(txs[i].execTipCap) > 0 {
- txs[i].evictionExecTip = txs[i].execTipCap
- }
- txs[i].evictionExecFeeJumps = txs[i-1].evictionExecFeeJumps
- if txs[i].evictionExecFeeJumps > txs[i].basefeeJumps {
- txs[i].evictionExecFeeJumps = txs[i].basefeeJumps
- }
- txs[i].evictionBlobFeeJumps = txs[i-1].evictionBlobFeeJumps
- if txs[i].evictionBlobFeeJumps > txs[i].blobfeeJumps {
- txs[i].evictionBlobFeeJumps = txs[i].blobfeeJumps
- }
- }
- // Update the eviction heap with the new information:
- // - If the transaction is from a new account, add it to the heap
- // - If the account had a singleton tx replaced, update the heap (new price caps)
- // - If the account has a transaction replaced or appended, update the heap if significantly changed
- switch {
- case newacc:
- heap.Push(p.evict, from)
-
- case len(txs) == 1: // 1 tx and not a new acc, must be replacement
- heap.Fix(p.evict, p.evict.index[from])
-
- default: // replacement or new append
- evictionExecFeeDiff := oldEvictionExecFeeJumps - txs[len(txs)-1].evictionExecFeeJumps
- evictionBlobFeeDiff := oldEvictionBlobFeeJumps - txs[len(txs)-1].evictionBlobFeeJumps
-
- if math.Abs(evictionExecFeeDiff) > 0.001 || math.Abs(evictionBlobFeeDiff) > 0.001 { // need math.Abs, can go up and down
- heap.Fix(p.evict, p.evict.index[from])
- }
- }
- // If the pool went over the allowed data limit, evict transactions until
- // we're again below the threshold
- for p.stored > p.config.Datacap {
- p.drop()
- }
- p.updateStorageMetrics()
-
- return nil
-}
-
-// drop removes the worst transaction from the pool. It is primarily used when a
-// freshly added transaction overflows the pool and needs to evict something. The
-// method is also called on startup if the user resizes their storage, might be an
-// expensive run but it should be fine-ish.
-func (p *BlobPool) drop() {
- // Peek at the account with the worse transaction set to evict from (Go's heap
- // stores the minimum at index zero of the heap slice) and retrieve it's last
- // transaction.
- var (
- from = p.evict.addrs[0] // cannot call drop on empty pool
-
- txs = p.index[from]
- drop = txs[len(txs)-1]
- last = len(txs) == 1
- )
- // Remove the transaction from the pool's index
- if last {
- delete(p.index, from)
- delete(p.spent, from)
- p.reserve(from, false)
- } else {
- txs[len(txs)-1] = nil
- txs = txs[:len(txs)-1]
-
- p.index[from] = txs
- p.spent[from] = new(uint256.Int).Sub(p.spent[from], drop.costCap)
- }
- p.stored -= uint64(drop.size)
- delete(p.lookup, drop.hash)
-
- // Remove the transaction from the pool's evicion heap:
- // - If the entire account was dropped, pop off the address
- // - Otherwise, if the new tail has better eviction caps, fix the heap
- if last {
- heap.Pop(p.evict)
- } else {
- tail := txs[len(txs)-1] // new tail, surely exists
-
- evictionExecFeeDiff := tail.evictionExecFeeJumps - drop.evictionExecFeeJumps
- evictionBlobFeeDiff := tail.evictionBlobFeeJumps - drop.evictionBlobFeeJumps
-
- if evictionExecFeeDiff > 0.001 || evictionBlobFeeDiff > 0.001 { // no need for math.Abs, monotonic decreasing
- heap.Fix(p.evict, 0)
- }
- }
- // Remove the transaction from the data store
- log.Warn("Evicting overflown blob transaction", "from", from, "evicted", drop.nonce, "id", drop.id)
- if err := p.store.Delete(drop.id); err != nil {
- log.Error("Failed to drop evicted transaction", "id", drop.id, "err", err)
- }
-}
-
-// Pending retrieves all currently processable transactions, grouped by origin
-// account and sorted by nonce.
-func (p *BlobPool) Pending(enforceTips bool) map[common.Address][]*txpool.LazyTransaction {
- // Track the amount of time waiting to retrieve the list of pending blob txs
- // from the pool and the amount of time actually spent on assembling the data.
- // The latter will be pretty much moot, but we've kept it to have symmetric
- // across all user operations.
- pendStart := time.Now()
- p.lock.RLock()
- pendwaitHist.Update(time.Since(pendStart).Nanoseconds())
- defer p.lock.RUnlock()
-
- defer func(start time.Time) {
- pendtimeHist.Update(time.Since(start).Nanoseconds())
- }(time.Now())
-
- pending := make(map[common.Address][]*txpool.LazyTransaction)
- for addr, txs := range p.index {
- var lazies []*txpool.LazyTransaction
- for _, tx := range txs {
- lazies = append(lazies, &txpool.LazyTransaction{
- Pool: p,
- Hash: tx.hash,
- Time: time.Now(), // TODO(karalabe): Maybe save these and use that?
- GasFeeCap: tx.execFeeCap.ToBig(),
- GasTipCap: tx.execTipCap.ToBig(),
- })
- }
- if len(lazies) > 0 {
- pending[addr] = lazies
- }
- }
- return pending
-}
-
-// updateStorageMetrics retrieves a bunch of stats from the data store and pushes
-// them out as metrics.
-func (p *BlobPool) updateStorageMetrics() {
- stats := p.store.Infos()
-
- var (
- dataused uint64
- datareal uint64
- slotused uint64
-
- oversizedDataused uint64
- oversizedDatagaps uint64
- oversizedSlotused uint64
- oversizedSlotgaps uint64
- )
- for _, shelf := range stats.Shelves {
- slotDataused := shelf.FilledSlots * uint64(shelf.SlotSize)
- slotDatagaps := shelf.GappedSlots * uint64(shelf.SlotSize)
-
- dataused += slotDataused
- datareal += slotDataused + slotDatagaps
- slotused += shelf.FilledSlots
-
- metrics.GetOrRegisterGauge(fmt.Sprintf(shelfDatausedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDataused))
- metrics.GetOrRegisterGauge(fmt.Sprintf(shelfDatagapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDatagaps))
- metrics.GetOrRegisterGauge(fmt.Sprintf(shelfSlotusedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.FilledSlots))
- metrics.GetOrRegisterGauge(fmt.Sprintf(shelfSlotgapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.GappedSlots))
-
- if shelf.SlotSize/blobSize > maxBlobsPerTransaction {
- oversizedDataused += slotDataused
- oversizedDatagaps += slotDatagaps
- oversizedSlotused += shelf.FilledSlots
- oversizedSlotgaps += shelf.GappedSlots
- }
- }
- datausedGauge.Update(int64(dataused))
- datarealGauge.Update(int64(datareal))
- slotusedGauge.Update(int64(slotused))
-
- oversizedDatausedGauge.Update(int64(oversizedDataused))
- oversizedDatagapsGauge.Update(int64(oversizedDatagaps))
- oversizedSlotusedGauge.Update(int64(oversizedSlotused))
- oversizedSlotgapsGauge.Update(int64(oversizedSlotgaps))
-
- p.updateLimboMetrics()
-}
-
-// updateLimboMetrics retrieves a bunch of stats from the limbo store and pushes
-// // them out as metrics.
-func (p *BlobPool) updateLimboMetrics() {
- stats := p.limbo.store.Infos()
-
- var (
- dataused uint64
- datareal uint64
- slotused uint64
- )
- for _, shelf := range stats.Shelves {
- slotDataused := shelf.FilledSlots * uint64(shelf.SlotSize)
- slotDatagaps := shelf.GappedSlots * uint64(shelf.SlotSize)
-
- dataused += slotDataused
- datareal += slotDataused + slotDatagaps
- slotused += shelf.FilledSlots
-
- metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfDatausedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDataused))
- metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfDatagapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(slotDatagaps))
- metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfSlotusedGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.FilledSlots))
- metrics.GetOrRegisterGauge(fmt.Sprintf(limboShelfSlotgapsGaugeName, shelf.SlotSize/blobSize), nil).Update(int64(shelf.GappedSlots))
- }
- limboDatausedGauge.Update(int64(dataused))
- limboDatarealGauge.Update(int64(datareal))
- limboSlotusedGauge.Update(int64(slotused))
-}
-
-// SubscribeTransactions registers a subscription of NewTxsEvent and
-// starts sending event to the given channel.
-func (p *BlobPool) SubscribeTransactions(ch chan<- core.NewTxsEvent) event.Subscription {
- return p.eventScope.Track(p.eventFeed.Subscribe(ch))
-}
-
-// Nonce returns the next nonce of an account, with all transactions executable
-// by the pool already applied on top.
-func (p *BlobPool) Nonce(addr common.Address) uint64 {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- if txs, ok := p.index[addr]; ok {
- return txs[len(txs)-1].nonce + 1
- }
- return p.state.GetNonce(addr)
-}
-
-// Stats retrieves the current pool stats, namely the number of pending and the
-// number of queued (non-executable) transactions.
-func (p *BlobPool) Stats() (int, int) {
- p.lock.Lock()
- defer p.lock.Unlock()
-
- var pending int
- for _, txs := range p.index {
- pending += len(txs)
- }
- return pending, 0 // No non-executable txs in the blob pool
-}
-
-// Content retrieves the data content of the transaction pool, returning all the
-// pending as well as queued transactions, grouped by account and sorted by nonce.
-//
-// For the blob pool, this method will return nothing for now.
-// TODO(karalabe): Abstract out the returned metadata.
-func (p *BlobPool) Content() (map[common.Address][]*types.Transaction, map[common.Address][]*types.Transaction) {
- return make(map[common.Address][]*types.Transaction), make(map[common.Address][]*types.Transaction)
-}
-
-// ContentFrom retrieves the data content of the transaction pool, returning the
-// pending as well as queued transactions of this address, grouped by nonce.
-//
-// For the blob pool, this method will return nothing for now.
-// TODO(karalabe): Abstract out the returned metadata.
-func (p *BlobPool) ContentFrom(addr common.Address) ([]*types.Transaction, []*types.Transaction) {
- return []*types.Transaction{}, []*types.Transaction{}
-}
-
-// Locals retrieves the accounts currently considered local by the pool.
-//
-// There is no notion of local accounts in the blob pool.
-func (p *BlobPool) Locals() []common.Address {
- return []common.Address{}
-}
-
-// Status returns the known status (unknown/pending/queued) of a transaction
-// identified by their hashes.
-func (p *BlobPool) Status(hash common.Hash) txpool.TxStatus {
- if p.Has(hash) {
- return txpool.TxStatusPending
- }
- return txpool.TxStatusUnknown
-}
diff --git a/core/txpool/blobpool/blobpool_test.go b/core/txpool/blobpool/blobpool_test.go
deleted file mode 100644
index a657dd339080e..0000000000000
--- a/core/txpool/blobpool/blobpool_test.go
+++ /dev/null
@@ -1,1247 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-import (
- "bytes"
- "crypto/ecdsa"
- "crypto/sha256"
- "errors"
- "math"
- "math/big"
- "os"
- "path/filepath"
- "sync"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/consensus/misc"
- "github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/rawdb"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
- "github.com/ethereum/go-ethereum/crypto/kzg4844"
- "github.com/ethereum/go-ethereum/ethdb/memorydb"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/params"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/holiman/billy"
- "github.com/holiman/uint256"
-)
-
-var (
- emptyBlob = kzg4844.Blob{}
- emptyBlobCommit, _ = kzg4844.BlobToCommitment(emptyBlob)
- emptyBlobProof, _ = kzg4844.ComputeBlobProof(emptyBlob, emptyBlobCommit)
- emptyBlobVHash = blobHash(emptyBlobCommit)
-)
-
-func blobHash(commit kzg4844.Commitment) common.Hash {
- hasher := sha256.New()
- hasher.Write(commit[:])
- hash := hasher.Sum(nil)
-
- var vhash common.Hash
- vhash[0] = params.BlobTxHashVersion
- copy(vhash[1:], hash[1:])
-
- return vhash
-}
-
-// Chain configuration with Cancun enabled.
-//
-// TODO(karalabe): replace with params.MainnetChainConfig after Cancun.
-var testChainConfig *params.ChainConfig
-
-func init() {
- testChainConfig = new(params.ChainConfig)
- *testChainConfig = *params.MainnetChainConfig
-
- testChainConfig.CancunTime = new(uint64)
- *testChainConfig.CancunTime = uint64(time.Now().Unix())
-}
-
-// testBlockChain is a mock of the live chain for testing the pool.
-type testBlockChain struct {
- config *params.ChainConfig
- basefee *uint256.Int
- blobfee *uint256.Int
- statedb *state.StateDB
-}
-
-func (bc *testBlockChain) Config() *params.ChainConfig {
- return bc.config
-}
-
-func (bc *testBlockChain) CurrentBlock() *types.Header {
- // Yolo, life is too short to invert mist.CalcBaseFee and misc.CalcBlobFee,
- // just binary search it them.
-
- // The base fee at 5714 ETH translates into the 21000 base gas higher than
- // mainnet ether existence, use that as a cap for the tests.
- var (
- blockNumber = new(big.Int).Add(bc.config.LondonBlock, big.NewInt(1))
- blockTime = *bc.config.CancunTime + 1
- gasLimit = uint64(30_000_000)
- )
- lo := new(big.Int)
- hi := new(big.Int).Mul(big.NewInt(5714), new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))
-
- for new(big.Int).Add(lo, big.NewInt(1)).Cmp(hi) != 0 {
- mid := new(big.Int).Add(lo, hi)
- mid.Div(mid, big.NewInt(2))
-
- if misc.CalcBaseFee(bc.config, &types.Header{
- Number: blockNumber,
- GasLimit: gasLimit,
- GasUsed: 0,
- BaseFee: mid,
- }).Cmp(bc.basefee.ToBig()) > 0 {
- hi = mid
- } else {
- lo = mid
- }
- }
- baseFee := lo
-
- // The excess data gas at 2^27 translates into a blob fee higher than mainnet
- // ether existence, use that as a cap for the tests.
- lo = new(big.Int)
- hi = new(big.Int).Exp(big.NewInt(2), big.NewInt(27), nil)
-
- for new(big.Int).Add(lo, big.NewInt(1)).Cmp(hi) != 0 {
- mid := new(big.Int).Add(lo, hi)
- mid.Div(mid, big.NewInt(2))
-
- if misc.CalcBlobFee(mid.Uint64()).Cmp(bc.blobfee.ToBig()) > 0 {
- hi = mid
- } else {
- lo = mid
- }
- }
- excessDataGas := lo.Uint64()
-
- return &types.Header{
- Number: blockNumber,
- Time: blockTime,
- GasLimit: gasLimit,
- BaseFee: baseFee,
- ExcessDataGas: &excessDataGas,
- }
-}
-
-func (bc *testBlockChain) CurrentFinalBlock() *types.Header {
- return &types.Header{
- Number: big.NewInt(0),
- }
-}
-
-func (bt *testBlockChain) GetBlock(hash common.Hash, number uint64) *types.Block {
- return nil
-}
-
-func (bc *testBlockChain) StateAt(common.Hash) (*state.StateDB, error) {
- return bc.statedb, nil
-}
-
-// makeAddressReserver is a utility method to sanity check that accounts are
-// properly reserved by the blobpool (no duplicate reserves or unreserves).
-func makeAddressReserver() txpool.AddressReserver {
- var (
- reserved = make(map[common.Address]struct{})
- lock sync.Mutex
- )
- return func(addr common.Address, reserve bool) error {
- lock.Lock()
- defer lock.Unlock()
-
- _, exists := reserved[addr]
- if reserve {
- if exists {
- panic("already reserved")
- }
- reserved[addr] = struct{}{}
- return nil
- }
- if !exists {
- panic("not reserved")
- }
- delete(reserved, addr)
- return nil
- }
-}
-
-// makeTx is a utility method to construct a random blob transaction and sign it
-// with a valid key, only setting the interesting fields from the perspective of
-// the blob pool.
-func makeTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64, key *ecdsa.PrivateKey) *types.Transaction {
- tx, _ := types.SignNewTx(key, types.LatestSigner(testChainConfig), makeUnsignedTx(nonce, gasTipCap, gasFeeCap, blobFeeCap))
- return tx
-}
-
-// makeUnsignedTx is a utility method to construct a random blob tranasaction
-// without signing it.
-func makeUnsignedTx(nonce uint64, gasTipCap uint64, gasFeeCap uint64, blobFeeCap uint64) *types.BlobTx {
- return &types.BlobTx{
- ChainID: uint256.MustFromBig(testChainConfig.ChainID),
- Nonce: nonce,
- GasTipCap: uint256.NewInt(gasTipCap),
- GasFeeCap: uint256.NewInt(gasFeeCap),
- Gas: 21000,
- BlobFeeCap: uint256.NewInt(blobFeeCap),
- BlobHashes: []common.Hash{emptyBlobVHash},
- Value: uint256.NewInt(100),
- }
-}
-
-// verifyPoolInternals iterates over all the transactions in the pool and checks
-// that sort orders, calculated fields, cumulated fields are correct.
-func verifyPoolInternals(t *testing.T, pool *BlobPool) {
- // Mark this method as a helper to remove from stack traces
- t.Helper()
-
- // Verify that all items in the index are present in the lookup and nothing more
- seen := make(map[common.Hash]struct{})
- for addr, txs := range pool.index {
- for _, tx := range txs {
- if _, ok := seen[tx.hash]; ok {
- t.Errorf("duplicate hash #%x in transaction index: address %s, nonce %d", tx.hash, addr, tx.nonce)
- }
- seen[tx.hash] = struct{}{}
- }
- }
- for hash, id := range pool.lookup {
- if _, ok := seen[hash]; !ok {
- t.Errorf("lookup entry missing from transaction index: hash #%x, id %d", hash, id)
- }
- delete(seen, hash)
- }
- for hash := range seen {
- t.Errorf("indexed transaction hash #%x missing from lookup table", hash)
- }
- // Verify that transactions are sorted per account and contain no nonce gaps
- for addr, txs := range pool.index {
- for i := 1; i < len(txs); i++ {
- if txs[i].nonce != txs[i-1].nonce+1 {
- t.Errorf("addr %v, tx %d nonce mismatch: have %d, want %d", addr, i, txs[i].nonce, txs[i-1].nonce+1)
- }
- }
- }
- // Verify that calculated evacuation thresholds are correct
- for addr, txs := range pool.index {
- if !txs[0].evictionExecTip.Eq(txs[0].execTipCap) {
- t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, 0, txs[0].evictionExecTip, txs[0].execTipCap)
- }
- if math.Abs(txs[0].evictionExecFeeJumps-txs[0].basefeeJumps) > 0.001 {
- t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionExecFeeJumps, txs[0].basefeeJumps)
- }
- if math.Abs(txs[0].evictionBlobFeeJumps-txs[0].blobfeeJumps) > 0.001 {
- t.Errorf("addr %v, tx %d eviction blob fee jumps mismatch: have %f, want %f", addr, 0, txs[0].evictionBlobFeeJumps, txs[0].blobfeeJumps)
- }
- for i := 1; i < len(txs); i++ {
- wantExecTip := txs[i-1].evictionExecTip
- if wantExecTip.Gt(txs[i].execTipCap) {
- wantExecTip = txs[i].execTipCap
- }
- if !txs[i].evictionExecTip.Eq(wantExecTip) {
- t.Errorf("addr %v, tx %d eviction execution tip mismatch: have %d, want %d", addr, i, txs[i].evictionExecTip, wantExecTip)
- }
-
- wantExecFeeJumps := txs[i-1].evictionExecFeeJumps
- if wantExecFeeJumps > txs[i].basefeeJumps {
- wantExecFeeJumps = txs[i].basefeeJumps
- }
- if math.Abs(txs[i].evictionExecFeeJumps-wantExecFeeJumps) > 0.001 {
- t.Errorf("addr %v, tx %d eviction execution fee jumps mismatch: have %f, want %f", addr, i, txs[i].evictionExecFeeJumps, wantExecFeeJumps)
- }
-
- wantBlobFeeJumps := txs[i-1].evictionBlobFeeJumps
- if wantBlobFeeJumps > txs[i].blobfeeJumps {
- wantBlobFeeJumps = txs[i].blobfeeJumps
- }
- if math.Abs(txs[i].evictionBlobFeeJumps-wantBlobFeeJumps) > 0.001 {
- t.Errorf("addr %v, tx %d eviction blob fee jumps mismatch: have %f, want %f", addr, i, txs[i].evictionBlobFeeJumps, wantBlobFeeJumps)
- }
- }
- }
- // Verify that account balance accumulations are correct
- for addr, txs := range pool.index {
- spent := new(uint256.Int)
- for _, tx := range txs {
- spent.Add(spent, tx.costCap)
- }
- if !pool.spent[addr].Eq(spent) {
- t.Errorf("addr %v expenditure mismatch: have %d, want %d", addr, pool.spent[addr], spent)
- }
- }
- // Verify that pool storage size is correct
- var stored uint64
- for _, txs := range pool.index {
- for _, tx := range txs {
- stored += uint64(tx.size)
- }
- }
- if pool.stored != stored {
- t.Errorf("pool storage mismatch: have %d, want %d", pool.stored, stored)
- }
- // Verify the price heap internals
- verifyHeapInternals(t, pool.evict)
-}
-
-// Tests that transactions can be loaded from disk on startup and that they are
-// correctly discarded if invalid.
-//
-// - 1. A transaction that cannot be decoded must be dropped
-// - 2. A transaction that cannot be recovered (bad signature) must be dropped
-// - 3. All transactions after a nonce gap must be dropped
-// - 4. All transactions after an underpriced one (including it) must be dropped
-func TestOpenDrops(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
-
- // Create a temporary folder for the persistent backend
- storage, _ := os.MkdirTemp("", "blobpool-")
- defer os.RemoveAll(storage)
-
- os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
- store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil)
-
- // Insert a malformed transaction to verify that decoding errors (or format
- // changes) are handled gracefully (case 1)
- malformed, _ := store.Put([]byte("this is a badly encoded transaction"))
-
- // Insert a transaction with a bad signature to verify that stale junk after
- // potential hard-forks can get evicted (case 2)
- tx := types.NewTx(&types.BlobTx{
- ChainID: uint256.MustFromBig(testChainConfig.ChainID),
- GasTipCap: new(uint256.Int),
- GasFeeCap: new(uint256.Int),
- Gas: 0,
- Value: new(uint256.Int),
- Data: nil,
- BlobFeeCap: new(uint256.Int),
- V: new(uint256.Int),
- R: new(uint256.Int),
- S: new(uint256.Int),
- })
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
- badsig, _ := store.Put(blob)
-
- // Insert a sequence of transactions with a nonce gap in between to verify
- // that anything gapped will get evicted (case 3)
- var (
- gapper, _ = crypto.GenerateKey()
-
- valids = make(map[uint64]struct{})
- gapped = make(map[uint64]struct{})
- )
- for _, nonce := range []uint64{0, 1, 3, 4, 6, 7} { // first gap at #2, another at #5
- tx := makeTx(nonce, 1, 1, 1, gapper)
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
-
- id, _ := store.Put(blob)
- if nonce < 2 {
- valids[id] = struct{}{}
- } else {
- gapped[id] = struct{}{}
- }
- }
- // Insert a sequence of transactions with a gapped starting nonce to verify
- // that the entire set will get dropped.
- var (
- dangler, _ = crypto.GenerateKey()
- dangling = make(map[uint64]struct{})
- )
- for _, nonce := range []uint64{1, 2, 3} { // first gap at #0, all set dangling
- tx := makeTx(nonce, 1, 1, 1, dangler)
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
-
- id, _ := store.Put(blob)
- dangling[id] = struct{}{}
- }
- // Insert a sequence of transactions with already passed nonces to veirfy
- // that the entire set will get dropped.
- var (
- filler, _ = crypto.GenerateKey()
- filled = make(map[uint64]struct{})
- )
- for _, nonce := range []uint64{0, 1, 2} { // account nonce at 3, all set filled
- tx := makeTx(nonce, 1, 1, 1, filler)
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
-
- id, _ := store.Put(blob)
- filled[id] = struct{}{}
- }
- // Insert a sequence of transactions with partially passed nonces to veirfy
- // that the included part of the set will get dropped
- var (
- overlapper, _ = crypto.GenerateKey()
- overlapped = make(map[uint64]struct{})
- )
- for _, nonce := range []uint64{0, 1, 2, 3} { // account nonce at 2, half filled
- tx := makeTx(nonce, 1, 1, 1, overlapper)
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
-
- id, _ := store.Put(blob)
- if nonce >= 2 {
- valids[id] = struct{}{}
- } else {
- overlapped[id] = struct{}{}
- }
- }
- // Insert a sequence of transactions with an underpriced first to verify that
- // the entire set will get dropped (case 4).
- var (
- underpayer, _ = crypto.GenerateKey()
- underpaid = make(map[uint64]struct{})
- )
- for i := 0; i < 5; i++ { // make #0 underpriced
- var tx *types.Transaction
- if i == 0 {
- tx = makeTx(uint64(i), 0, 0, 0, underpayer)
- } else {
- tx = makeTx(uint64(i), 1, 1, 1, underpayer)
- }
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
-
- id, _ := store.Put(blob)
- underpaid[id] = struct{}{}
- }
-
- // Insert a sequence of transactions with an underpriced in between to verify
- // that it and anything newly gapped will get evicted (case 4).
- var (
- outpricer, _ = crypto.GenerateKey()
- outpriced = make(map[uint64]struct{})
- )
- for i := 0; i < 5; i++ { // make #2 underpriced
- var tx *types.Transaction
- if i == 2 {
- tx = makeTx(uint64(i), 0, 0, 0, outpricer)
- } else {
- tx = makeTx(uint64(i), 1, 1, 1, outpricer)
- }
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
-
- id, _ := store.Put(blob)
- if i < 2 {
- valids[id] = struct{}{}
- } else {
- outpriced[id] = struct{}{}
- }
- }
- // Insert a sequence of transactions fully overdrafted to verify that the
- // entire set will get invalidated.
- var (
- exceeder, _ = crypto.GenerateKey()
- exceeded = make(map[uint64]struct{})
- )
- for _, nonce := range []uint64{0, 1, 2} { // nonce 0 overdrafts the account
- var tx *types.Transaction
- if nonce == 0 {
- tx = makeTx(nonce, 1, 100, 1, exceeder)
- } else {
- tx = makeTx(nonce, 1, 1, 1, exceeder)
- }
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
-
- id, _ := store.Put(blob)
- exceeded[id] = struct{}{}
- }
- // Insert a sequence of transactions partially overdrafted to verify that part
- // of the set will get invalidated.
- var (
- overdrafter, _ = crypto.GenerateKey()
- overdrafted = make(map[uint64]struct{})
- )
- for _, nonce := range []uint64{0, 1, 2} { // nonce 1 overdrafts the account
- var tx *types.Transaction
- if nonce == 1 {
- tx = makeTx(nonce, 1, 100, 1, overdrafter)
- } else {
- tx = makeTx(nonce, 1, 1, 1, overdrafter)
- }
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
-
- id, _ := store.Put(blob)
- if nonce < 1 {
- valids[id] = struct{}{}
- } else {
- overdrafted[id] = struct{}{}
- }
- }
- // Insert a sequence of transactions overflowing the account cap to verify
- // that part of the set will get invalidated.
- var (
- overcapper, _ = crypto.GenerateKey()
- overcapped = make(map[uint64]struct{})
- )
- for nonce := uint64(0); nonce < maxTxsPerAccount+3; nonce++ {
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: makeTx(nonce, 1, 1, 1, overcapper)})
-
- id, _ := store.Put(blob)
- if nonce < maxTxsPerAccount {
- valids[id] = struct{}{}
- } else {
- overcapped[id] = struct{}{}
- }
- }
- store.Close()
-
- // Create a blob pool out of the pre-seeded data
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
- statedb.AddBalance(crypto.PubkeyToAddress(gapper.PublicKey), big.NewInt(1000000))
- statedb.AddBalance(crypto.PubkeyToAddress(dangler.PublicKey), big.NewInt(1000000))
- statedb.AddBalance(crypto.PubkeyToAddress(filler.PublicKey), big.NewInt(1000000))
- statedb.SetNonce(crypto.PubkeyToAddress(filler.PublicKey), 3)
- statedb.AddBalance(crypto.PubkeyToAddress(overlapper.PublicKey), big.NewInt(1000000))
- statedb.SetNonce(crypto.PubkeyToAddress(overlapper.PublicKey), 2)
- statedb.AddBalance(crypto.PubkeyToAddress(underpayer.PublicKey), big.NewInt(1000000))
- statedb.AddBalance(crypto.PubkeyToAddress(outpricer.PublicKey), big.NewInt(1000000))
- statedb.AddBalance(crypto.PubkeyToAddress(exceeder.PublicKey), big.NewInt(1000000))
- statedb.AddBalance(crypto.PubkeyToAddress(overdrafter.PublicKey), big.NewInt(1000000))
- statedb.AddBalance(crypto.PubkeyToAddress(overcapper.PublicKey), big.NewInt(10000000))
- statedb.Commit(0, true)
-
- chain := &testBlockChain{
- config: testChainConfig,
- basefee: uint256.NewInt(params.InitialBaseFee),
- blobfee: uint256.NewInt(params.BlobTxMinDataGasprice),
- statedb: statedb,
- }
- pool := New(Config{Datadir: storage}, chain)
- if err := pool.Init(big.NewInt(1), chain.CurrentBlock(), makeAddressReserver()); err != nil {
- t.Fatalf("failed to create blob pool: %v", err)
- }
- defer pool.Close()
-
- // Verify that the malformed (case 1), badly signed (case 2) and gapped (case
- // 3) txs have been deleted from the pool
- alive := make(map[uint64]struct{})
- for _, txs := range pool.index {
- for _, tx := range txs {
- switch tx.id {
- case malformed:
- t.Errorf("malformed RLP transaction remained in storage")
- case badsig:
- t.Errorf("invalidly signed transaction remained in storage")
- default:
- if _, ok := dangling[tx.id]; ok {
- t.Errorf("dangling transaction remained in storage: %d", tx.id)
- } else if _, ok := filled[tx.id]; ok {
- t.Errorf("filled transaction remained in storage: %d", tx.id)
- } else if _, ok := overlapped[tx.id]; ok {
- t.Errorf("overlapped transaction remained in storage: %d", tx.id)
- } else if _, ok := gapped[tx.id]; ok {
- t.Errorf("gapped transaction remained in storage: %d", tx.id)
- } else if _, ok := underpaid[tx.id]; ok {
- t.Errorf("underpaid transaction remained in storage: %d", tx.id)
- } else if _, ok := outpriced[tx.id]; ok {
- t.Errorf("outpriced transaction remained in storage: %d", tx.id)
- } else if _, ok := exceeded[tx.id]; ok {
- t.Errorf("fully overdrafted transaction remained in storage: %d", tx.id)
- } else if _, ok := overdrafted[tx.id]; ok {
- t.Errorf("partially overdrafted transaction remained in storage: %d", tx.id)
- } else if _, ok := overcapped[tx.id]; ok {
- t.Errorf("overcapped transaction remained in storage: %d", tx.id)
- } else {
- alive[tx.id] = struct{}{}
- }
- }
- }
- }
- // Verify that the rest of the transactions remained alive
- if len(alive) != len(valids) {
- t.Errorf("valid transaction count mismatch: have %d, want %d", len(alive), len(valids))
- }
- for id := range alive {
- if _, ok := valids[id]; !ok {
- t.Errorf("extra transaction %d", id)
- }
- }
- for id := range valids {
- if _, ok := alive[id]; !ok {
- t.Errorf("missing transaction %d", id)
- }
- }
- // Verify all the calculated pool internals. Interestingly, this is **not**
- // a duplication of the above checks, this actually validates the verifier
- // using the above already hard coded checks.
- //
- // Do not remove this, nor alter the above to be generic.
- verifyPoolInternals(t, pool)
-}
-
-// Tests that transactions loaded from disk are indexed corrently.
-//
-// - 1. Transactions must be groupped by sender, sorted by nonce
-// - 2. Eviction thresholds are calculated correctly for the sequences
-// - 3. Balance usage of an account is totals across all transactions
-func TestOpenIndex(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
-
- // Create a temporary folder for the persistent backend
- storage, _ := os.MkdirTemp("", "blobpool-")
- defer os.RemoveAll(storage)
-
- os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
- store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil)
-
- // Insert a sequence of transactions with varying price points to check that
- // the cumulative minimumw will be maintained.
- var (
- key, _ = crypto.GenerateKey()
- addr = crypto.PubkeyToAddress(key.PublicKey)
-
- txExecTipCaps = []uint64{10, 25, 5, 7, 1, 100}
- txExecFeeCaps = []uint64{100, 90, 200, 10, 80, 300}
- txBlobFeeCaps = []uint64{55, 66, 77, 33, 22, 11}
-
- //basefeeJumps = []float64{39.098, 38.204, 44.983, 19.549, 37.204, 48.426} // log 1.125 (exec fee cap)
- //blobfeeJumps = []float64{34.023, 35.570, 36.879, 29.686, 26.243, 20.358} // log 1.125 (blob fee cap)
-
- evictExecTipCaps = []uint64{10, 10, 5, 5, 1, 1}
- evictExecFeeJumps = []float64{39.098, 38.204, 38.204, 19.549, 19.549, 19.549} // min(log 1.125 (exec fee cap))
- evictBlobFeeJumps = []float64{34.023, 34.023, 34.023, 29.686, 26.243, 20.358} // min(log 1.125 (blob fee cap))
-
- totalSpent = uint256.NewInt(21000*(100+90+200+10+80+300) + blobSize*(55+66+77+33+22+11) + 100*6) // 21000 gas x price + 128KB x blobprice + value
- )
- for _, i := range []int{5, 3, 4, 2, 0, 1} { // Randomize the tx insertion order to force sorting on load
- tx := makeTx(uint64(i), txExecTipCaps[i], txExecFeeCaps[i], txBlobFeeCaps[i], key)
- blob, _ := rlp.EncodeToBytes(&blobTx{Tx: tx})
- store.Put(blob)
- }
- store.Close()
-
- // Create a blob pool out of the pre-seeded data
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
- statedb.AddBalance(addr, big.NewInt(1_000_000_000))
- statedb.Commit(0, true)
-
- chain := &testBlockChain{
- config: testChainConfig,
- basefee: uint256.NewInt(params.InitialBaseFee),
- blobfee: uint256.NewInt(params.BlobTxMinDataGasprice),
- statedb: statedb,
- }
- pool := New(Config{Datadir: storage}, chain)
- if err := pool.Init(big.NewInt(1), chain.CurrentBlock(), makeAddressReserver()); err != nil {
- t.Fatalf("failed to create blob pool: %v", err)
- }
- defer pool.Close()
-
- // Verify that the transactions have been sorted by nonce (case 1)
- for i := 0; i < len(pool.index[addr]); i++ {
- if pool.index[addr][i].nonce != uint64(i) {
- t.Errorf("tx %d nonce mismatch: have %d, want %d", i, pool.index[addr][i].nonce, uint64(i))
- }
- }
- // Verify that the cumulative fee minimums have been correctly calculated (case 2)
- for i, cap := range evictExecTipCaps {
- if !pool.index[addr][i].evictionExecTip.Eq(uint256.NewInt(cap)) {
- t.Errorf("eviction tip cap %d mismatch: have %d, want %d", i, pool.index[addr][i].evictionExecTip, cap)
- }
- }
- for i, jumps := range evictExecFeeJumps {
- if math.Abs(pool.index[addr][i].evictionExecFeeJumps-jumps) > 0.001 {
- t.Errorf("eviction fee cap jumps %d mismatch: have %f, want %f", i, pool.index[addr][i].evictionExecFeeJumps, jumps)
- }
- }
- for i, jumps := range evictBlobFeeJumps {
- if math.Abs(pool.index[addr][i].evictionBlobFeeJumps-jumps) > 0.001 {
- t.Errorf("eviction blob fee cap jumps %d mismatch: have %f, want %f", i, pool.index[addr][i].evictionBlobFeeJumps, jumps)
- }
- }
- // Verify that the balance usage has been correctly calculated (case 3)
- if !pool.spent[addr].Eq(totalSpent) {
- t.Errorf("expenditure mismatch: have %d, want %d", pool.spent[addr], totalSpent)
- }
- // Verify all the calculated pool internals. Interestingly, this is **not**
- // a duplication of the above checks, this actually validates the verifier
- // using the above already hard coded checks.
- //
- // Do not remove this, nor alter the above to be generic.
- verifyPoolInternals(t, pool)
-}
-
-// Tests that after indexing all the loaded transactions from disk, a price heap
-// is correctly constructed based on the head basefee and blobfee.
-func TestOpenHeap(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
-
- // Create a temporary folder for the persistent backend
- storage, _ := os.MkdirTemp("", "blobpool-")
- defer os.RemoveAll(storage)
-
- os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
- store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil)
-
- // Insert a few transactions from a few accounts. To remove randomness from
- // the heap initialization, use a deterministic account/tx/priority ordering.
- var (
- key1, _ = crypto.GenerateKey()
- key2, _ = crypto.GenerateKey()
- key3, _ = crypto.GenerateKey()
-
- addr1 = crypto.PubkeyToAddress(key1.PublicKey)
- addr2 = crypto.PubkeyToAddress(key2.PublicKey)
- addr3 = crypto.PubkeyToAddress(key3.PublicKey)
- )
- if bytes.Compare(addr1[:], addr2[:]) > 0 {
- key1, addr1, key2, addr2 = key2, addr2, key1, addr1
- }
- if bytes.Compare(addr1[:], addr3[:]) > 0 {
- key1, addr1, key3, addr3 = key3, addr3, key1, addr1
- }
- if bytes.Compare(addr2[:], addr3[:]) > 0 {
- key2, addr2, key3, addr3 = key3, addr3, key2, addr2
- }
- var (
- tx1 = makeTx(0, 1, 1000, 90, key1)
- tx2 = makeTx(0, 1, 800, 70, key2)
- tx3 = makeTx(0, 1, 1500, 110, key3)
-
- blob1, _ = rlp.EncodeToBytes(&blobTx{Tx: tx1})
- blob2, _ = rlp.EncodeToBytes(&blobTx{Tx: tx2})
- blob3, _ = rlp.EncodeToBytes(&blobTx{Tx: tx3})
-
- heapOrder = []common.Address{addr2, addr1, addr3}
- heapIndex = map[common.Address]int{addr2: 0, addr1: 1, addr3: 2}
- )
- store.Put(blob1)
- store.Put(blob2)
- store.Put(blob3)
- store.Close()
-
- // Create a blob pool out of the pre-seeded data
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
- statedb.AddBalance(addr1, big.NewInt(1_000_000_000))
- statedb.AddBalance(addr2, big.NewInt(1_000_000_000))
- statedb.AddBalance(addr3, big.NewInt(1_000_000_000))
- statedb.Commit(0, true)
-
- chain := &testBlockChain{
- config: testChainConfig,
- basefee: uint256.NewInt(1050),
- blobfee: uint256.NewInt(105),
- statedb: statedb,
- }
- pool := New(Config{Datadir: storage}, chain)
- if err := pool.Init(big.NewInt(1), chain.CurrentBlock(), makeAddressReserver()); err != nil {
- t.Fatalf("failed to create blob pool: %v", err)
- }
- defer pool.Close()
-
- // Verify that the heap's internal state matches the expectations
- for i, addr := range pool.evict.addrs {
- if addr != heapOrder[i] {
- t.Errorf("slot %d mismatch: have %v, want %v", i, addr, heapOrder[i])
- }
- }
- for addr, i := range pool.evict.index {
- if i != heapIndex[addr] {
- t.Errorf("index for %v mismatch: have %d, want %d", addr, i, heapIndex[addr])
- }
- }
- // Verify all the calculated pool internals. Interestingly, this is **not**
- // a duplication of the above checks, this actually validates the verifier
- // using the above already hard coded checks.
- //
- // Do not remove this, nor alter the above to be generic.
- verifyPoolInternals(t, pool)
-}
-
-// Tests that after the pool's previous state is loaded back, any transactions
-// over the new storage cap will get dropped.
-func TestOpenCap(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
-
- // Create a temporary folder for the persistent backend
- storage, _ := os.MkdirTemp("", "blobpool-")
- defer os.RemoveAll(storage)
-
- os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
- store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil)
-
- // Insert a few transactions from a few accounts
- var (
- key1, _ = crypto.GenerateKey()
- key2, _ = crypto.GenerateKey()
- key3, _ = crypto.GenerateKey()
-
- addr1 = crypto.PubkeyToAddress(key1.PublicKey)
- addr2 = crypto.PubkeyToAddress(key2.PublicKey)
- addr3 = crypto.PubkeyToAddress(key3.PublicKey)
-
- tx1 = makeTx(0, 1, 1000, 100, key1)
- tx2 = makeTx(0, 1, 800, 70, key2)
- tx3 = makeTx(0, 1, 1500, 110, key3)
-
- blob1, _ = rlp.EncodeToBytes(&blobTx{Tx: tx1, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}})
- blob2, _ = rlp.EncodeToBytes(&blobTx{Tx: tx2, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}})
- blob3, _ = rlp.EncodeToBytes(&blobTx{Tx: tx3, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}})
-
- keep = []common.Address{addr1, addr3}
- drop = []common.Address{addr2}
- size = uint64(2 * (txAvgSize + blobSize))
- )
- store.Put(blob1)
- store.Put(blob2)
- store.Put(blob3)
- store.Close()
-
- // Verify pool capping twice: first by reducing the data cap, then restarting
- // with a high cap to ensure everything was persisted previously
- for _, datacap := range []uint64{2 * (txAvgSize + blobSize), 100 * (txAvgSize + blobSize)} {
- // Create a blob pool out of the pre-seeded data, but cap it to 2 blob transaction
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
- statedb.AddBalance(addr1, big.NewInt(1_000_000_000))
- statedb.AddBalance(addr2, big.NewInt(1_000_000_000))
- statedb.AddBalance(addr3, big.NewInt(1_000_000_000))
- statedb.Commit(0, true)
-
- chain := &testBlockChain{
- config: testChainConfig,
- basefee: uint256.NewInt(1050),
- blobfee: uint256.NewInt(105),
- statedb: statedb,
- }
- pool := New(Config{Datadir: storage, Datacap: datacap}, chain)
- if err := pool.Init(big.NewInt(1), chain.CurrentBlock(), makeAddressReserver()); err != nil {
- t.Fatalf("failed to create blob pool: %v", err)
- }
- // Verify that enough transactions have been dropped to get the pool's size
- // under the requested limit
- if len(pool.index) != len(keep) {
- t.Errorf("tracked account count mismatch: have %d, want %d", len(pool.index), len(keep))
- }
- for _, addr := range keep {
- if _, ok := pool.index[addr]; !ok {
- t.Errorf("expected account %v missing from pool", addr)
- }
- }
- for _, addr := range drop {
- if _, ok := pool.index[addr]; ok {
- t.Errorf("unexpected account %v present in pool", addr)
- }
- }
- if pool.stored != size {
- t.Errorf("pool stored size mismatch: have %v, want %v", pool.stored, size)
- }
- // Verify all the calculated pool internals. Interestingly, this is **not**
- // a duplication of the above checks, this actually validates the verifier
- // using the above already hard coded checks.
- //
- // Do not remove this, nor alter the above to be generic.
- verifyPoolInternals(t, pool)
-
- pool.Close()
- }
-}
-
-// Tests that adding transaction will correctly store it in the persistent store
-// and update all the indices.
-//
-// Note, this tests mostly checks the pool transaction shuffling logic or things
-// specific to the blob pool. It does not do an exhaustive transaction validity
-// check.
-func TestAdd(t *testing.T) {
- log.Root().SetHandler(log.LvlFilterHandler(log.LvlTrace, log.StreamHandler(os.Stderr, log.TerminalFormat(true))))
-
- // seed is a helper tumpe to seed an initial state db and pool
- type seed struct {
- balance uint64
- nonce uint64
- txs []*types.BlobTx
- }
-
- // addtx is a helper sender/tx tuple to represent a new tx addition
- type addtx struct {
- from string
- tx *types.BlobTx
- err error
- }
-
- tests := []struct {
- seeds map[string]seed
- adds []addtx
- }{
- // Transactions from new accounts should be accepted if their initial
- // nonce matches the expected one from the statedb. Higher or lower must
- // be rejected.
- {
- seeds: map[string]seed{
- "alice": {balance: 21100 + blobSize},
- "bob": {balance: 21100 + blobSize, nonce: 1},
- "claire": {balance: 21100 + blobSize},
- "dave": {balance: 21100 + blobSize, nonce: 1},
- },
- adds: []addtx{
- { // New account, no previous txs: accept nonce 0
- from: "alice",
- tx: makeUnsignedTx(0, 1, 1, 1),
- err: nil,
- },
- { // Old account, 1 tx in chain, 0 pending: accept nonce 1
- from: "bob",
- tx: makeUnsignedTx(1, 1, 1, 1),
- err: nil,
- },
- { // New account, no previous txs: reject nonce 1
- from: "claire",
- tx: makeUnsignedTx(1, 1, 1, 1),
- err: core.ErrNonceTooHigh,
- },
- { // Old account, 1 tx in chain, 0 pending: reject nonce 0
- from: "dave",
- tx: makeUnsignedTx(0, 1, 1, 1),
- err: core.ErrNonceTooLow,
- },
- { // Old account, 1 tx in chain, 0 pending: reject nonce 2
- from: "dave",
- tx: makeUnsignedTx(2, 1, 1, 1),
- err: core.ErrNonceTooHigh,
- },
- },
- },
- // Transactions from already pooled accounts should only be accepted if
- // the nonces are contiguous (ignore prices for now, will check later)
- {
- seeds: map[string]seed{
- "alice": {
- balance: 1000000,
- txs: []*types.BlobTx{
- makeUnsignedTx(0, 1, 1, 1),
- },
- },
- "bob": {
- balance: 1000000,
- nonce: 1,
- txs: []*types.BlobTx{
- makeUnsignedTx(1, 1, 1, 1),
- },
- },
- },
- adds: []addtx{
- { // New account, 1 tx pending: reject replacement nonce 0 (ignore price for now)
- from: "alice",
- tx: makeUnsignedTx(0, 1, 1, 1),
- err: txpool.ErrReplaceUnderpriced,
- },
- { // New account, 1 tx pending: accept nonce 1
- from: "alice",
- tx: makeUnsignedTx(1, 1, 1, 1),
- err: nil,
- },
- { // New account, 2 txs pending: reject nonce 3
- from: "alice",
- tx: makeUnsignedTx(3, 1, 1, 1),
- err: core.ErrNonceTooHigh,
- },
- { // New account, 2 txs pending: accept nonce 2
- from: "alice",
- tx: makeUnsignedTx(2, 1, 1, 1),
- err: nil,
- },
- { // New account, 3 txs pending: accept nonce 3 now
- from: "alice",
- tx: makeUnsignedTx(3, 1, 1, 1),
- err: nil,
- },
- { // Old account, 1 tx in chain, 1 tx pending: reject replacement nonce 1 (ignore price for now)
- from: "bob",
- tx: makeUnsignedTx(1, 1, 1, 1),
- err: txpool.ErrReplaceUnderpriced,
- },
- { // Old account, 1 tx in chain, 1 tx pending: accept nonce 2 (ignore price for now)
- from: "bob",
- tx: makeUnsignedTx(2, 1, 1, 1),
- err: nil,
- },
- },
- },
- // Transactions should only be accepted into the pool if the cumulative
- // expenditure doesn't overflow the account balance
- {
- seeds: map[string]seed{
- "alice": {balance: 63299 + 3*blobSize}, // 3 tx - 1 wei
- },
- adds: []addtx{
- { // New account, no previous txs: accept nonce 0 with 21100 wei spend
- from: "alice",
- tx: makeUnsignedTx(0, 1, 1, 1),
- err: nil,
- },
- { // New account, 1 pooled tx with 21100 wei spent: accept nonce 1 with 21100 wei spend
- from: "alice",
- tx: makeUnsignedTx(1, 1, 1, 1),
- err: nil,
- },
- { // New account, 2 pooled tx with 42200 wei spent: reject nonce 2 with 21100 wei spend (1 wei overflow)
- from: "alice",
- tx: makeUnsignedTx(2, 1, 1, 1),
- err: core.ErrInsufficientFunds,
- },
- },
- },
- // Transactions should only be accepted into the pool if the total count
- // from the same account doesn't overflow the pool limits
- {
- seeds: map[string]seed{
- "alice": {balance: 10000000},
- },
- adds: []addtx{
- { // New account, no previous txs, 16 slots left: accept nonce 0
- from: "alice",
- tx: makeUnsignedTx(0, 1, 1, 1),
- err: nil,
- },
- { // New account, 1 pooled tx, 15 slots left: accept nonce 1
- from: "alice",
- tx: makeUnsignedTx(1, 1, 1, 1),
- err: nil,
- },
- { // New account, 2 pooled tx, 14 slots left: accept nonce 2
- from: "alice",
- tx: makeUnsignedTx(2, 1, 1, 1),
- err: nil,
- },
- { // New account, 3 pooled tx, 13 slots left: accept nonce 3
- from: "alice",
- tx: makeUnsignedTx(3, 1, 1, 1),
- err: nil,
- },
- { // New account, 4 pooled tx, 12 slots left: accept nonce 4
- from: "alice",
- tx: makeUnsignedTx(4, 1, 1, 1),
- err: nil,
- },
- { // New account, 5 pooled tx, 11 slots left: accept nonce 5
- from: "alice",
- tx: makeUnsignedTx(5, 1, 1, 1),
- err: nil,
- },
- { // New account, 6 pooled tx, 10 slots left: accept nonce 6
- from: "alice",
- tx: makeUnsignedTx(6, 1, 1, 1),
- err: nil,
- },
- { // New account, 7 pooled tx, 9 slots left: accept nonce 7
- from: "alice",
- tx: makeUnsignedTx(7, 1, 1, 1),
- err: nil,
- },
- { // New account, 8 pooled tx, 8 slots left: accept nonce 8
- from: "alice",
- tx: makeUnsignedTx(8, 1, 1, 1),
- err: nil,
- },
- { // New account, 9 pooled tx, 7 slots left: accept nonce 9
- from: "alice",
- tx: makeUnsignedTx(9, 1, 1, 1),
- err: nil,
- },
- { // New account, 10 pooled tx, 6 slots left: accept nonce 10
- from: "alice",
- tx: makeUnsignedTx(10, 1, 1, 1),
- err: nil,
- },
- { // New account, 11 pooled tx, 5 slots left: accept nonce 11
- from: "alice",
- tx: makeUnsignedTx(11, 1, 1, 1),
- err: nil,
- },
- { // New account, 12 pooled tx, 4 slots left: accept nonce 12
- from: "alice",
- tx: makeUnsignedTx(12, 1, 1, 1),
- err: nil,
- },
- { // New account, 13 pooled tx, 3 slots left: accept nonce 13
- from: "alice",
- tx: makeUnsignedTx(13, 1, 1, 1),
- err: nil,
- },
- { // New account, 14 pooled tx, 2 slots left: accept nonce 14
- from: "alice",
- tx: makeUnsignedTx(14, 1, 1, 1),
- err: nil,
- },
- { // New account, 15 pooled tx, 1 slots left: accept nonce 15
- from: "alice",
- tx: makeUnsignedTx(15, 1, 1, 1),
- err: nil,
- },
- { // New account, 16 pooled tx, 0 slots left: accept nonce 15 replacement
- from: "alice",
- tx: makeUnsignedTx(15, 10, 10, 10),
- err: nil,
- },
- { // New account, 16 pooled tx, 0 slots left: reject nonce 16 with overcap
- from: "alice",
- tx: makeUnsignedTx(16, 1, 1, 1),
- err: txpool.ErrAccountLimitExceeded,
- },
- },
- },
- // Previously existing transactions should be allowed to be replaced iff
- // the new cumulative expenditure can be covered by the account and the
- // prices are bumped all around (no percentage check here).
- {
- seeds: map[string]seed{
- "alice": {balance: 2*100 + 5*21000 + 3*blobSize},
- },
- adds: []addtx{
- { // New account, no previous txs: reject nonce 0 with 341172 wei spend
- from: "alice",
- tx: makeUnsignedTx(0, 1, 20, 1),
- err: core.ErrInsufficientFunds,
- },
- { // New account, no previous txs: accept nonce 0 with 173172 wei spend
- from: "alice",
- tx: makeUnsignedTx(0, 1, 2, 1),
- err: nil,
- },
- { // New account, 1 pooled tx with 173172 wei spent: accept nonce 1 with 152172 wei spend
- from: "alice",
- tx: makeUnsignedTx(1, 1, 1, 1),
- err: nil,
- },
- { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with 599684 wei spend (173072 extra) (would overflow balance at nonce 1)
- from: "alice",
- tx: makeUnsignedTx(0, 2, 5, 2),
- err: core.ErrInsufficientFunds,
- },
- { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-gastip-bump
- from: "alice",
- tx: makeUnsignedTx(0, 1, 3, 2),
- err: txpool.ErrReplaceUnderpriced,
- },
- { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-gascap-bump
- from: "alice",
- tx: makeUnsignedTx(0, 2, 2, 2),
- err: txpool.ErrReplaceUnderpriced,
- },
- { // New account, 2 pooled tx with 325344 wei spent: reject nonce 0 with no-blobcap-bump
- from: "alice",
- tx: makeUnsignedTx(0, 2, 4, 1),
- err: txpool.ErrReplaceUnderpriced,
- },
- { // New account, 2 pooled tx with 325344 wei spent: accept nonce 0 with 84100 wei spend (42000 extra)
- from: "alice",
- tx: makeUnsignedTx(0, 2, 4, 2),
- err: nil,
- },
- },
- },
- // Previously existing transactions should be allowed to be replaced iff
- // the new prices are bumped by a sufficient amount.
- {
- seeds: map[string]seed{
- "alice": {balance: 100 + 8*21000 + 4*blobSize},
- },
- adds: []addtx{
- { // New account, no previous txs: accept nonce 0
- from: "alice",
- tx: makeUnsignedTx(0, 2, 4, 2),
- err: nil,
- },
- { // New account, 1 pooled tx: reject nonce 0 with low-gastip-bump
- from: "alice",
- tx: makeUnsignedTx(0, 3, 8, 4),
- err: txpool.ErrReplaceUnderpriced,
- },
- { // New account, 1 pooled tx: reject nonce 0 with low-gascap-bump
- from: "alice",
- tx: makeUnsignedTx(0, 4, 6, 4),
- err: txpool.ErrReplaceUnderpriced,
- },
- { // New account, 1 pooled tx: reject nonce 0 with low-blobcap-bump
- from: "alice",
- tx: makeUnsignedTx(0, 4, 8, 3),
- err: txpool.ErrReplaceUnderpriced,
- },
- { // New account, 1 pooled tx: accept nonce 0 with all-bumps
- from: "alice",
- tx: makeUnsignedTx(0, 4, 8, 4),
- err: nil,
- },
- },
- },
- }
- for i, tt := range tests {
- // Create a temporary folder for the persistent backend
- storage, _ := os.MkdirTemp("", "blobpool-")
- defer os.RemoveAll(storage) // late defer, still ok
-
- os.MkdirAll(filepath.Join(storage, pendingTransactionStore), 0700)
- store, _ := billy.Open(billy.Options{Path: filepath.Join(storage, pendingTransactionStore)}, newSlotter(), nil)
-
- // Insert the seed transactions for the pool startup
- var (
- keys = make(map[string]*ecdsa.PrivateKey)
- addrs = make(map[string]common.Address)
- )
- statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewDatabase(memorydb.New())), nil)
- for acc, seed := range tt.seeds {
- // Generate a new random key/address for the seed account
- keys[acc], _ = crypto.GenerateKey()
- addrs[acc] = crypto.PubkeyToAddress(keys[acc].PublicKey)
-
- // Seed the state database with this acocunt
- statedb.AddBalance(addrs[acc], new(big.Int).SetUint64(seed.balance))
- statedb.SetNonce(addrs[acc], seed.nonce)
-
- // Sign the seed transactions and store them in the data store
- for _, tx := range seed.txs {
- var (
- signed, _ = types.SignNewTx(keys[acc], types.LatestSigner(testChainConfig), tx)
- blob, _ = rlp.EncodeToBytes(&blobTx{Tx: signed, Blobs: []kzg4844.Blob{emptyBlob}, Commits: []kzg4844.Commitment{emptyBlobCommit}, Proofs: []kzg4844.Proof{emptyBlobProof}})
- )
- store.Put(blob)
- }
- }
- statedb.Commit(0, true)
- store.Close()
-
- // Create a blob pool out of the pre-seeded dats
- chain := &testBlockChain{
- config: testChainConfig,
- basefee: uint256.NewInt(1050),
- blobfee: uint256.NewInt(105),
- statedb: statedb,
- }
- pool := New(Config{Datadir: storage}, chain)
- if err := pool.Init(big.NewInt(1), chain.CurrentBlock(), makeAddressReserver()); err != nil {
- t.Fatalf("test %d: failed to create blob pool: %v", i, err)
- }
- verifyPoolInternals(t, pool)
-
- // Add each transaction one by one, verifying the pool internals in between
- for j, add := range tt.adds {
- signed, _ := types.SignNewTx(keys[add.from], types.LatestSigner(testChainConfig), add.tx)
- if err := pool.add(signed, []kzg4844.Blob{emptyBlob}, []kzg4844.Commitment{emptyBlobCommit}, []kzg4844.Proof{emptyBlobProof}); !errors.Is(err, add.err) {
- t.Errorf("test %d, tx %d: adding transaction error mismatch: have %v, want %v", i, j, err, add.err)
- }
- verifyPoolInternals(t, pool)
- }
- // Verify the pool internals and close down the test
- verifyPoolInternals(t, pool)
- pool.Close()
- }
-}
diff --git a/core/txpool/blobpool/config.go b/core/txpool/blobpool/config.go
deleted file mode 100644
index 99a2002a303f8..0000000000000
--- a/core/txpool/blobpool/config.go
+++ /dev/null
@@ -1,50 +0,0 @@
-// Copyright 2022 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-import (
- "github.com/ethereum/go-ethereum/log"
-)
-
-// Config are the configuration parameters of the blob transaction pool.
-type Config struct {
- Datadir string // Data directory containing the currently executable blobs
- Datacap uint64 // Soft-cap of database storage (hard cap is larger due to overhead)
- PriceBump uint64 // Minimum price bump percentage to replace an already existing nonce
-}
-
-// DefaultConfig contains the default configurations for the transaction pool.
-var DefaultConfig = Config{
- Datadir: "blobpool",
- Datacap: 10 * 1024 * 1024 * 1024,
- PriceBump: 100, // either have patience or be aggressive, no mushy ground
-}
-
-// sanitize checks the provided user configurations and changes anything that's
-// unreasonable or unworkable.
-func (config *Config) sanitize() Config {
- conf := *config
- if conf.Datacap < 1 {
- log.Warn("Sanitizing invalid blobpool storage cap", "provided", conf.Datacap, "updated", DefaultConfig.Datacap)
- conf.Datacap = DefaultConfig.Datacap
- }
- if conf.PriceBump < 1 {
- log.Warn("Sanitizing invalid blobpool price bump", "provided", conf.PriceBump, "updated", DefaultConfig.PriceBump)
- conf.PriceBump = DefaultConfig.PriceBump
- }
- return conf
-}
diff --git a/core/txpool/blobpool/evictheap.go b/core/txpool/blobpool/evictheap.go
deleted file mode 100644
index 7607a911c15bf..0000000000000
--- a/core/txpool/blobpool/evictheap.go
+++ /dev/null
@@ -1,146 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-import (
- "bytes"
- "container/heap"
- "math"
- "sort"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/holiman/uint256"
-)
-
-// evictHeap is a helper data structure to keep track of the cheapest bottleneck
-// transaction from each account to determine which account to evict from.
-//
-// The heap internally tracks a slice of cheapest transactions from each account
-// and a mapping from addresses to indices for direct removals/udates.
-//
-// The goal of the heap is to decide which account has the worst bottleneck to
-// evict transactions from.
-type evictHeap struct {
- metas *map[common.Address][]*blobTxMeta // Pointer to the blob pool's index for price retrievals
-
- basefeeJumps float64 // Pre-calculated absolute dynamic fee jumps for the base fee
- blobfeeJumps float64 // Pre-calculated absolute dynamic fee jumps for the blob fee
-
- addrs []common.Address // Heap of addresses to retrieve the cheapest out of
- index map[common.Address]int // Indices into the heap for replacements
-}
-
-// newPriceHeap creates a new heap of cheapets accounts in the blob pool to evict
-// from in case of over saturation.
-func newPriceHeap(basefee *uint256.Int, blobfee *uint256.Int, index *map[common.Address][]*blobTxMeta) *evictHeap {
- heap := &evictHeap{
- metas: index,
- index: make(map[common.Address]int),
- }
- // Populate the heap in account sort order. Not really needed in practice,
- // but it makes the heap initialization deterministic and less annoying to
- // test in unit tests.
- addrs := make([]common.Address, 0, len(*index))
- for addr := range *index {
- addrs = append(addrs, addr)
- }
- sort.Slice(addrs, func(i, j int) bool { return bytes.Compare(addrs[i][:], addrs[j][:]) < 0 })
-
- for _, addr := range addrs {
- heap.index[addr] = len(heap.addrs)
- heap.addrs = append(heap.addrs, addr)
- }
- heap.reinit(basefee, blobfee, true)
- return heap
-}
-
-// reinit updates the pre-calculated dynamic fee jumps in the price heap and runs
-// the sorting algorithm from scratch on the entire heap.
-func (h *evictHeap) reinit(basefee *uint256.Int, blobfee *uint256.Int, force bool) {
- // If the update is mostly the same as the old, don't sort pointlessly
- basefeeJumps := dynamicFeeJumps(basefee)
- blobfeeJumps := dynamicFeeJumps(blobfee)
-
- if !force && math.Abs(h.basefeeJumps-basefeeJumps) < 0.01 && math.Abs(h.blobfeeJumps-blobfeeJumps) < 0.01 { // TODO(karalabe): 0.01 enough, maybe should be smaller? Maybe this optimization is moot?
- return
- }
- // One or both of the dynamic fees jumped, resort the pool
- h.basefeeJumps = basefeeJumps
- h.blobfeeJumps = blobfeeJumps
-
- heap.Init(h)
-}
-
-// Len implements sort.Interface as part of heap.Interface, returning the number
-// of accounts in the pool which can be considered for eviction.
-func (h *evictHeap) Len() int {
- return len(h.addrs)
-}
-
-// Less implements sort.Interface as part of heap.Interface, returning which of
-// the two requested accounts has a cheaper bottleneck.
-func (h *evictHeap) Less(i, j int) bool {
- txsI := (*(h.metas))[h.addrs[i]]
- txsJ := (*(h.metas))[h.addrs[j]]
-
- lastI := txsI[len(txsI)-1]
- lastJ := txsJ[len(txsJ)-1]
-
- prioI := evictionPriority(h.basefeeJumps, lastI.evictionExecFeeJumps, h.blobfeeJumps, lastI.evictionBlobFeeJumps)
- if prioI > 0 {
- prioI = 0
- }
- prioJ := evictionPriority(h.basefeeJumps, lastJ.evictionExecFeeJumps, h.blobfeeJumps, lastJ.evictionBlobFeeJumps)
- if prioJ > 0 {
- prioJ = 0
- }
- if prioI == prioJ {
- return lastI.evictionExecTip.Lt(lastJ.evictionExecTip)
- }
- return prioI < prioJ
-}
-
-// Swap implements sort.Interface as part of heap.Interface, maintaining both the
-// order of the accounts according to the heap, and the account->item slot mapping
-// for replacements.
-func (h *evictHeap) Swap(i, j int) {
- h.index[h.addrs[i]], h.index[h.addrs[j]] = h.index[h.addrs[j]], h.index[h.addrs[i]]
- h.addrs[i], h.addrs[j] = h.addrs[j], h.addrs[i]
-}
-
-// Push implements heap.Interface, appending an item to the end of the account
-// ordering as well as the address to item slot mapping.
-func (h *evictHeap) Push(x any) {
- h.index[x.(common.Address)] = len(h.addrs)
- h.addrs = append(h.addrs, x.(common.Address))
-}
-
-// Pop implements heap.Interface, removing and returning the last element of the
-// heap.
-//
-// Note, use `heap.Pop`, not `evictHeap.Pop`. This method is used by Go's heap,
-// to provide the functionality, it does not embed it.
-func (h *evictHeap) Pop() any {
- // Remove the last element from the heap
- size := len(h.addrs)
- addr := h.addrs[size-1]
- h.addrs = h.addrs[:size-1]
-
- // Unindex the removed element and return
- delete(h.index, addr)
- return addr
-}
diff --git a/core/txpool/blobpool/evictheap_test.go b/core/txpool/blobpool/evictheap_test.go
deleted file mode 100644
index 01b136551cf2a..0000000000000
--- a/core/txpool/blobpool/evictheap_test.go
+++ /dev/null
@@ -1,320 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-import (
- "container/heap"
- mrand "math/rand"
- "testing"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/params"
- "github.com/holiman/uint256"
-)
-
-var rand = mrand.New(mrand.NewSource(1))
-
-// verifyHeapInternals verifies that all accounts present in the index are also
-// present in the heap and internals are consistent across various indices.
-func verifyHeapInternals(t *testing.T, evict *evictHeap) {
- t.Helper()
-
- // Ensure that all accounts are present in the heap and no extras
- seen := make(map[common.Address]struct{})
- for i, addr := range evict.addrs {
- seen[addr] = struct{}{}
- if _, ok := (*evict.metas)[addr]; !ok {
- t.Errorf("heap contains unexpected address at slot %d: %v", i, addr)
- }
- }
- for addr := range *evict.metas {
- if _, ok := seen[addr]; !ok {
- t.Errorf("heap is missing required address %v", addr)
- }
- }
- if len(evict.addrs) != len(*evict.metas) {
- t.Errorf("heap size %d mismatches metadata size %d", len(evict.addrs), len(*evict.metas))
- }
- // Ensure that all accounts are present in the heap order index and no extras
- have := make([]common.Address, len(evict.index))
- for addr, i := range evict.index {
- have[i] = addr
- }
- if len(have) != len(evict.addrs) {
- t.Errorf("heap index size %d mismatches heap size %d", len(have), len(evict.addrs))
- }
- for i := 0; i < len(have) && i < len(evict.addrs); i++ {
- if have[i] != evict.addrs[i] {
- t.Errorf("heap index for slot %d mismatches: have %v, want %v", i, have[i], evict.addrs[i])
- }
- }
-}
-
-// Tests that the price heap can correctly sort its set of transactions based on
-// an input base- and blob fee.
-func TestPriceHeapSorting(t *testing.T) {
- tests := []struct {
- execTips []uint64
- execFees []uint64
- blobFees []uint64
-
- basefee uint64
- blobfee uint64
-
- order []int
- }{
- // If everything is above the basefee and blobfee, order by miner tip
- {
- execTips: []uint64{1, 0, 2},
- execFees: []uint64{1, 2, 3},
- blobFees: []uint64{3, 2, 1},
- basefee: 0,
- blobfee: 0,
- order: []int{1, 0, 2},
- },
- // If only basefees are used (blob fee matches with network), return the
- // ones the furthest below the current basefee, splitting same ones with
- // the tip. Anything above the basefee should be split by tip.
- {
- execTips: []uint64{100, 50, 100, 50, 1, 2, 3},
- execFees: []uint64{1000, 1000, 500, 500, 2000, 2000, 2000},
- blobFees: []uint64{0, 0, 0, 0, 0, 0, 0},
- basefee: 1999,
- blobfee: 0,
- order: []int{3, 2, 1, 0, 4, 5, 6},
- },
- // If only blobfees are used (base fee matches with network), return the
- // ones the furthest below the current blobfee, splitting same ones with
- // the tip. Anything above the blobfee should be split by tip.
- {
- execTips: []uint64{100, 50, 100, 50, 1, 2, 3},
- execFees: []uint64{0, 0, 0, 0, 0, 0, 0},
- blobFees: []uint64{1000, 1000, 500, 500, 2000, 2000, 2000},
- basefee: 0,
- blobfee: 1999,
- order: []int{3, 2, 1, 0, 4, 5, 6},
- },
- // If both basefee and blobfee is specified, sort by the larger distance
- // of the two from the current network conditions, splitting same (loglog)
- // ones via the tip.
- //
- // Basefee: 1000
- // Blobfee: 100
- //
- // Tx #0: (800, 80) - 2 jumps below both => priority -1
- // Tx #1: (630, 63) - 4 jumps below both => priority -2
- // Tx #2: (800, 63) - 2 jumps below basefee, 4 jumps below blobfee => priority -2 (blob penalty dominates)
- // Tx #3: (630, 80) - 4 jumps below basefee, 2 jumps below blobfee => priority -2 (base penalty dominates)
- //
- // Txs 1, 2, 3 share the same priority, split via tip, prefer 0 as the best
- {
- execTips: []uint64{1, 2, 3, 4},
- execFees: []uint64{800, 630, 800, 630},
- blobFees: []uint64{80, 63, 63, 80},
- basefee: 1000,
- blobfee: 100,
- order: []int{1, 2, 3, 0},
- },
- }
- for i, tt := range tests {
- // Create an index of the transactions
- index := make(map[common.Address][]*blobTxMeta)
- for j := byte(0); j < byte(len(tt.execTips)); j++ {
- addr := common.Address{j}
-
- var (
- execTip = uint256.NewInt(tt.execTips[j])
- execFee = uint256.NewInt(tt.execFees[j])
- blobFee = uint256.NewInt(tt.blobFees[j])
-
- basefeeJumps = dynamicFeeJumps(execFee)
- blobfeeJumps = dynamicFeeJumps(blobFee)
- )
- index[addr] = []*blobTxMeta{{
- id: uint64(j),
- size: 128 * 1024,
- nonce: 0,
- execTipCap: execTip,
- execFeeCap: execFee,
- blobFeeCap: blobFee,
- basefeeJumps: basefeeJumps,
- blobfeeJumps: blobfeeJumps,
- evictionExecTip: execTip,
- evictionExecFeeJumps: basefeeJumps,
- evictionBlobFeeJumps: blobfeeJumps,
- }}
- }
- // Create a price heap and check the pop order
- priceheap := newPriceHeap(uint256.NewInt(tt.basefee), uint256.NewInt(tt.blobfee), &index)
- verifyHeapInternals(t, priceheap)
-
- for j := 0; j < len(tt.order); j++ {
- if next := heap.Pop(priceheap); int(next.(common.Address)[0]) != tt.order[j] {
- t.Errorf("test %d, item %d: order mismatch: have %d, want %d", i, j, next.(common.Address)[0], tt.order[j])
- } else {
- delete(index, next.(common.Address)) // remove to simulate a correct pool for the test
- }
- verifyHeapInternals(t, priceheap)
- }
- }
-}
-
-// Benchmarks reheaping the entire set of accounts in the blob pool.
-func BenchmarkPriceHeapReinit1MB(b *testing.B) { benchmarkPriceHeapReinit(b, 1024*1024) }
-func BenchmarkPriceHeapReinit10MB(b *testing.B) { benchmarkPriceHeapReinit(b, 10*1024*1024) }
-func BenchmarkPriceHeapReinit100MB(b *testing.B) { benchmarkPriceHeapReinit(b, 100*1024*1024) }
-func BenchmarkPriceHeapReinit1GB(b *testing.B) { benchmarkPriceHeapReinit(b, 1024*1024*1024) }
-func BenchmarkPriceHeapReinit10GB(b *testing.B) { benchmarkPriceHeapReinit(b, 10*1024*1024*1024) }
-func BenchmarkPriceHeapReinit25GB(b *testing.B) { benchmarkPriceHeapReinit(b, 25*1024*1024*1024) }
-func BenchmarkPriceHeapReinit50GB(b *testing.B) { benchmarkPriceHeapReinit(b, 50*1024*1024*1024) }
-func BenchmarkPriceHeapReinit100GB(b *testing.B) { benchmarkPriceHeapReinit(b, 100*1024*1024*1024) }
-
-func benchmarkPriceHeapReinit(b *testing.B, datacap uint64) {
- // Calculate how many unique transactions we can fit into the provided disk
- // data cap
- blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob)
-
- // Create a random set of transactions with random fees. Use a separate account
- // for each transaction to make it worse case.
- index := make(map[common.Address][]*blobTxMeta)
- for i := 0; i < int(blobs); i++ {
- var addr common.Address
- rand.Read(addr[:])
-
- var (
- execTip = uint256.NewInt(rand.Uint64())
- execFee = uint256.NewInt(rand.Uint64())
- blobFee = uint256.NewInt(rand.Uint64())
-
- basefeeJumps = dynamicFeeJumps(execFee)
- blobfeeJumps = dynamicFeeJumps(blobFee)
- )
- index[addr] = []*blobTxMeta{{
- id: uint64(i),
- size: 128 * 1024,
- nonce: 0,
- execTipCap: execTip,
- execFeeCap: execFee,
- blobFeeCap: blobFee,
- basefeeJumps: basefeeJumps,
- blobfeeJumps: blobfeeJumps,
- evictionExecTip: execTip,
- evictionExecFeeJumps: basefeeJumps,
- evictionBlobFeeJumps: blobfeeJumps,
- }}
- }
- // Create a price heap and reinit it over and over
- heap := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), &index)
-
- basefees := make([]*uint256.Int, b.N)
- blobfees := make([]*uint256.Int, b.N)
- for i := 0; i < b.N; i++ {
- basefees[i] = uint256.NewInt(rand.Uint64())
- blobfees[i] = uint256.NewInt(rand.Uint64())
- }
- b.ResetTimer()
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- heap.reinit(basefees[i], blobfees[i], true)
- }
-}
-
-// Benchmarks overflowing the heap over and over (add and then drop).
-func BenchmarkPriceHeapOverflow1MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 1024*1024) }
-func BenchmarkPriceHeapOverflow10MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 10*1024*1024) }
-func BenchmarkPriceHeapOverflow100MB(b *testing.B) { benchmarkPriceHeapOverflow(b, 100*1024*1024) }
-func BenchmarkPriceHeapOverflow1GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 1024*1024*1024) }
-func BenchmarkPriceHeapOverflow10GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 10*1024*1024*1024) }
-func BenchmarkPriceHeapOverflow25GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 25*1024*1024*1024) }
-func BenchmarkPriceHeapOverflow50GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 50*1024*1024*1024) }
-func BenchmarkPriceHeapOverflow100GB(b *testing.B) { benchmarkPriceHeapOverflow(b, 100*1024*1024*1024) }
-
-func benchmarkPriceHeapOverflow(b *testing.B, datacap uint64) {
- // Calculate how many unique transactions we can fit into the provided disk
- // data cap
- blobs := datacap / (params.BlobTxBytesPerFieldElement * params.BlobTxFieldElementsPerBlob)
-
- // Create a random set of transactions with random fees. Use a separate account
- // for each transaction to make it worse case.
- index := make(map[common.Address][]*blobTxMeta)
- for i := 0; i < int(blobs); i++ {
- var addr common.Address
- rand.Read(addr[:])
-
- var (
- execTip = uint256.NewInt(rand.Uint64())
- execFee = uint256.NewInt(rand.Uint64())
- blobFee = uint256.NewInt(rand.Uint64())
-
- basefeeJumps = dynamicFeeJumps(execFee)
- blobfeeJumps = dynamicFeeJumps(blobFee)
- )
- index[addr] = []*blobTxMeta{{
- id: uint64(i),
- size: 128 * 1024,
- nonce: 0,
- execTipCap: execTip,
- execFeeCap: execFee,
- blobFeeCap: blobFee,
- basefeeJumps: basefeeJumps,
- blobfeeJumps: blobfeeJumps,
- evictionExecTip: execTip,
- evictionExecFeeJumps: basefeeJumps,
- evictionBlobFeeJumps: blobfeeJumps,
- }}
- }
- // Create a price heap and overflow it over and over
- evict := newPriceHeap(uint256.NewInt(rand.Uint64()), uint256.NewInt(rand.Uint64()), &index)
- var (
- addrs = make([]common.Address, b.N)
- metas = make([]*blobTxMeta, b.N)
- )
- for i := 0; i < b.N; i++ {
- rand.Read(addrs[i][:])
-
- var (
- execTip = uint256.NewInt(rand.Uint64())
- execFee = uint256.NewInt(rand.Uint64())
- blobFee = uint256.NewInt(rand.Uint64())
-
- basefeeJumps = dynamicFeeJumps(execFee)
- blobfeeJumps = dynamicFeeJumps(blobFee)
- )
- metas[i] = &blobTxMeta{
- id: uint64(int(blobs) + i),
- size: 128 * 1024,
- nonce: 0,
- execTipCap: execTip,
- execFeeCap: execFee,
- blobFeeCap: blobFee,
- basefeeJumps: basefeeJumps,
- blobfeeJumps: blobfeeJumps,
- evictionExecTip: execTip,
- evictionExecFeeJumps: basefeeJumps,
- evictionBlobFeeJumps: blobfeeJumps,
- }
- }
- b.ResetTimer()
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- index[addrs[i]] = []*blobTxMeta{metas[i]}
- heap.Push(evict, addrs[i])
-
- drop := heap.Pop(evict)
- delete(index, drop.(common.Address))
- }
-}
diff --git a/core/txpool/blobpool/interface.go b/core/txpool/blobpool/interface.go
deleted file mode 100644
index 6f296a54bd63d..0000000000000
--- a/core/txpool/blobpool/interface.go
+++ /dev/null
@@ -1,44 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-import (
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/params"
-)
-
-// BlockChain defines the minimal set of methods needed to back a blob pool with
-// a chain. Exists to allow mocking the live chain out of tests.
-type BlockChain interface {
- // Config retrieves the chain's fork configuration.
- Config() *params.ChainConfig
-
- // CurrentBlock returns the current head of the chain.
- CurrentBlock() *types.Header
-
- // CurrentFinalBlock returns the current block below which blobs should not
- // be maintained anymore for reorg purposes.
- CurrentFinalBlock() *types.Header
-
- // GetBlock retrieves a specific block, used during pool resets.
- GetBlock(hash common.Hash, number uint64) *types.Block
-
- // StateAt returns a state database for a given root hash (generally the head).
- StateAt(root common.Hash) (*state.StateDB, error)
-}
diff --git a/core/txpool/blobpool/limbo.go b/core/txpool/blobpool/limbo.go
deleted file mode 100644
index 4cb5042c2bb5a..0000000000000
--- a/core/txpool/blobpool/limbo.go
+++ /dev/null
@@ -1,258 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-import (
- "errors"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto/kzg4844"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/rlp"
- "github.com/holiman/billy"
-)
-
-// limboBlob is a wrapper around an opaque blobset that also contains the tx hash
-// to which it belongs as well as the block number in which it was included for
-// finality eviction.
-type limboBlob struct {
- Owner common.Hash // Owner transaction's hash to support resurrecting reorged txs
- Block uint64 // Block in which the blob transaction was included
-
- Blobs []kzg4844.Blob // The opaque blobs originally part of the transaction
- Commits []kzg4844.Commitment // The commitments for the original blobs
- Proofs []kzg4844.Proof // The proofs verifying the commitments
-}
-
-// limbo is a light, indexed database to temporarily store recently included
-// blobs until they are finalized. The purpose is to support small reorgs, which
-// would require pulling back up old blobs (which aren't part of the chain).
-//
-// TODO(karalabe): Currently updating the inclusion block of a blob needs a full db rewrite. Can we do without?
-type limbo struct {
- store billy.Database // Persistent data store for limboed blobs
-
- index map[common.Hash]uint64 // Mappings from tx hashes to datastore ids
- groups map[uint64]map[uint64]common.Hash // Set of txs included in past blocks
-}
-
-// newLimbo opens and indexes a set of limboed blob transactions.
-func newLimbo(datadir string) (*limbo, error) {
- l := &limbo{
- index: make(map[common.Hash]uint64),
- groups: make(map[uint64]map[uint64]common.Hash),
- }
- // Index all limboed blobs on disk and delete anything inprocessable
- var fails []uint64
- index := func(id uint64, size uint32, data []byte) {
- if l.parseBlob(id, data) != nil {
- fails = append(fails, id)
- }
- }
- store, err := billy.Open(billy.Options{Path: datadir}, newSlotter(), index)
- if err != nil {
- return nil, err
- }
- l.store = store
-
- if len(fails) > 0 {
- log.Warn("Dropping invalidated limboed blobs", "ids", fails)
- for _, id := range fails {
- if err := l.store.Delete(id); err != nil {
- l.Close()
- return nil, err
- }
- }
- }
- return l, nil
-}
-
-// Close closes down the underlying persistent store.
-func (l *limbo) Close() error {
- return l.store.Close()
-}
-
-// parseBlob is a callback method on limbo creation that gets called for each
-// limboed blob on disk to create the in-memory metadata index.
-func (l *limbo) parseBlob(id uint64, data []byte) error {
- item := new(limboBlob)
- if err := rlp.DecodeBytes(data, item); err != nil {
- // This path is impossible unless the disk data representation changes
- // across restarts. For that ever unprobable case, recover gracefully
- // by ignoring this data entry.
- log.Error("Failed to decode blob limbo entry", "id", id, "err", err)
- return err
- }
- if _, ok := l.index[item.Owner]; ok {
- // This path is impossible, unless due to a programming error a blob gets
- // inserted into the limbo which was already part of if. Recover gracefully
- // by ignoring this data entry.
- log.Error("Dropping duplicate blob limbo entry", "owner", item.Owner, "id", id)
- return errors.New("duplicate blob")
- }
- l.index[item.Owner] = id
-
- if _, ok := l.groups[item.Block]; !ok {
- l.groups[item.Block] = make(map[uint64]common.Hash)
- }
- l.groups[item.Block][id] = item.Owner
-
- return nil
-}
-
-// finalize evicts all blobs belonging to a recently finalized block or older.
-func (l *limbo) finalize(final *types.Header) {
- // Just in case there's no final block yet (network not yet merged, weird
- // restart, sethead, etc), fail gracefully.
- if final == nil {
- log.Error("Nil finalized block cannot evict old blobs")
- return
- }
- for block, ids := range l.groups {
- if block > final.Number.Uint64() {
- continue
- }
- for id, owner := range ids {
- if err := l.store.Delete(id); err != nil {
- log.Error("Failed to drop finalized blob", "block", block, "id", id, "err", err)
- }
- delete(l.index, owner)
- }
- delete(l.groups, block)
- }
-}
-
-// push stores a new blob transaction into the limbo, waiting until finality for
-// it to be automatically evicted.
-func (l *limbo) push(tx common.Hash, block uint64, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error {
- // If the blobs are already tracked by the limbo, consider it a programming
- // error. There's not much to do against it, but be loud.
- if _, ok := l.index[tx]; ok {
- log.Error("Limbo cannot push already tracked blobs", "tx", tx)
- return errors.New("already tracked blob transaction")
- }
- if err := l.setAndIndex(tx, block, blobs, commits, proofs); err != nil {
- log.Error("Failed to set and index liboed blobs", "tx", tx, "err", err)
- return err
- }
- return nil
-}
-
-// pull retrieves a previously pushed set of blobs back from the limbo, removing
-// it at the same time. This method should be used when a previously included blob
-// transaction gets reorged out.
-func (l *limbo) pull(tx common.Hash) ([]kzg4844.Blob, []kzg4844.Commitment, []kzg4844.Proof, error) {
- // If the blobs are not tracked by the limbo, there's not much to do. This
- // can happen for example if a blob transaction is mined without pushing it
- // into the network first.
- id, ok := l.index[tx]
- if !ok {
- log.Trace("Limbo cannot pull non-tracked blobs", "tx", tx)
- return nil, nil, nil, errors.New("unseen blob transaction")
- }
- item, err := l.getAndDrop(id)
- if err != nil {
- log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
- return nil, nil, nil, err
- }
- return item.Blobs, item.Commits, item.Proofs, nil
-}
-
-// update changes the block number under which a blob transaction is tracked. This
-// method should be used when a reorg changes a transaction's inclusion block.
-//
-// The method may log errors for various unexpcted scenarios but will not return
-// any of it since there's no clear error case. Some errors may be due to coding
-// issues, others caused by signers mining MEV stuff or swapping transactions. In
-// all cases, the pool needs to continue operating.
-func (l *limbo) update(tx common.Hash, block uint64) {
- // If the blobs are not tracked by the limbo, there's not much to do. This
- // can happen for example if a blob transaction is mined without pushing it
- // into the network first.
- id, ok := l.index[tx]
- if !ok {
- log.Trace("Limbo cannot update non-tracked blobs", "tx", tx)
- return
- }
- // If there was no change in the blob's inclusion block, don't mess around
- // with heavy database operations.
- if _, ok := l.groups[block][id]; ok {
- log.Trace("Blob transaction unchanged in limbo", "tx", tx, "block", block)
- return
- }
- // Retrieve the old blobs from the data store and write tehm back with a new
- // block number. IF anything fails, there's not much to do, go on.
- item, err := l.getAndDrop(id)
- if err != nil {
- log.Error("Failed to get and drop limboed blobs", "tx", tx, "id", id, "err", err)
- return
- }
- if err := l.setAndIndex(tx, block, item.Blobs, item.Commits, item.Proofs); err != nil {
- log.Error("Failed to set and index limboed blobs", "tx", tx, "err", err)
- return
- }
- log.Trace("Blob transaction updated in limbo", "tx", tx, "old-block", item.Block, "new-block", block)
-}
-
-// getAndDrop retrieves a blob item from the limbo store and deletes it both from
-// the store and indices.
-func (l *limbo) getAndDrop(id uint64) (*limboBlob, error) {
- data, err := l.store.Get(id)
- if err != nil {
- return nil, err
- }
- item := new(limboBlob)
- if err = rlp.DecodeBytes(data, item); err != nil {
- return nil, err
- }
- delete(l.index, item.Owner)
- delete(l.groups[item.Block], id)
- if len(l.groups[item.Block]) == 0 {
- delete(l.groups, item.Block)
- }
- if err := l.store.Delete(id); err != nil {
- return nil, err
- }
- return item, nil
-}
-
-// setAndIndex assembles a limbo blob database entry and stores it, also updating
-// the in-memory indices.
-func (l *limbo) setAndIndex(tx common.Hash, block uint64, blobs []kzg4844.Blob, commits []kzg4844.Commitment, proofs []kzg4844.Proof) error {
- item := &limboBlob{
- Owner: tx,
- Block: block,
- Blobs: blobs,
- Commits: commits,
- Proofs: proofs,
- }
- data, err := rlp.EncodeToBytes(item)
- if err != nil {
- panic(err) // cannot happen runtime, dev error
- }
- id, err := l.store.Put(data)
- if err != nil {
- return err
- }
- l.index[tx] = id
- if _, ok := l.groups[block]; !ok {
- l.groups[block] = make(map[uint64]common.Hash)
- }
- l.groups[block][id] = tx
- return nil
-}
diff --git a/core/txpool/blobpool/metrics.go b/core/txpool/blobpool/metrics.go
deleted file mode 100644
index 280913b3a9166..0000000000000
--- a/core/txpool/blobpool/metrics.go
+++ /dev/null
@@ -1,78 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-import "github.com/ethereum/go-ethereum/metrics"
-
-var (
- // datacapGauge tracks the user's configured capacity for the blob pool. It
- // is mostly a way to expose/debug issues.
- datacapGauge = metrics.NewRegisteredGauge("blobpool/datacap", nil)
-
- // The below metrics track the per-datastore metrics for the primary blob
- // store and the temporary limbo store.
- datausedGauge = metrics.NewRegisteredGauge("blobpool/dataused", nil)
- datarealGauge = metrics.NewRegisteredGauge("blobpool/datareal", nil)
- slotusedGauge = metrics.NewRegisteredGauge("blobpool/slotused", nil)
-
- limboDatausedGauge = metrics.NewRegisteredGauge("blobpool/limbo/dataused", nil)
- limboDatarealGauge = metrics.NewRegisteredGauge("blobpool/limbo/datareal", nil)
- limboSlotusedGauge = metrics.NewRegisteredGauge("blobpool/limbo/slotused", nil)
-
- // The below metrics track the per-shelf metrics for the primary blob store
- // and the temporary limbo store.
- shelfDatausedGaugeName = "blobpool/shelf-%d/dataused"
- shelfDatagapsGaugeName = "blobpool/shelf-%d/datagaps"
- shelfSlotusedGaugeName = "blobpool/shelf-%d/slotused"
- shelfSlotgapsGaugeName = "blobpool/shelf-%d/slotgaps"
-
- limboShelfDatausedGaugeName = "blobpool/limbo/shelf-%d/dataused"
- limboShelfDatagapsGaugeName = "blobpool/limbo/shelf-%d/datagaps"
- limboShelfSlotusedGaugeName = "blobpool/limbo/shelf-%d/slotused"
- limboShelfSlotgapsGaugeName = "blobpool/limbo/shelf-%d/slotgaps"
-
- // The oversized metrics aggregate the shelf stats above the max blob count
- // limits to track transactions that are just huge, but don't contain blobs.
- //
- // There are no oversized data in the limbo, it only contains blobs and some
- // constant metadata.
- oversizedDatausedGauge = metrics.NewRegisteredGauge("blobpool/oversized/dataused", nil)
- oversizedDatagapsGauge = metrics.NewRegisteredGauge("blobpool/oversized/datagaps", nil)
- oversizedSlotusedGauge = metrics.NewRegisteredGauge("blobpool/oversized/slotused", nil)
- oversizedSlotgapsGauge = metrics.NewRegisteredGauge("blobpool/oversized/slotgaps", nil)
-
- // basefeeGauge and blobfeeGauge track the current network 1559 base fee and
- // 4844 blob fee respectively.
- basefeeGauge = metrics.NewRegisteredGauge("blobpool/basefee", nil)
- blobfeeGauge = metrics.NewRegisteredGauge("blobpool/blobfee", nil)
-
- // pooltipGauge is the configurable miner tip to permit a transaction into
- // the pool.
- pooltipGauge = metrics.NewRegisteredGauge("blobpool/pooltip", nil)
-
- // addwait/time, resetwait/time and getwait/time track the rough health of
- // the pool and wether or not it's capable of keeping up with the load from
- // the network.
- addwaitHist = metrics.NewRegisteredHistogram("blobpool/addwait", nil, metrics.NewExpDecaySample(1028, 0.015))
- addtimeHist = metrics.NewRegisteredHistogram("blobpool/addtime", nil, metrics.NewExpDecaySample(1028, 0.015))
- getwaitHist = metrics.NewRegisteredHistogram("blobpool/getwait", nil, metrics.NewExpDecaySample(1028, 0.015))
- gettimeHist = metrics.NewRegisteredHistogram("blobpool/gettime", nil, metrics.NewExpDecaySample(1028, 0.015))
- pendwaitHist = metrics.NewRegisteredHistogram("blobpool/pendwait", nil, metrics.NewExpDecaySample(1028, 0.015))
- pendtimeHist = metrics.NewRegisteredHistogram("blobpool/pendtime", nil, metrics.NewExpDecaySample(1028, 0.015))
- resetwaitHist = metrics.NewRegisteredHistogram("blobpool/resetwait", nil, metrics.NewExpDecaySample(1028, 0.015))
- resettimeHist = metrics.NewRegisteredHistogram("blobpool/resettime", nil, metrics.NewExpDecaySample(1028, 0.015))
-)
diff --git a/core/txpool/blobpool/priority.go b/core/txpool/blobpool/priority.go
deleted file mode 100644
index 18e545c2a8761..0000000000000
--- a/core/txpool/blobpool/priority.go
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-import (
- "math"
- "math/bits"
-
- "github.com/holiman/uint256"
-)
-
-// log2_1_125 is used in the eviction priority calculation.
-var log2_1_125 = math.Log2(1.125)
-
-// evictionPriority calculates the eviction priority based on the algorithm
-// described in the BlobPool docs for a both fee components.
-//
-// This method takes about 8ns on a very recent laptop CPU, recalculating about
-// 125 million transaction priority values per second.
-func evictionPriority(basefeeJumps float64, txBasefeeJumps, blobfeeJumps, txBlobfeeJumps float64) int {
- var (
- basefeePriority = evictionPriority1D(basefeeJumps, txBasefeeJumps)
- blobfeePriority = evictionPriority1D(blobfeeJumps, txBlobfeeJumps)
- )
- if basefeePriority < blobfeePriority {
- return basefeePriority
- }
- return blobfeePriority
-}
-
-// evictionPriority1D calculates the eviction priority based on the algorithm
-// described in the BlobPool docs for a single fee component.
-func evictionPriority1D(basefeeJumps float64, txfeeJumps float64) int {
- jumps := txfeeJumps - basefeeJumps
- if int(jumps) == 0 {
- return 0 // can't log2 0
- }
- if jumps < 0 {
- return -intLog2(uint(-math.Floor(jumps)))
- }
- return intLog2(uint(math.Ceil(jumps)))
-}
-
-// dynamicFeeJumps calculates the log1.125(fee), namely the number of fee jumps
-// needed to reach the requested one. We only use it when calculating the jumps
-// between 2 fees, so it doesn't matter from what exact number with returns.
-// it returns the result from (0, 1, 1.125).
-//
-// This method is very expensive, taking about 75ns on a very recent laptop CPU,
-// but the result does not change with the lifetime of a transaction, so it can
-// be cached.
-func dynamicFeeJumps(fee *uint256.Int) float64 {
- if fee.IsZero() {
- return 0 // can't log2 zero, should never happen outside tests, but don't choke
- }
- return math.Log2(fee.Float64()) / log2_1_125
-}
-
-// intLog2 is a helper to calculate the integral part of a log2 of an unsigned
-// integer. It is a very specific calculation that's not particularly useful in
-// general, but it's what we need here (it's fast).
-func intLog2(n uint) int {
- switch {
- case n == 0:
- panic("log2(0) is undefined")
-
- case n < 2048:
- return bits.UintSize - bits.LeadingZeros(n) - 1
-
- default:
- // The input is log1.125(uint256) = log2(uint256) / log2(1.125). At the
- // most extreme, log2(uint256) will be a bit below 257, and the constant
- // log2(1.125) ~= 0.17. The larges input thus is ~257 / ~0.17 ~= ~1511.
- panic("dynamic fee jump diffs cannot reach this")
- }
-}
diff --git a/core/txpool/blobpool/priority_test.go b/core/txpool/blobpool/priority_test.go
deleted file mode 100644
index 4aad919925f56..0000000000000
--- a/core/txpool/blobpool/priority_test.go
+++ /dev/null
@@ -1,87 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-import (
- "testing"
-
- "github.com/holiman/uint256"
-)
-
-// Tests that the priority fees are calculated correctly as the log2 of the fee
-// jumps needed to go from the base fee to the tx's fee cap.
-func TestPriorityCalculation(t *testing.T) {
- tests := []struct {
- basefee uint64
- txfee uint64
- result int
- }{
- {basefee: 7, txfee: 10, result: 2}, // 3.02 jumps, 4 ceil, 2 log2
- {basefee: 17_200_000_000, txfee: 17_200_000_000, result: 0}, // 0 jumps, special case 0 log2
- {basefee: 9_853_941_692, txfee: 11_085_092_510, result: 0}, // 0.99 jumps, 1 ceil, 0 log2
- {basefee: 11_544_106_391, txfee: 10_356_781_100, result: 0}, // -0.92 jumps, -1 floor, 0 log2
- {basefee: 17_200_000_000, txfee: 7, result: -7}, // -183.57 jumps, -184 floor, -7 log2
- {basefee: 7, txfee: 17_200_000_000, result: 7}, // 183.57 jumps, 184 ceil, 7 log2
- }
- for i, tt := range tests {
- var (
- baseJumps = dynamicFeeJumps(uint256.NewInt(tt.basefee))
- feeJumps = dynamicFeeJumps(uint256.NewInt(tt.txfee))
- )
- if prio := evictionPriority1D(baseJumps, feeJumps); prio != tt.result {
- t.Errorf("test %d priority mismatch: have %d, want %d", i, prio, tt.result)
- }
- }
-}
-
-// Benchmarks how many dynamic fee jump values can be done.
-func BenchmarkDynamicFeeJumpCalculation(b *testing.B) {
- fees := make([]*uint256.Int, b.N)
- for i := 0; i < b.N; i++ {
- fees[i] = uint256.NewInt(rand.Uint64())
- }
- b.ResetTimer()
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- dynamicFeeJumps(fees[i])
- }
-}
-
-// Benchmarks how many priority recalculations can be done.
-func BenchmarkPriorityCalculation(b *testing.B) {
- // The basefee and blob fee is constant for all transactions across a block,
- // so we can assume theit absolute jump counts can be pre-computed.
- basefee := uint256.NewInt(17_200_000_000) // 17.2 Gwei is the 22.03.2023 zero-emission basefee, random number
- blobfee := uint256.NewInt(123_456_789_000) // Completely random, no idea what this will be
-
- basefeeJumps := dynamicFeeJumps(basefee)
- blobfeeJumps := dynamicFeeJumps(blobfee)
-
- // The transaction's fee cap and blob fee cap are constant across the life
- // of the transaction, so we can pre-calculate and cache them.
- txBasefeeJumps := make([]float64, b.N)
- txBlobfeeJumps := make([]float64, b.N)
- for i := 0; i < b.N; i++ {
- txBasefeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rand.Uint64()))
- txBlobfeeJumps[i] = dynamicFeeJumps(uint256.NewInt(rand.Uint64()))
- }
- b.ResetTimer()
- b.ReportAllocs()
- for i := 0; i < b.N; i++ {
- evictionPriority(basefeeJumps, txBasefeeJumps[i], blobfeeJumps, txBlobfeeJumps[i])
- }
-}
diff --git a/core/txpool/blobpool/slotter.go b/core/txpool/blobpool/slotter.go
deleted file mode 100644
index 35349c3445cf5..0000000000000
--- a/core/txpool/blobpool/slotter.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-// newSlotter creates a helper method for the Billy datastore that returns the
-// individual shelf sizes used to store transactions in.
-//
-// The slotter will create shelves for each possible blob count + some tx metadata
-// wiggle room, up to the max permitted limits.
-//
-// The slotter also creates a shelf for 0-blob transactions. Whilst those are not
-// allowed in the current protocol, having an empty shelf is not a relevant use
-// of resources, but it makes stress testing with junk transactions simpler.
-func newSlotter() func() (uint32, bool) {
- slotsize := uint32(txAvgSize)
- slotsize -= uint32(blobSize) // underflows, it's ok, will overflow back in the first return
-
- return func() (size uint32, done bool) {
- slotsize += blobSize
- finished := slotsize > maxBlobsPerTransaction*blobSize+txMaxSize
-
- return slotsize, finished
- }
-}
diff --git a/core/txpool/blobpool/slotter_test.go b/core/txpool/blobpool/slotter_test.go
deleted file mode 100644
index 2751a1872541f..0000000000000
--- a/core/txpool/blobpool/slotter_test.go
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright 2023 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package blobpool
-
-import "testing"
-
-// Tests that the slotter creates the expected database shelves.
-func TestNewSlotter(t *testing.T) {
- // Generate the database shelve sizes
- slotter := newSlotter()
-
- var shelves []uint32
- for {
- shelf, done := slotter()
- shelves = append(shelves, shelf)
- if done {
- break
- }
- }
- // Compare the database shelves to the expected ones
- want := []uint32{
- 0*blobSize + txAvgSize, // 0 blob + some expected tx infos
- 1*blobSize + txAvgSize, // 1 blob + some expected tx infos
- 2*blobSize + txAvgSize, // 2 blob + some expected tx infos (could be fewer blobs and more tx data)
- 3*blobSize + txAvgSize, // 3 blob + some expected tx infos (could be fewer blobs and more tx data)
- 4*blobSize + txAvgSize, // 4 blob + some expected tx infos (could be fewer blobs and more tx data)
- 5*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
- 6*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
- 7*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
- 8*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
- 9*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
- 10*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
- 11*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos < 4 blobs + max tx metadata size
- 12*blobSize + txAvgSize, // 1-4 blobs + unexpectedly large tx infos >= 4 blobs + max tx metadata size
- }
- if len(shelves) != len(want) {
- t.Errorf("shelves count mismatch: have %d, want %d", len(shelves), len(want))
- }
- for i := 0; i < len(shelves) && i < len(want); i++ {
- if shelves[i] != want[i] {
- t.Errorf("shelf %d mismatch: have %d, want %d", i, shelves[i], want[i])
- }
- }
-}
diff --git a/core/txpool/errors.go b/core/txpool/errors.go
index bc26550f78ca2..b8c1c914a347d 100644
--- a/core/txpool/errors.go
+++ b/core/txpool/errors.go
@@ -34,10 +34,6 @@ var (
// with a different one without the required price bump.
ErrReplaceUnderpriced = errors.New("replacement transaction underpriced")
- // ErrAccountLimitExceeded is returned if a transaction would exceed the number
- // allowed by a pool for a single account.
- ErrAccountLimitExceeded = errors.New("account limit exceeded")
-
// 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")
diff --git a/core/txpool/legacypool/legacypool.go b/core/txpool/legacypool/legacypool.go
index 782f0facb5195..5b2f4eea09fac 100644
--- a/core/txpool/legacypool/legacypool.go
+++ b/core/txpool/legacypool/legacypool.go
@@ -219,7 +219,6 @@ type LegacyPool struct {
locals *accountSet // Set of local transaction to exempt from eviction rules
journal *journal // Journal of local transaction to back up to disk
- reserve txpool.AddressReserver // Address reserver to ensure exclusivity across subpools
pending map[common.Address]*list // All currently processable transactions
queue map[common.Address]*list // Queued but non-processable transactions
beats map[common.Address]time.Time // Last heartbeat from each known account
@@ -292,10 +291,7 @@ func (pool *LegacyPool) Filter(tx *types.Transaction) bool {
// head to allow balance / nonce checks. The transaction journal will be loaded
// from disk and filtered based on the provided starting settings. The internal
// goroutines will be spun up and the pool deemed operational afterwards.
-func (pool *LegacyPool) Init(gasTip *big.Int, head *types.Header, reserve txpool.AddressReserver) error {
- // Set the address reserver to request exclusive access to pooled accounts
- pool.reserve = reserve
-
+func (pool *LegacyPool) Init(gasTip *big.Int, head *types.Header) error {
// Set the basic pool parameters
pool.gasTip.Store(gasTip)
pool.reset(nil, head)
@@ -369,7 +365,7 @@ func (pool *LegacyPool) loop() {
if time.Since(pool.beats[addr]) > pool.config.Lifetime {
list := pool.queue[addr].Flatten()
for _, tx := range list {
- pool.removeTx(tx.Hash(), true, true)
+ pool.removeTx(tx.Hash(), true)
}
queuedEvictionMeter.Mark(int64(len(list)))
}
@@ -432,7 +428,7 @@ func (pool *LegacyPool) SetGasTip(tip *big.Int) {
// pool.priced is sorted by GasFeeCap, so we have to iterate through pool.all instead
drop := pool.all.RemotesBelowTip(tip)
for _, tx := range drop {
- pool.removeTx(tx.Hash(), false, true)
+ pool.removeTx(tx.Hash(), false)
}
pool.priced.Removed(len(drop))
}
@@ -512,11 +508,11 @@ func (pool *LegacyPool) ContentFrom(addr common.Address) ([]*types.Transaction,
// The enforceTips parameter can be used to do an extra filtering on the pending
// transactions and only return those whose **effective** tip is large enough in
// the next pending execution environment.
-func (pool *LegacyPool) Pending(enforceTips bool) map[common.Address][]*txpool.LazyTransaction {
+func (pool *LegacyPool) Pending(enforceTips bool) map[common.Address][]*types.Transaction {
pool.mu.Lock()
defer pool.mu.Unlock()
- pending := make(map[common.Address][]*txpool.LazyTransaction, len(pool.pending))
+ pending := make(map[common.Address][]*types.Transaction, len(pool.pending))
for addr, list := range pool.pending {
txs := list.Flatten()
@@ -530,18 +526,7 @@ func (pool *LegacyPool) Pending(enforceTips bool) map[common.Address][]*txpool.L
}
}
if len(txs) > 0 {
- lazies := make([]*txpool.LazyTransaction, len(txs))
- for i := 0; i < len(txs); i++ {
- lazies[i] = &txpool.LazyTransaction{
- Pool: pool,
- Hash: txs[i].Hash(),
- Tx: &txpool.Transaction{Tx: txs[i]},
- Time: txs[i].Time(),
- GasFeeCap: txs[i].GasFeeCap(),
- GasTipCap: txs[i].GasTipCap(),
- }
- }
- pending[addr] = lazies
+ pending[addr] = txs
}
}
return pending
@@ -601,16 +586,6 @@ func (pool *LegacyPool) validateTx(tx *types.Transaction, local bool) error {
State: pool.currentState,
FirstNonceGap: nil, // Pool allows arbitrary arrival order, don't invalidate nonce gaps
- UsedAndLeftSlots: func(addr common.Address) (int, int) {
- var have int
- if list := pool.pending[addr]; list != nil {
- have += list.Len()
- }
- if list := pool.queue[addr]; list != nil {
- have += list.Len()
- }
- return have, math.MaxInt
- },
ExistingExpenditure: func(addr common.Address) *big.Int {
if list := pool.pending[addr]; list != nil {
return list.totalcost
@@ -657,31 +632,10 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e
invalidTxMeter.Mark(1)
return false, err
}
+
// already validated by this point
from, _ := types.Sender(pool.signer, tx)
- // If the address is not yet known, request exclusivity to track the account
- // only by this subpool until all transactions are evicted
- var (
- _, hasPending = pool.pending[from]
- _, hasQueued = pool.queue[from]
- )
- if !hasPending && !hasQueued {
- if err := pool.reserve(from, true); err != nil {
- return false, err
- }
- defer func() {
- // If the transaction is rejected by some post-validation check, remove
- // the lock on the reservation set.
- //
- // Note, `err` here is the named error return, which will be initialized
- // by a return statement before running deferred methods. Take care with
- // removing or subscoping err as it will break this clause.
- if err != nil {
- pool.reserve(from, false)
- }
- }()
- }
// If the transaction pool is full, discard underpriced transactions
if uint64(pool.all.Slots()+numSlots(tx)) > pool.config.GlobalSlots+pool.config.GlobalQueue {
// If the new transaction is underpriced, don't accept it
@@ -736,10 +690,7 @@ func (pool *LegacyPool) add(tx *types.Transaction, local bool) (replaced bool, e
for _, tx := range drop {
log.Trace("Discarding freshly underpriced transaction", "hash", tx.Hash(), "gasTipCap", tx.GasTipCap(), "gasFeeCap", tx.GasFeeCap())
underpricedTxMeter.Mark(1)
-
- sender, _ := types.Sender(pool.signer, tx)
- dropped := pool.removeTx(tx.Hash(), false, sender != from) // Don't unreserve the sender of the tx being added if last from the acc
-
+ dropped := pool.removeTx(tx.Hash(), false)
pool.changesSinceReorg += dropped
}
}
@@ -1063,14 +1014,8 @@ func (pool *LegacyPool) Has(hash common.Hash) bool {
// removeTx removes a single transaction from the queue, moving all subsequent
// transactions back to the future queue.
-//
-// In unreserve is false, the account will not be relinquished to the main txpool
-// even if there are no more references to it. This is used to handle a race when
-// a tx being added, and it evicts a previously scheduled tx from the same account,
-// which could lead to a premature release of the lock.
-//
// Returns the number of transactions removed from the pending queue.
-func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bool) int {
+func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool) int {
// Fetch the transaction we wish to delete
tx := pool.all.Get(hash)
if tx == nil {
@@ -1078,20 +1023,6 @@ func (pool *LegacyPool) removeTx(hash common.Hash, outofbound bool, unreserve bo
}
addr, _ := types.Sender(pool.signer, tx) // already validated during insertion
- // If after deletion there are no more transactions belonging to this account,
- // relinquish the address reservation. It's a bit convoluted do this, via a
- // defer, but it's safer vs. the many return pathways.
- if unreserve {
- defer func() {
- var (
- _, hasPending = pool.pending[addr]
- _, hasQueued = pool.queue[addr]
- )
- if !hasPending && !hasQueued {
- pool.reserve(addr, false)
- }
- }()
- }
// Remove it from the list of known transactions
pool.all.Remove(hash)
if outofbound {
@@ -1342,7 +1273,7 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
// there's nothing to add
if newNum >= oldNum {
// If we reorged to a same or higher number, then it's not a case of setHead
- log.Warn("Transaction pool reset with missing old head",
+ log.Warn("Transaction pool reset with missing oldhead",
"old", oldHead.Hash(), "oldnum", oldNum, "new", newHead.Hash(), "newnum", newNum)
return
}
@@ -1356,7 +1287,7 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
// the firing of newhead-event and _now_: most likely a
// reorg caused by sync-reversion or explicit sethead back to an
// earlier block.
- log.Warn("Transaction pool reset with missing new head", "number", newHead.Number, "hash", newHead.Hash())
+ log.Warn("New head missing in txpool reset", "number", newHead.Number, "hash", newHead.Hash())
return
}
var discarded, included types.Transactions
@@ -1386,13 +1317,7 @@ func (pool *LegacyPool) reset(oldHead, newHead *types.Header) {
return
}
}
- lost := make([]*types.Transaction, 0, len(discarded))
- for _, tx := range types.TxDifference(discarded, included) {
- if pool.Filter(tx) {
- lost = append(lost, tx)
- }
- }
- reinject = lost
+ reinject = types.TxDifference(discarded, included)
}
}
}
@@ -1477,9 +1402,6 @@ func (pool *LegacyPool) promoteExecutables(accounts []common.Address) []*types.T
if list.Empty() {
delete(pool.queue, addr)
delete(pool.beats, addr)
- if _, ok := pool.pending[addr]; !ok {
- pool.reserve(addr, false)
- }
}
}
return promoted
@@ -1601,7 +1523,7 @@ func (pool *LegacyPool) truncateQueue() {
// Drop all transactions if they are less than the overflow
if size := uint64(list.Len()); size <= drop {
for _, tx := range list.Flatten() {
- pool.removeTx(tx.Hash(), true, true)
+ pool.removeTx(tx.Hash(), true)
}
drop -= size
queuedRateLimitMeter.Mark(int64(size))
@@ -1610,7 +1532,7 @@ func (pool *LegacyPool) truncateQueue() {
// Otherwise drop only last few transactions
txs := list.Flatten()
for i := len(txs) - 1; i >= 0 && drop > 0; i-- {
- pool.removeTx(txs[i].Hash(), true, true)
+ pool.removeTx(txs[i].Hash(), true)
drop--
queuedRateLimitMeter.Mark(1)
}
@@ -1672,9 +1594,6 @@ func (pool *LegacyPool) demoteUnexecutables() {
// Delete the entire pending entry if it became empty.
if list.Empty() {
delete(pool.pending, addr)
- if _, ok := pool.queue[addr]; !ok {
- pool.reserve(addr, false)
- }
}
}
}
diff --git a/core/txpool/legacypool/legacypool2_test.go b/core/txpool/legacypool/legacypool2_test.go
index a73c1bb8a7724..5de34588afe2c 100644
--- a/core/txpool/legacypool/legacypool2_test.go
+++ b/core/txpool/legacypool/legacypool2_test.go
@@ -84,7 +84,7 @@ func TestTransactionFutureAttack(t *testing.T) {
config.GlobalQueue = 100
config.GlobalSlots = 100
pool := New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
fillPool(t, pool)
pending, _ := pool.Stats()
@@ -118,7 +118,7 @@ func TestTransactionFuture1559(t *testing.T) {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create a number of test accounts, fund them and make transactions
@@ -151,7 +151,7 @@ func TestTransactionZAttack(t *testing.T) {
statedb, _ := state.New(types.EmptyRootHash, state.NewDatabase(rawdb.NewMemoryDatabase()), nil)
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create a number of test accounts, fund them and make transactions
fillPool(t, pool)
@@ -222,7 +222,7 @@ func BenchmarkFutureAttack(b *testing.B) {
config.GlobalQueue = 100
config.GlobalSlots = 100
pool := New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
fillPool(b, pool)
diff --git a/core/txpool/legacypool/legacypool_test.go b/core/txpool/legacypool/legacypool_test.go
index a8f3dd7d86247..14612a686cfd8 100644
--- a/core/txpool/legacypool/legacypool_test.go
+++ b/core/txpool/legacypool/legacypool_test.go
@@ -24,7 +24,6 @@ import (
"math/big"
"math/rand"
"os"
- "sync"
"sync/atomic"
"testing"
"time"
@@ -128,31 +127,6 @@ func dynamicFeeTx(nonce uint64, gaslimit uint64, gasFee *big.Int, tip *big.Int,
return tx
}
-func makeAddressReserver() txpool.AddressReserver {
- var (
- reserved = make(map[common.Address]struct{})
- lock sync.Mutex
- )
- return func(addr common.Address, reserve bool) error {
- lock.Lock()
- defer lock.Unlock()
-
- _, exists := reserved[addr]
- if reserve {
- if exists {
- panic("already reserved")
- }
- reserved[addr] = struct{}{}
- return nil
- }
- if !exists {
- panic("not reserved")
- }
- delete(reserved, addr)
- return nil
- }
-}
-
func setupPool() (*LegacyPool, *ecdsa.PrivateKey) {
return setupPoolWithConfig(params.TestChainConfig)
}
@@ -163,7 +137,7 @@ func setupPoolWithConfig(config *params.ChainConfig) (*LegacyPool, *ecdsa.Privat
key, _ := crypto.GenerateKey()
pool := New(testTxPoolConfig, blockchain)
- if err := pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver()); err != nil {
+ if err := pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock()); err != nil {
panic(err)
}
// wait for the pool to initialize
@@ -282,7 +256,7 @@ func TestStateChangeDuringReset(t *testing.T) {
tx1 := transaction(1, 100000, key)
pool := New(testTxPoolConfig, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
nonce := pool.Nonce(address)
@@ -481,7 +455,7 @@ func TestChainFork(t *testing.T) {
if _, err := pool.add(tx, false); err != nil {
t.Error("didn't expect error", err)
}
- pool.removeTx(tx.Hash(), true, true)
+ pool.removeTx(tx.Hash(), true)
// reset the pool's internal state
resetState()
@@ -702,7 +676,7 @@ func TestPostponing(t *testing.T) {
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create two test accounts to produce different gap profiles with
@@ -919,7 +893,7 @@ func testQueueGlobalLimiting(t *testing.T, nolocals bool) {
config.GlobalQueue = config.AccountQueue*3 - 1 // reduce the queue limits to shorten test time (-1 to make it non divisible)
pool := New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create a number of test accounts and fund them (last one will be the local)
@@ -1012,7 +986,7 @@ func testQueueTimeLimiting(t *testing.T, nolocals bool) {
config.NoLocals = nolocals
pool := New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create two test accounts to ensure remotes expire but locals do not
@@ -1197,7 +1171,7 @@ func TestPendingGlobalLimiting(t *testing.T) {
config.GlobalSlots = config.AccountSlots * 10
pool := New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create a number of test accounts and fund them
@@ -1301,7 +1275,7 @@ func TestCapClearsFromAll(t *testing.T) {
config.GlobalSlots = 8
pool := New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create a number of test accounts and fund them
@@ -1334,7 +1308,7 @@ func TestPendingMinimumAllowance(t *testing.T) {
config.GlobalSlots = 1
pool := New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create a number of test accounts and fund them
@@ -1380,7 +1354,7 @@ func TestRepricing(t *testing.T) {
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Keep track of transaction events to ensure all executables get announced
@@ -1629,7 +1603,7 @@ func TestRepricingKeepsLocals(t *testing.T) {
blockchain := newTestBlockChain(eip1559Config, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create a number of test accounts and fund them
@@ -1707,7 +1681,7 @@ func TestUnderpricing(t *testing.T) {
config.GlobalQueue = 2
pool := New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Keep track of transaction events to ensure all executables get announced
@@ -1822,7 +1796,7 @@ func TestStableUnderpricing(t *testing.T) {
config.GlobalQueue = 0
pool := New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Keep track of transaction events to ensure all executables get announced
@@ -2051,7 +2025,7 @@ func TestDeduplication(t *testing.T) {
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create a test account to add transactions with
@@ -2118,7 +2092,7 @@ func TestReplacement(t *testing.T) {
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Keep track of transaction events to ensure all executables get announced
@@ -2329,7 +2303,7 @@ func testJournaling(t *testing.T, nolocals bool) {
config.Rejournal = time.Second
pool := New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
// Create two test accounts to ensure remotes expire but locals do not
local, _ := crypto.GenerateKey()
@@ -2367,7 +2341,7 @@ func testJournaling(t *testing.T, nolocals bool) {
blockchain = newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool = New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
pending, queued = pool.Stats()
if queued != 0 {
@@ -2394,7 +2368,7 @@ func testJournaling(t *testing.T, nolocals bool) {
statedb.SetNonce(crypto.PubkeyToAddress(local.PublicKey), 1)
blockchain = newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool = New(config, blockchain)
- pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(config.PriceLimit), blockchain.CurrentBlock())
pending, queued = pool.Stats()
if pending != 0 {
@@ -2425,7 +2399,7 @@ func TestStatusCheck(t *testing.T) {
blockchain := newTestBlockChain(params.TestChainConfig, 1000000, statedb, new(event.Feed))
pool := New(testTxPoolConfig, blockchain)
- pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock(), makeAddressReserver())
+ pool.Init(new(big.Int).SetUint64(testTxPoolConfig.PriceLimit), blockchain.CurrentBlock())
defer pool.Close()
// Create the test accounts to check various transaction statuses with
diff --git a/core/txpool/subpool.go b/core/txpool/subpool.go
index 70c0918e140c2..835f0de97a3bc 100644
--- a/core/txpool/subpool.go
+++ b/core/txpool/subpool.go
@@ -18,7 +18,6 @@ package txpool
import (
"math/big"
- "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
@@ -37,32 +36,6 @@ type Transaction struct {
BlobTxProofs []kzg4844.Proof // Proofs needed by the blob pool
}
-// LazyTransaction contains a small subset of the transaction properties that is
-// enough for the miner and other APIs to handle large batches of transactions;
-// and supports pulling up the entire transaction when really needed.
-type LazyTransaction struct {
- Pool SubPool // Transaction subpool to pull the real transaction up
- Hash common.Hash // Transaction hash to pull up if needed
- Tx *Transaction // Transaction if already resolved
-
- Time time.Time // Time when the transaction was first seen
- GasFeeCap *big.Int // Maximum fee per gas the transaction may consume
- GasTipCap *big.Int // Maximum miner tip per gas the transaction can pay
-}
-
-// Resolve retrieves the full transaction belonging to a lazy handle if it is still
-// maintained by the transaction pool.
-func (ltx *LazyTransaction) Resolve() *Transaction {
- if ltx.Tx == nil {
- ltx.Tx = ltx.Pool.Get(ltx.Hash)
- }
- return ltx.Tx
-}
-
-// AddressReserver is passed by the main transaction pool to subpools, so they
-// may request (and relinquish) exclusive access to certain addresses.
-type AddressReserver func(addr common.Address, reserve bool) error
-
// SubPool represents a specialized transaction pool that lives on its own (e.g.
// blob pool). Since independent of how many specialized pools we have, they do
// need to be updated in lockstep and assemble into one coherent view for block
@@ -80,7 +53,7 @@ type SubPool interface {
// These should not be passed as a constructor argument - nor should the pools
// start by themselves - in order to keep multiple subpools in lockstep with
// one another.
- Init(gasTip *big.Int, head *types.Header, reserve AddressReserver) error
+ Init(gasTip *big.Int, head *types.Header) error
// Close terminates any background processing threads and releases any held
// resources.
@@ -108,7 +81,7 @@ type SubPool interface {
// Pending retrieves all currently processable transactions, grouped by origin
// account and sorted by nonce.
- Pending(enforceTips bool) map[common.Address][]*LazyTransaction
+ Pending(enforceTips bool) map[common.Address][]*types.Transaction
// SubscribeTransactions subscribes to new transaction events.
SubscribeTransactions(ch chan<- core.NewTxsEvent) event.Subscription
diff --git a/core/txpool/txpool.go b/core/txpool/txpool.go
index b0e91fee6c446..24105babc94fe 100644
--- a/core/txpool/txpool.go
+++ b/core/txpool/txpool.go
@@ -17,17 +17,13 @@
package txpool
import (
- "errors"
"fmt"
"math/big"
- "sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
- "github.com/ethereum/go-ethereum/log"
- "github.com/ethereum/go-ethereum/metrics"
)
// TxStatus is the current status of a transaction as seen by the pool.
@@ -40,15 +36,6 @@ const (
TxStatusIncluded
)
-var (
- // reservationsGaugeName is the prefix of a per-subpool address reservation
- // metric.
- //
- // This is mostly a sanity metric to ensure there's no bug that would make
- // some subpool hog all the reservations due to mis-accounting.
- reservationsGaugeName = "txpool/reservations"
-)
-
// BlockChain defines the minimal set of methods needed to back a tx pool with
// a chain. Exists to allow mocking the live chain out of tests.
type BlockChain interface {
@@ -65,13 +52,9 @@ type BlockChain interface {
// They exit the pool when they are included in the blockchain or evicted due to
// resource constraints.
type TxPool struct {
- subpools []SubPool // List of subpools for specialized transaction handling
-
- reservations map[common.Address]SubPool // Map with the account to pool reservations
- reserveLock sync.Mutex // Lock protecting the account reservations
-
- subs event.SubscriptionScope // Subscription scope to unscubscribe all on shutdown
- quit chan chan error // Quit channel to tear down the head updater
+ subpools []SubPool // List of subpools for specialized transaction handling
+ subs event.SubscriptionScope // Subscription scope to unscubscribe all on shutdown
+ quit chan chan error // Quit channel to tear down the head updater
}
// New creates a new transaction pool to gather, sort and filter inbound
@@ -83,12 +66,11 @@ func New(gasTip *big.Int, chain BlockChain, subpools []SubPool) (*TxPool, error)
head := chain.CurrentBlock()
pool := &TxPool{
- subpools: subpools,
- reservations: make(map[common.Address]SubPool),
- quit: make(chan chan error),
+ subpools: subpools,
+ quit: make(chan chan error),
}
for i, subpool := range subpools {
- if err := subpool.Init(gasTip, head, pool.reserver(i, subpool)); err != nil {
+ if err := subpool.Init(gasTip, head); err != nil {
for j := i - 1; j >= 0; j-- {
subpools[j].Close()
}
@@ -99,52 +81,6 @@ func New(gasTip *big.Int, chain BlockChain, subpools []SubPool) (*TxPool, error)
return pool, nil
}
-// reserver is a method to create an address reservation callback to exclusively
-// assign/deassign addresses to/from subpools. This can ensure that at any point
-// in time, only a single subpool is able to manage an account, avoiding cross
-// subpool eviction issues and nonce conflicts.
-func (p *TxPool) reserver(id int, subpool SubPool) AddressReserver {
- return func(addr common.Address, reserve bool) error {
- p.reserveLock.Lock()
- defer p.reserveLock.Unlock()
-
- owner, exists := p.reservations[addr]
- if reserve {
- // Double reservations are forbidden even from the same pool to
- // avoid subtle bugs in the long term.
- if exists {
- if owner == subpool {
- log.Error("pool attempted to reserve already-owned address", "address", addr)
- return nil // Ignore fault to give the pool a chance to recover while the bug gets fixed
- }
- return errors.New("address already reserved")
- }
- p.reservations[addr] = subpool
- if metrics.Enabled {
- m := fmt.Sprintf("%s/%d", reservationsGaugeName, id)
- metrics.GetOrRegisterGauge(m, nil).Inc(1)
- }
- return nil
- }
- // Ensure subpools only attempt to unreserve their own owned addresses,
- // otherwise flag as a programming error.
- if !exists {
- log.Error("pool attempted to unreserve non-reserved address", "address", addr)
- return errors.New("address not reserved")
- }
- if subpool != owner {
- log.Error("pool attempted to unreserve non-owned address", "address", addr)
- return errors.New("address not owned")
- }
- delete(p.reservations, addr)
- if metrics.Enabled {
- m := fmt.Sprintf("%s/%d", reservationsGaugeName, id)
- metrics.GetOrRegisterGauge(m, nil).Dec(1)
- }
- return nil
- }
-}
-
// Close terminates the transaction pool and all its subpools.
func (p *TxPool) Close() error {
var errs []error
@@ -306,8 +242,8 @@ func (p *TxPool) Add(txs []*Transaction, local bool, sync bool) []error {
// Pending retrieves all currently processable transactions, grouped by origin
// account and sorted by nonce.
-func (p *TxPool) Pending(enforceTips bool) map[common.Address][]*LazyTransaction {
- txs := make(map[common.Address][]*LazyTransaction)
+func (p *TxPool) Pending(enforceTips bool) map[common.Address][]*types.Transaction {
+ txs := make(map[common.Address][]*types.Transaction)
for _, subpool := range p.subpools {
for addr, set := range subpool.Pending(enforceTips) {
txs[addr] = set
diff --git a/core/txpool/validation.go b/core/txpool/validation.go
index 67c39a99396f9..af678277f868a 100644
--- a/core/txpool/validation.go
+++ b/core/txpool/validation.go
@@ -166,11 +166,6 @@ type ValidationOptionsWithState struct {
// nonce gaps will be ignored and permitted.
FirstNonceGap func(addr common.Address) uint64
- // UsedAndLeftSlots is a mandatory callback to retrieve the number of tx slots
- // used and the number still permitted for an account. New transactions will
- // be rejected once the number of remaining slots reaches zero.
- UsedAndLeftSlots func(addr common.Address) (int, int)
-
// ExistingExpenditure is a mandatory callback to retrieve the cummulative
// cost of the already pooled transactions to check for overdrafts.
ExistingExpenditure func(addr common.Address) *big.Int
@@ -225,12 +220,6 @@ func ValidateTransactionWithState(tx *types.Transaction, signer types.Signer, op
if balance.Cmp(need) < 0 {
return fmt.Errorf("%w: balance %v, queued cost %v, tx cost %v, overshot %v", core.ErrInsufficientFunds, balance, spent, cost, new(big.Int).Sub(need, balance))
}
- // Transaction takes a new nonce value out of the pool. Ensure it doesn't
- // overflow the number of permitted transactions from a single accoun
- // (i.e. max cancellable via out-of-bound transaction).
- if used, left := opts.UsedAndLeftSlots(from); left <= 0 {
- return fmt.Errorf("%w: pooled %d txs", ErrAccountLimitExceeded, used)
- }
}
return nil
}
diff --git a/core/types/transaction.go b/core/types/transaction.go
index 91a20d32a7930..579fc00d10436 100644
--- a/core/types/transaction.go
+++ b/core/types/transaction.go
@@ -18,6 +18,7 @@ package types
import (
"bytes"
+ "container/heap"
"errors"
"io"
"math/big"
@@ -393,19 +394,6 @@ func (tx *Transaction) BlobGasFeeCapIntCmp(other *big.Int) int {
return tx.inner.blobGasFeeCap().Cmp(other)
}
-// SetTime sets the decoding time of a transaction. This is used by tests to set
-// arbitrary times and by persistent transaction pools when loading old txs from
-// disk.
-func (tx *Transaction) SetTime(t time.Time) {
- tx.time = t
-}
-
-// Time returns the time when the transaction was first seen on the network. It
-// is a heuristic to prefer mining older txs vs new all other things equal.
-func (tx *Transaction) Time() time.Time {
- return tx.time
-}
-
// Hash returns the transaction hash.
func (tx *Transaction) Hash() common.Hash {
if hash := tx.hash.Load(); hash != nil {
@@ -514,6 +502,123 @@ func (s TxByNonce) Len() int { return len(s) }
func (s TxByNonce) Less(i, j int) bool { return s[i].Nonce() < s[j].Nonce() }
func (s TxByNonce) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+// TxWithMinerFee wraps a transaction with its gas price or effective miner gasTipCap
+type TxWithMinerFee struct {
+ tx *Transaction
+ minerFee *big.Int
+}
+
+// NewTxWithMinerFee creates a wrapped transaction, calculating the effective
+// miner gasTipCap if a base fee is provided.
+// Returns error in case of a negative effective miner gasTipCap.
+func NewTxWithMinerFee(tx *Transaction, baseFee *big.Int) (*TxWithMinerFee, error) {
+ minerFee, err := tx.EffectiveGasTip(baseFee)
+ if err != nil {
+ return nil, err
+ }
+ return &TxWithMinerFee{
+ tx: tx,
+ minerFee: minerFee,
+ }, nil
+}
+
+// TxByPriceAndTime implements both the sort and the heap interface, making it useful
+// for all at once sorting as well as individually adding and removing elements.
+type TxByPriceAndTime []*TxWithMinerFee
+
+func (s TxByPriceAndTime) Len() int { return len(s) }
+func (s TxByPriceAndTime) Less(i, j int) bool {
+ // If the prices are equal, use the time the transaction was first seen for
+ // deterministic sorting
+ cmp := s[i].minerFee.Cmp(s[j].minerFee)
+ if cmp == 0 {
+ return s[i].tx.time.Before(s[j].tx.time)
+ }
+ return cmp > 0
+}
+func (s TxByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
+
+func (s *TxByPriceAndTime) Push(x interface{}) {
+ *s = append(*s, x.(*TxWithMinerFee))
+}
+
+func (s *TxByPriceAndTime) Pop() interface{} {
+ old := *s
+ n := len(old)
+ x := old[n-1]
+ old[n-1] = nil
+ *s = old[0 : n-1]
+ return x
+}
+
+// TransactionsByPriceAndNonce represents a set of transactions that can return
+// transactions in a profit-maximizing sorted order, while supporting removing
+// entire batches of transactions for non-executable accounts.
+type TransactionsByPriceAndNonce struct {
+ txs map[common.Address][]*Transaction // Per account nonce-sorted list of transactions
+ heads TxByPriceAndTime // Next transaction for each unique account (price heap)
+ signer Signer // Signer for the set of transactions
+ baseFee *big.Int // Current base fee
+}
+
+// NewTransactionsByPriceAndNonce creates a transaction set that can retrieve
+// price sorted transactions in a nonce-honouring way.
+//
+// Note, the input map is reowned so the caller should not interact any more with
+// if after providing it to the constructor.
+func NewTransactionsByPriceAndNonce(signer Signer, txs map[common.Address][]*Transaction, baseFee *big.Int) *TransactionsByPriceAndNonce {
+ // Initialize a price and received time based heap with the head transactions
+ heads := make(TxByPriceAndTime, 0, len(txs))
+ for from, accTxs := range txs {
+ acc, _ := Sender(signer, accTxs[0])
+ wrapped, err := NewTxWithMinerFee(accTxs[0], baseFee)
+ // Remove transaction if sender doesn't match from, or if wrapping fails.
+ if acc != from || err != nil {
+ delete(txs, from)
+ continue
+ }
+ heads = append(heads, wrapped)
+ txs[from] = accTxs[1:]
+ }
+ heap.Init(&heads)
+
+ // Assemble and return the transaction set
+ return &TransactionsByPriceAndNonce{
+ txs: txs,
+ heads: heads,
+ signer: signer,
+ baseFee: baseFee,
+ }
+}
+
+// Peek returns the next transaction by price.
+func (t *TransactionsByPriceAndNonce) Peek() *Transaction {
+ if len(t.heads) == 0 {
+ return nil
+ }
+ return t.heads[0].tx
+}
+
+// Shift replaces the current best head with the next one from the same account.
+func (t *TransactionsByPriceAndNonce) Shift() {
+ acc, _ := Sender(t.signer, t.heads[0].tx)
+ if txs, ok := t.txs[acc]; ok && len(txs) > 0 {
+ if wrapped, err := NewTxWithMinerFee(txs[0], t.baseFee); err == nil {
+ t.heads[0], t.txs[acc] = wrapped, txs[1:]
+ heap.Fix(&t.heads, 0)
+ return
+ }
+ }
+ heap.Pop(&t.heads)
+}
+
+// Pop removes the best transaction, *not* replacing it with the next one from
+// the same account. This should be used when a transaction cannot be executed
+// and hence all subsequent ones should be discarded from the same account.
+func (t *TransactionsByPriceAndNonce) Pop() {
+ heap.Pop(&t.heads)
+}
+
// copyAddressPtr copies an address.
func copyAddressPtr(a *common.Address) *common.Address {
if a == nil {
diff --git a/core/types/transaction_test.go b/core/types/transaction_test.go
index a984a9c70952d..dbe77fa6036aa 100644
--- a/core/types/transaction_test.go
+++ b/core/types/transaction_test.go
@@ -23,8 +23,10 @@ import (
"errors"
"fmt"
"math/big"
+ "math/rand"
"reflect"
"testing"
+ "time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
@@ -257,6 +259,152 @@ func TestRecipientNormal(t *testing.T) {
}
}
+func TestTransactionPriceNonceSortLegacy(t *testing.T) {
+ testTransactionPriceNonceSort(t, nil)
+}
+
+func TestTransactionPriceNonceSort1559(t *testing.T) {
+ testTransactionPriceNonceSort(t, big.NewInt(0))
+ testTransactionPriceNonceSort(t, big.NewInt(5))
+ testTransactionPriceNonceSort(t, big.NewInt(50))
+}
+
+// Tests that transactions can be correctly sorted according to their price in
+// decreasing order, but at the same time with increasing nonces when issued by
+// the same account.
+func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) {
+ // Generate a batch of accounts to start with
+ keys := make([]*ecdsa.PrivateKey, 25)
+ for i := 0; i < len(keys); i++ {
+ keys[i], _ = crypto.GenerateKey()
+ }
+ signer := LatestSignerForChainID(common.Big1)
+
+ // Generate a batch of transactions with overlapping values, but shifted nonces
+ groups := map[common.Address][]*Transaction{}
+ expectedCount := 0
+ for start, key := range keys {
+ addr := crypto.PubkeyToAddress(key.PublicKey)
+ count := 25
+ for i := 0; i < 25; i++ {
+ var tx *Transaction
+ gasFeeCap := rand.Intn(50)
+ if baseFee == nil {
+ tx = NewTx(&LegacyTx{
+ Nonce: uint64(start + i),
+ To: &common.Address{},
+ Value: big.NewInt(100),
+ Gas: 100,
+ GasPrice: big.NewInt(int64(gasFeeCap)),
+ Data: nil,
+ })
+ } else {
+ tx = NewTx(&DynamicFeeTx{
+ Nonce: uint64(start + i),
+ To: &common.Address{},
+ Value: big.NewInt(100),
+ Gas: 100,
+ GasFeeCap: big.NewInt(int64(gasFeeCap)),
+ GasTipCap: big.NewInt(int64(rand.Intn(gasFeeCap + 1))),
+ Data: nil,
+ })
+ if count == 25 && int64(gasFeeCap) < baseFee.Int64() {
+ count = i
+ }
+ }
+ tx, err := SignTx(tx, signer, key)
+ if err != nil {
+ t.Fatalf("failed to sign tx: %s", err)
+ }
+ groups[addr] = append(groups[addr], tx)
+ }
+ expectedCount += count
+ }
+ // Sort the transactions and cross check the nonce ordering
+ txset := NewTransactionsByPriceAndNonce(signer, groups, baseFee)
+
+ txs := Transactions{}
+ for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
+ txs = append(txs, tx)
+ txset.Shift()
+ }
+ if len(txs) != expectedCount {
+ t.Errorf("expected %d transactions, found %d", expectedCount, len(txs))
+ }
+ for i, txi := range txs {
+ fromi, _ := Sender(signer, txi)
+
+ // Make sure the nonce order is valid
+ for j, txj := range txs[i+1:] {
+ fromj, _ := Sender(signer, txj)
+ if fromi == fromj && txi.Nonce() > txj.Nonce() {
+ t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce())
+ }
+ }
+ // If the next tx has different from account, the price must be lower than the current one
+ if i+1 < len(txs) {
+ next := txs[i+1]
+ fromNext, _ := Sender(signer, next)
+ tip, err := txi.EffectiveGasTip(baseFee)
+ nextTip, nextErr := next.EffectiveGasTip(baseFee)
+ if err != nil || nextErr != nil {
+ t.Errorf("error calculating effective tip")
+ }
+ if fromi != fromNext && tip.Cmp(nextTip) < 0 {
+ t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
+ }
+ }
+ }
+}
+
+// Tests that if multiple transactions have the same price, the ones seen earlier
+// are prioritized to avoid network spam attacks aiming for a specific ordering.
+func TestTransactionTimeSort(t *testing.T) {
+ // Generate a batch of accounts to start with
+ keys := make([]*ecdsa.PrivateKey, 5)
+ for i := 0; i < len(keys); i++ {
+ keys[i], _ = crypto.GenerateKey()
+ }
+ signer := HomesteadSigner{}
+
+ // Generate a batch of transactions with overlapping prices, but different creation times
+ groups := map[common.Address][]*Transaction{}
+ for start, key := range keys {
+ addr := crypto.PubkeyToAddress(key.PublicKey)
+
+ tx, _ := SignTx(NewTransaction(0, common.Address{}, big.NewInt(100), 100, big.NewInt(1), nil), signer, key)
+ tx.time = time.Unix(0, int64(len(keys)-start))
+
+ groups[addr] = append(groups[addr], tx)
+ }
+ // Sort the transactions and cross check the nonce ordering
+ txset := NewTransactionsByPriceAndNonce(signer, groups, nil)
+
+ txs := Transactions{}
+ for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
+ txs = append(txs, tx)
+ txset.Shift()
+ }
+ if len(txs) != len(keys) {
+ t.Errorf("expected %d transactions, found %d", len(keys), len(txs))
+ }
+ for i, txi := range txs {
+ fromi, _ := Sender(signer, txi)
+ if i+1 < len(txs) {
+ next := txs[i+1]
+ fromNext, _ := Sender(signer, next)
+
+ if txi.GasPrice().Cmp(next.GasPrice()) < 0 {
+ t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
+ }
+ // Make sure time order is ascending if the txs have the same gas price
+ if txi.GasPrice().Cmp(next.GasPrice()) == 0 && txi.time.After(next.time) {
+ t.Errorf("invalid received time ordering: tx #%d (A=%x T=%v) > tx #%d (A=%x T=%v)", i, fromi[:4], txi.time, i+1, fromNext[:4], next.time)
+ }
+ }
+ }
+}
+
// TestTransactionCoding tests serializing/de-serializing to/from rlp and JSON.
func TestTransactionCoding(t *testing.T) {
key, err := crypto.GenerateKey()
diff --git a/eth/api_backend.go b/eth/api_backend.go
index 30e2493684109..80f5bcee61444 100644
--- a/eth/api_backend.go
+++ b/eth/api_backend.go
@@ -301,11 +301,7 @@ func (b *EthAPIBackend) GetPoolTransactions() (types.Transactions, error) {
pending := b.eth.txPool.Pending(false)
var txs types.Transactions
for _, batch := range pending {
- for _, lazy := range batch {
- if tx := lazy.Resolve(); tx != nil {
- txs = append(txs, tx.Tx)
- }
- }
+ txs = append(txs, batch...)
}
return txs, nil
}
diff --git a/eth/backend.go b/eth/backend.go
index 667200bceddaf..63bd864b21eb7 100644
--- a/eth/backend.go
+++ b/eth/backend.go
@@ -35,7 +35,6 @@ import (
"github.com/ethereum/go-ethereum/core/rawdb"
"github.com/ethereum/go-ethereum/core/state/pruner"
"github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
@@ -207,17 +206,12 @@ func New(stack *node.Node, config *ethconfig.Config) (*Ethereum, error) {
}
eth.bloomIndexer.Start(eth.blockchain)
- if config.BlobPool.Datadir != "" {
- config.BlobPool.Datadir = stack.ResolvePath(config.BlobPool.Datadir)
- }
- blobPool := blobpool.New(config.BlobPool, eth.blockchain)
-
if config.TxPool.Journal != "" {
config.TxPool.Journal = stack.ResolvePath(config.TxPool.Journal)
}
legacyPool := legacypool.New(config.TxPool, eth.blockchain)
- eth.txPool, err = txpool.New(new(big.Int).SetUint64(config.TxPool.PriceLimit), eth.blockchain, []txpool.SubPool{legacyPool, blobPool})
+ eth.txPool, err = txpool.New(new(big.Int).SetUint64(config.TxPool.PriceLimit), eth.blockchain, []txpool.SubPool{legacyPool})
if err != nil {
return nil, err
}
diff --git a/eth/ethconfig/config.go b/eth/ethconfig/config.go
index 4bc8b8dc6c6e5..5de0055b51933 100644
--- a/eth/ethconfig/config.go
+++ b/eth/ethconfig/config.go
@@ -27,7 +27,6 @@ import (
"github.com/ethereum/go-ethereum/consensus/clique"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice"
@@ -70,7 +69,6 @@ var Defaults = Config{
FilterLogCacheSize: 32,
Miner: miner.DefaultConfig,
TxPool: legacypool.DefaultConfig,
- BlobPool: blobpool.DefaultConfig,
RPCGasCap: 50000000,
RPCEVMTimeout: 5 * time.Second,
GPO: FullNodeGPO,
@@ -131,8 +129,7 @@ type Config struct {
Miner miner.Config
// Transaction pool options
- TxPool legacypool.Config
- BlobPool blobpool.Config
+ TxPool legacypool.Config
// Gas Price Oracle options
GPO gasprice.Config
diff --git a/eth/ethconfig/gen_config.go b/eth/ethconfig/gen_config.go
index 324fbe380ea38..82b2d5b6b0007 100644
--- a/eth/ethconfig/gen_config.go
+++ b/eth/ethconfig/gen_config.go
@@ -7,7 +7,6 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
- "github.com/ethereum/go-ethereum/core/txpool/blobpool"
"github.com/ethereum/go-ethereum/core/txpool/legacypool"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/gasprice"
@@ -44,7 +43,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
FilterLogCacheSize int
Miner miner.Config
TxPool legacypool.Config
- BlobPool blobpool.Config
GPO gasprice.Config
EnablePreimageRecording bool
DocRoot string `toml:"-"`
@@ -82,7 +80,6 @@ func (c Config) MarshalTOML() (interface{}, error) {
enc.FilterLogCacheSize = c.FilterLogCacheSize
enc.Miner = c.Miner
enc.TxPool = c.TxPool
- enc.BlobPool = c.BlobPool
enc.GPO = c.GPO
enc.EnablePreimageRecording = c.EnablePreimageRecording
enc.DocRoot = c.DocRoot
@@ -124,7 +121,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
FilterLogCacheSize *int
Miner *miner.Config
TxPool *legacypool.Config
- BlobPool *blobpool.Config
GPO *gasprice.Config
EnablePreimageRecording *bool
DocRoot *string `toml:"-"`
@@ -219,9 +215,6 @@ func (c *Config) UnmarshalTOML(unmarshal func(interface{}) error) error {
if dec.TxPool != nil {
c.TxPool = *dec.TxPool
}
- if dec.BlobPool != nil {
- c.BlobPool = *dec.BlobPool
- }
if dec.GPO != nil {
c.GPO = *dec.GPO
}
diff --git a/eth/handler.go b/eth/handler.go
index 2453c08afbc05..f0b8d65a12925 100644
--- a/eth/handler.go
+++ b/eth/handler.go
@@ -73,7 +73,7 @@ type txPool interface {
// Pending should return pending transactions.
// The slice should be modifiable by the caller.
- Pending(enforceTips bool) map[common.Address][]*txpool.LazyTransaction
+ Pending(enforceTips bool) map[common.Address][]*types.Transaction
// SubscribeNewTxsEvent should return an event subscription of
// NewTxsEvent and send events to the given channel.
diff --git a/eth/handler_test.go b/eth/handler_test.go
index 7451e17012a00..2f776e874b3d8 100644
--- a/eth/handler_test.go
+++ b/eth/handler_test.go
@@ -101,7 +101,7 @@ func (p *testTxPool) Add(txs []*txpool.Transaction, local bool, sync bool) []err
}
// Pending returns all the transactions known to the pool
-func (p *testTxPool) Pending(enforceTips bool) map[common.Address][]*txpool.LazyTransaction {
+func (p *testTxPool) Pending(enforceTips bool) map[common.Address][]*types.Transaction {
p.lock.RLock()
defer p.lock.RUnlock()
@@ -113,19 +113,7 @@ func (p *testTxPool) Pending(enforceTips bool) map[common.Address][]*txpool.Lazy
for _, batch := range batches {
sort.Sort(types.TxByNonce(batch))
}
- pending := make(map[common.Address][]*txpool.LazyTransaction)
- for addr, batch := range batches {
- for _, tx := range batch {
- pending[addr] = append(pending[addr], &txpool.LazyTransaction{
- Hash: tx.Hash(),
- Tx: &txpool.Transaction{Tx: tx},
- Time: tx.Time(),
- GasFeeCap: tx.GasFeeCap(),
- GasTipCap: tx.GasTipCap(),
- })
- }
- }
- return pending
+ return batches
}
// SubscribeNewTxsEvent should return an event subscription of NewTxsEvent and
diff --git a/eth/sync.go b/eth/sync.go
index ba7a7427a51a9..89bffbe653bd5 100644
--- a/eth/sync.go
+++ b/eth/sync.go
@@ -23,6 +23,7 @@ import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/rawdb"
+ "github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/eth/downloader"
"github.com/ethereum/go-ethereum/eth/protocols/eth"
"github.com/ethereum/go-ethereum/log"
@@ -35,15 +36,27 @@ const (
// syncTransactions starts sending all currently pending transactions to the given peer.
func (h *handler) syncTransactions(p *eth.Peer) {
- var hashes []common.Hash
- for _, batch := range h.txpool.Pending(false) {
- for _, tx := range batch {
- hashes = append(hashes, tx.Hash)
- }
+ // Assemble the set of transaction to broadcast or announce to the remote
+ // peer. Fun fact, this is quite an expensive operation as it needs to sort
+ // the transactions if the sorting is not cached yet. However, with a random
+ // order, insertions could overflow the non-executable queues and get dropped.
+ //
+ // TODO(karalabe): Figure out if we could get away with random order somehow
+ var txs types.Transactions
+ pending := h.txpool.Pending(false)
+ for _, batch := range pending {
+ txs = append(txs, batch...)
}
- if len(hashes) == 0 {
+ if len(txs) == 0 {
return
}
+ // The eth/65 protocol introduces proper transaction announcements, so instead
+ // of dripping transactions across multiple peers, just send the entire list as
+ // an announcement and let the remote side decide what they need (likely nothing).
+ hashes := make([]common.Hash, len(txs))
+ for i, tx := range txs {
+ hashes[i] = tx.Hash()
+ }
p.AsyncSendPooledTransactionHashes(hashes)
}
diff --git a/go.mod b/go.mod
index 9c7121c0355ce..47535d7923868 100644
--- a/go.mod
+++ b/go.mod
@@ -36,9 +36,8 @@ require (
github.com/gorilla/websocket v1.4.2
github.com/graph-gophers/graphql-go v1.3.0
github.com/hashicorp/go-bexpr v0.1.10
- github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7
github.com/holiman/bloomfilter/v2 v2.0.3
- github.com/holiman/uint256 v1.2.3
+ github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c
github.com/huin/goupnp v1.0.3
github.com/influxdata/influxdb-client-go/v2 v2.4.0
github.com/influxdata/influxdb1-client v0.0.0-20220302092344-a9ab5670611c
@@ -60,7 +59,7 @@ require (
github.com/supranational/blst v0.3.11-0.20230406105308-e9dfc5ee724b
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
github.com/tyler-smith/go-bip39 v1.1.0
- github.com/urfave/cli/v2 v2.24.1
+ github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa
go.uber.org/automaxprocs v1.5.2
golang.org/x/crypto v0.9.0
golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc
diff --git a/go.sum b/go.sum
index 591764e65830b..4fce0746cc00a 100644
--- a/go.sum
+++ b/go.sum
@@ -7,7 +7,7 @@ github.com/Azure/azure-sdk-for-go/sdk/internal v0.8.3/go.mod h1:KLF4gFr6DcKFZwSu
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0 h1:Px2UA+2RvSSvv+RvJNuUB6n7rs5Wsel4dXLe90Um2n4=
github.com/Azure/azure-sdk-for-go/sdk/storage/azblob v0.3.0/go.mod h1:tPaiy8S5bQ+S5sOiDlINkp7+Ef339+Nz5L5XO+cnOHo=
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
-github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak=
+github.com/BurntSushi/toml v1.2.0 h1:Rt8g24XnyGTyglgET/PRUNlrUeu9F5L+7FilkXfZgs0=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
github.com/DataDog/zstd v1.5.2 h1:vUG4lAyuPCXO0TLbXvPv7EB7cNK1QV/luu55UHLrrn8=
@@ -233,12 +233,10 @@ github.com/hashicorp/go-bexpr v0.1.10 h1:9kuI5PFotCboP3dkDYFr/wi0gg0QVbSNz5oFRpx
github.com/hashicorp/go-bexpr v0.1.10/go.mod h1:oxlubA2vC/gFVfX1A6JGp7ls7uCDlfJn732ehYYg+g0=
github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
-github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7 h1:3JQNjnMRil1yD0IfZKHF9GxxWKDJGj8I0IqOUol//sw=
-github.com/holiman/billy v0.0.0-20230718173358-1c7e68d277a7/go.mod h1:5GuXa7vkL8u9FkFuWdVvfR5ix8hRB7DbOAaYULamFpc=
github.com/holiman/bloomfilter/v2 v2.0.3 h1:73e0e/V0tCydx14a0SCYS/EWCxgwLZ18CZcZKVu0fao=
github.com/holiman/bloomfilter/v2 v2.0.3/go.mod h1:zpoh+gs7qcpqrHr3dB55AMiJwo0iURXE7ZOP9L9hSkA=
-github.com/holiman/uint256 v1.2.3 h1:K8UWO1HUJpRMXBxbmaY1Y8IAMZC/RsKB+ArEnnK4l5o=
-github.com/holiman/uint256 v1.2.3/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw=
+github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c h1:DZfsyhDK1hnSS5lH8l+JggqzEleHteTYfutAiVlSUM8=
+github.com/holiman/uint256 v1.2.2-0.20230321075855-87b91420868c/go.mod h1:SC8Ryt4n+UBbPbIBKaG9zbbDlp4jOru9xFZmPzLUTxw=
github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU=
github.com/huin/goupnp v1.0.3 h1:N8No57ls+MnjlB+JPiCVSOyy/ot7MJTqlo7rn+NYSqQ=
github.com/huin/goupnp v1.0.3/go.mod h1:ZxNlw5WqJj6wSsRK5+YfflQGXYfccj5VgQsMNixHM7Y=
@@ -441,8 +439,8 @@ github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVM
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY=
github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI=
-github.com/urfave/cli/v2 v2.24.1 h1:/QYYr7g0EhwXEML8jO+8OYt5trPnLHS0p3mrgExJ5NU=
-github.com/urfave/cli/v2 v2.24.1/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
+github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa h1:5SqCsI/2Qya2bCzK15ozrqo2sZxkh0FHynJZOTVoV6Q=
+github.com/urfave/cli/v2 v2.17.2-0.20221006022127-8f469abc00aa/go.mod h1:1CNUng3PtjQMtRzJO4FMXBQvkGtuYRxxiR9xMa7jMwI=
github.com/urfave/negroni v1.0.0/go.mod h1:Meg73S6kFm/4PpbYdq35yYWoCZ9mS/YSx+lKnmiohz4=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.6.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w=
diff --git a/miner/ordering.go b/miner/ordering.go
deleted file mode 100644
index 4c3055f0d3178..0000000000000
--- a/miner/ordering.go
+++ /dev/null
@@ -1,147 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package miner
-
-import (
- "container/heap"
- "math/big"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/common/math"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
-)
-
-// txWithMinerFee wraps a transaction with its gas price or effective miner gasTipCap
-type txWithMinerFee struct {
- tx *txpool.LazyTransaction
- from common.Address
- fees *big.Int
-}
-
-// newTxWithMinerFee creates a wrapped transaction, calculating the effective
-// miner gasTipCap if a base fee is provided.
-// Returns error in case of a negative effective miner gasTipCap.
-func newTxWithMinerFee(tx *txpool.LazyTransaction, from common.Address, baseFee *big.Int) (*txWithMinerFee, error) {
- tip := new(big.Int).Set(tx.GasTipCap)
- if baseFee != nil {
- if tx.GasFeeCap.Cmp(baseFee) < 0 {
- return nil, types.ErrGasFeeCapTooLow
- }
- tip = math.BigMin(tx.GasTipCap, new(big.Int).Sub(tx.GasFeeCap, baseFee))
- }
- return &txWithMinerFee{
- tx: tx,
- from: from,
- fees: tip,
- }, nil
-}
-
-// txByPriceAndTime implements both the sort and the heap interface, making it useful
-// for all at once sorting as well as individually adding and removing elements.
-type txByPriceAndTime []*txWithMinerFee
-
-func (s txByPriceAndTime) Len() int { return len(s) }
-func (s txByPriceAndTime) Less(i, j int) bool {
- // If the prices are equal, use the time the transaction was first seen for
- // deterministic sorting
- cmp := s[i].fees.Cmp(s[j].fees)
- if cmp == 0 {
- return s[i].tx.Time.Before(s[j].tx.Time)
- }
- return cmp > 0
-}
-func (s txByPriceAndTime) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
-
-func (s *txByPriceAndTime) Push(x interface{}) {
- *s = append(*s, x.(*txWithMinerFee))
-}
-
-func (s *txByPriceAndTime) Pop() interface{} {
- old := *s
- n := len(old)
- x := old[n-1]
- old[n-1] = nil
- *s = old[0 : n-1]
- return x
-}
-
-// transactionsByPriceAndNonce represents a set of transactions that can return
-// transactions in a profit-maximizing sorted order, while supporting removing
-// entire batches of transactions for non-executable accounts.
-type transactionsByPriceAndNonce struct {
- txs map[common.Address][]*txpool.LazyTransaction // Per account nonce-sorted list of transactions
- heads txByPriceAndTime // Next transaction for each unique account (price heap)
- signer types.Signer // Signer for the set of transactions
- baseFee *big.Int // Current base fee
-}
-
-// newTransactionsByPriceAndNonce creates a transaction set that can retrieve
-// price sorted transactions in a nonce-honouring way.
-//
-// Note, the input map is reowned so the caller should not interact any more with
-// if after providing it to the constructor.
-func newTransactionsByPriceAndNonce(signer types.Signer, txs map[common.Address][]*txpool.LazyTransaction, baseFee *big.Int) *transactionsByPriceAndNonce {
- // Initialize a price and received time based heap with the head transactions
- heads := make(txByPriceAndTime, 0, len(txs))
- for from, accTxs := range txs {
- wrapped, err := newTxWithMinerFee(accTxs[0], from, baseFee)
- if err != nil {
- delete(txs, from)
- continue
- }
- heads = append(heads, wrapped)
- txs[from] = accTxs[1:]
- }
- heap.Init(&heads)
-
- // Assemble and return the transaction set
- return &transactionsByPriceAndNonce{
- txs: txs,
- heads: heads,
- signer: signer,
- baseFee: baseFee,
- }
-}
-
-// Peek returns the next transaction by price.
-func (t *transactionsByPriceAndNonce) Peek() *txpool.LazyTransaction {
- if len(t.heads) == 0 {
- return nil
- }
- return t.heads[0].tx
-}
-
-// Shift replaces the current best head with the next one from the same account.
-func (t *transactionsByPriceAndNonce) Shift() {
- acc := t.heads[0].from
- if txs, ok := t.txs[acc]; ok && len(txs) > 0 {
- if wrapped, err := newTxWithMinerFee(txs[0], acc, t.baseFee); err == nil {
- t.heads[0], t.txs[acc] = wrapped, txs[1:]
- heap.Fix(&t.heads, 0)
- return
- }
- }
- heap.Pop(&t.heads)
-}
-
-// Pop removes the best transaction, *not* replacing it with the next one from
-// the same account. This should be used when a transaction cannot be executed
-// and hence all subsequent ones should be discarded from the same account.
-func (t *transactionsByPriceAndNonce) Pop() {
- heap.Pop(&t.heads)
-}
diff --git a/miner/ordering_test.go b/miner/ordering_test.go
deleted file mode 100644
index 589633e0b8dd2..0000000000000
--- a/miner/ordering_test.go
+++ /dev/null
@@ -1,188 +0,0 @@
-// Copyright 2014 The go-ethereum Authors
-// This file is part of the go-ethereum library.
-//
-// The go-ethereum library is free software: you can redistribute it and/or modify
-// it under the terms of the GNU Lesser General Public License as published by
-// the Free Software Foundation, either version 3 of the License, or
-// (at your option) any later version.
-//
-// The go-ethereum library is distributed in the hope that it will be useful,
-// but WITHOUT ANY WARRANTY; without even the implied warranty of
-// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-// GNU Lesser General Public License for more details.
-//
-// You should have received a copy of the GNU Lesser General Public License
-// along with the go-ethereum library. If not, see .
-
-package miner
-
-import (
- "crypto/ecdsa"
- "math/big"
- "math/rand"
- "testing"
- "time"
-
- "github.com/ethereum/go-ethereum/common"
- "github.com/ethereum/go-ethereum/core/txpool"
- "github.com/ethereum/go-ethereum/core/types"
- "github.com/ethereum/go-ethereum/crypto"
-)
-
-func TestTransactionPriceNonceSortLegacy(t *testing.T) {
- testTransactionPriceNonceSort(t, nil)
-}
-
-func TestTransactionPriceNonceSort1559(t *testing.T) {
- testTransactionPriceNonceSort(t, big.NewInt(0))
- testTransactionPriceNonceSort(t, big.NewInt(5))
- testTransactionPriceNonceSort(t, big.NewInt(50))
-}
-
-// Tests that transactions can be correctly sorted according to their price in
-// decreasing order, but at the same time with increasing nonces when issued by
-// the same account.
-func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) {
- // Generate a batch of accounts to start with
- keys := make([]*ecdsa.PrivateKey, 25)
- for i := 0; i < len(keys); i++ {
- keys[i], _ = crypto.GenerateKey()
- }
- signer := types.LatestSignerForChainID(common.Big1)
-
- // Generate a batch of transactions with overlapping values, but shifted nonces
- groups := map[common.Address][]*txpool.LazyTransaction{}
- expectedCount := 0
- for start, key := range keys {
- addr := crypto.PubkeyToAddress(key.PublicKey)
- count := 25
- for i := 0; i < 25; i++ {
- var tx *types.Transaction
- gasFeeCap := rand.Intn(50)
- if baseFee == nil {
- tx = types.NewTx(&types.LegacyTx{
- Nonce: uint64(start + i),
- To: &common.Address{},
- Value: big.NewInt(100),
- Gas: 100,
- GasPrice: big.NewInt(int64(gasFeeCap)),
- Data: nil,
- })
- } else {
- tx = types.NewTx(&types.DynamicFeeTx{
- Nonce: uint64(start + i),
- To: &common.Address{},
- Value: big.NewInt(100),
- Gas: 100,
- GasFeeCap: big.NewInt(int64(gasFeeCap)),
- GasTipCap: big.NewInt(int64(rand.Intn(gasFeeCap + 1))),
- Data: nil,
- })
- if count == 25 && int64(gasFeeCap) < baseFee.Int64() {
- count = i
- }
- }
- tx, err := types.SignTx(tx, signer, key)
- if err != nil {
- t.Fatalf("failed to sign tx: %s", err)
- }
- groups[addr] = append(groups[addr], &txpool.LazyTransaction{
- Hash: tx.Hash(),
- Tx: &txpool.Transaction{Tx: tx},
- Time: tx.Time(),
- GasFeeCap: tx.GasFeeCap(),
- GasTipCap: tx.GasTipCap(),
- })
- }
- expectedCount += count
- }
- // Sort the transactions and cross check the nonce ordering
- txset := newTransactionsByPriceAndNonce(signer, groups, baseFee)
-
- txs := types.Transactions{}
- for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
- txs = append(txs, tx.Tx.Tx)
- txset.Shift()
- }
- if len(txs) != expectedCount {
- t.Errorf("expected %d transactions, found %d", expectedCount, len(txs))
- }
- for i, txi := range txs {
- fromi, _ := types.Sender(signer, txi)
-
- // Make sure the nonce order is valid
- for j, txj := range txs[i+1:] {
- fromj, _ := types.Sender(signer, txj)
- if fromi == fromj && txi.Nonce() > txj.Nonce() {
- t.Errorf("invalid nonce ordering: tx #%d (A=%x N=%v) < tx #%d (A=%x N=%v)", i, fromi[:4], txi.Nonce(), i+j, fromj[:4], txj.Nonce())
- }
- }
- // If the next tx has different from account, the price must be lower than the current one
- if i+1 < len(txs) {
- next := txs[i+1]
- fromNext, _ := types.Sender(signer, next)
- tip, err := txi.EffectiveGasTip(baseFee)
- nextTip, nextErr := next.EffectiveGasTip(baseFee)
- if err != nil || nextErr != nil {
- t.Errorf("error calculating effective tip: %v, %v", err, nextErr)
- }
- if fromi != fromNext && tip.Cmp(nextTip) < 0 {
- t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
- }
- }
- }
-}
-
-// Tests that if multiple transactions have the same price, the ones seen earlier
-// are prioritized to avoid network spam attacks aiming for a specific ordering.
-func TestTransactionTimeSort(t *testing.T) {
- // Generate a batch of accounts to start with
- keys := make([]*ecdsa.PrivateKey, 5)
- for i := 0; i < len(keys); i++ {
- keys[i], _ = crypto.GenerateKey()
- }
- signer := types.HomesteadSigner{}
-
- // Generate a batch of transactions with overlapping prices, but different creation times
- groups := map[common.Address][]*txpool.LazyTransaction{}
- for start, key := range keys {
- addr := crypto.PubkeyToAddress(key.PublicKey)
-
- tx, _ := types.SignTx(types.NewTransaction(0, common.Address{}, big.NewInt(100), 100, big.NewInt(1), nil), signer, key)
- tx.SetTime(time.Unix(0, int64(len(keys)-start)))
-
- groups[addr] = append(groups[addr], &txpool.LazyTransaction{
- Hash: tx.Hash(),
- Tx: &txpool.Transaction{Tx: tx},
- Time: tx.Time(),
- GasFeeCap: tx.GasFeeCap(),
- GasTipCap: tx.GasTipCap(),
- })
- }
- // Sort the transactions and cross check the nonce ordering
- txset := newTransactionsByPriceAndNonce(signer, groups, nil)
-
- txs := types.Transactions{}
- for tx := txset.Peek(); tx != nil; tx = txset.Peek() {
- txs = append(txs, tx.Tx.Tx)
- txset.Shift()
- }
- if len(txs) != len(keys) {
- t.Errorf("expected %d transactions, found %d", len(keys), len(txs))
- }
- for i, txi := range txs {
- fromi, _ := types.Sender(signer, txi)
- if i+1 < len(txs) {
- next := txs[i+1]
- fromNext, _ := types.Sender(signer, next)
-
- if txi.GasPrice().Cmp(next.GasPrice()) < 0 {
- t.Errorf("invalid gasprice ordering: tx #%d (A=%x P=%v) < tx #%d (A=%x P=%v)", i, fromi[:4], txi.GasPrice(), i+1, fromNext[:4], next.GasPrice())
- }
- // Make sure time order is ascending if the txs have the same gas price
- if txi.GasPrice().Cmp(next.GasPrice()) == 0 && txi.Time().After(next.Time()) {
- t.Errorf("invalid received time ordering: tx #%d (A=%x T=%v) > tx #%d (A=%x T=%v)", i, fromi[:4], txi.Time(), i+1, fromNext[:4], next.Time())
- }
- }
- }
-}
diff --git a/miner/worker.go b/miner/worker.go
index f05d56703037a..d524bb133dfc6 100644
--- a/miner/worker.go
+++ b/miner/worker.go
@@ -29,7 +29,6 @@ import (
"github.com/ethereum/go-ethereum/consensus/misc"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/state"
- "github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
@@ -534,18 +533,12 @@ func (w *worker) mainLoop() {
if gp := w.current.gasPool; gp != nil && gp.Gas() < params.TxGas {
continue
}
- txs := make(map[common.Address][]*txpool.LazyTransaction, len(ev.Txs))
+ txs := make(map[common.Address][]*types.Transaction, len(ev.Txs))
for _, tx := range ev.Txs {
acc, _ := types.Sender(w.current.signer, tx)
- txs[acc] = append(txs[acc], &txpool.LazyTransaction{
- Hash: tx.Hash(),
- Tx: &txpool.Transaction{Tx: tx},
- Time: tx.Time(),
- GasFeeCap: tx.GasFeeCap(),
- GasTipCap: tx.GasTipCap(),
- })
+ txs[acc] = append(txs[acc], tx)
}
- txset := newTransactionsByPriceAndNonce(w.current.signer, txs, w.current.header.BaseFee)
+ txset := types.NewTransactionsByPriceAndNonce(w.current.signer, txs, w.current.header.BaseFee)
tcount := w.current.tcount
w.commitTransactions(w.current, txset, nil)
@@ -734,24 +727,24 @@ func (w *worker) updateSnapshot(env *environment) {
w.snapshotState = env.state.Copy()
}
-func (w *worker) commitTransaction(env *environment, tx *txpool.Transaction) ([]*types.Log, error) {
+func (w *worker) commitTransaction(env *environment, tx *types.Transaction) ([]*types.Log, error) {
var (
snap = env.state.Snapshot()
gp = env.gasPool.Gas()
)
- receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx.Tx, &env.header.GasUsed, *w.chain.GetVMConfig())
+ receipt, err := core.ApplyTransaction(w.chainConfig, w.chain, &env.coinbase, env.gasPool, env.state, env.header, tx, &env.header.GasUsed, *w.chain.GetVMConfig())
if err != nil {
env.state.RevertToSnapshot(snap)
env.gasPool.SetGas(gp)
return nil, err
}
- env.txs = append(env.txs, tx.Tx)
+ env.txs = append(env.txs, tx)
env.receipts = append(env.receipts, receipt)
return receipt.Logs, nil
}
-func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAndNonce, interrupt *atomic.Int32) error {
+func (w *worker) commitTransactions(env *environment, txs *types.TransactionsByPriceAndNonce, interrupt *atomic.Int32) error {
gasLimit := env.header.GasLimit
if env.gasPool == nil {
env.gasPool = new(core.GasPool).AddGas(gasLimit)
@@ -771,37 +764,30 @@ func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAn
break
}
// Retrieve the next transaction and abort if all done.
- ltx := txs.Peek()
- if ltx == nil {
- break
- }
- tx := ltx.Resolve()
+ tx := txs.Peek()
if tx == nil {
- log.Warn("Ignoring evicted transaction")
-
- txs.Pop()
- continue
+ break
}
// Error may be ignored here. The error has already been checked
// during transaction acceptance is the transaction pool.
- from, _ := types.Sender(env.signer, tx.Tx)
+ from, _ := types.Sender(env.signer, tx)
// Check whether the tx is replay protected. If we're not in the EIP155 hf
// phase, start ignoring the sender until we do.
- if tx.Tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
- log.Trace("Ignoring reply protected transaction", "hash", tx.Tx.Hash(), "eip155", w.chainConfig.EIP155Block)
+ if tx.Protected() && !w.chainConfig.IsEIP155(env.header.Number) {
+ log.Trace("Ignoring reply protected transaction", "hash", tx.Hash(), "eip155", w.chainConfig.EIP155Block)
txs.Pop()
continue
}
// Start executing the transaction
- env.state.SetTxContext(tx.Tx.Hash(), env.tcount)
+ env.state.SetTxContext(tx.Hash(), env.tcount)
logs, err := w.commitTransaction(env, tx)
switch {
case errors.Is(err, core.ErrNonceTooLow):
// New head notification data race between the transaction pool and miner, shift
- log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Tx.Nonce())
+ log.Trace("Skipping transaction with low nonce", "sender", from, "nonce", tx.Nonce())
txs.Shift()
case errors.Is(err, nil):
@@ -813,7 +799,7 @@ func (w *worker) commitTransactions(env *environment, txs *transactionsByPriceAn
default:
// Transaction is regarded as invalid, drop all consecutive transactions from
// the same sender because of `nonce-too-high` clause.
- log.Debug("Transaction failed, account skipped", "hash", tx.Tx.Hash(), "err", err)
+ log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
txs.Pop()
}
}
@@ -919,7 +905,7 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err
// Fill the block with all available pending transactions.
pending := w.eth.TxPool().Pending(true)
- localTxs, remoteTxs := make(map[common.Address][]*txpool.LazyTransaction), pending
+ localTxs, remoteTxs := make(map[common.Address][]*types.Transaction), pending
for _, account := range w.eth.TxPool().Locals() {
if txs := remoteTxs[account]; len(txs) > 0 {
delete(remoteTxs, account)
@@ -927,13 +913,13 @@ func (w *worker) fillTransactions(interrupt *atomic.Int32, env *environment) err
}
}
if len(localTxs) > 0 {
- txs := newTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee)
+ txs := types.NewTransactionsByPriceAndNonce(env.signer, localTxs, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
return err
}
}
if len(remoteTxs) > 0 {
- txs := newTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee)
+ txs := types.NewTransactionsByPriceAndNonce(env.signer, remoteTxs, env.header.BaseFee)
if err := w.commitTransactions(env, txs, interrupt); err != nil {
return err
}