go-ethereum/miner/tx_source.go

265 lines
7.4 KiB
Go

// Copyright 2026 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 <http://www.gnu.org/licenses/>.
package miner
import (
"fmt"
"slices"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/txpool"
"github.com/ethereum/go-ethereum/core/types"
)
// resolveableTransaction represents a transaction of which some of the fields
// are available, but the full transaction will be resolved from some source.
type resolveableTransaction interface {
Hash() common.Hash
Resolve() *types.Transaction
Gas() uint64
BlobGas() uint64
}
// lazyTransaction wraps a txpool.LazyTransaction and implements resolveableTransaction.
type lazyTransaction struct {
*txpool.LazyTransaction
}
func (l *lazyTransaction) Resolve() *types.Transaction {
return l.LazyTransaction.Resolve()
}
func (l *lazyTransaction) Gas() uint64 {
return l.LazyTransaction.Gas
}
func (l *lazyTransaction) Hash() common.Hash {
return l.LazyTransaction.Hash
}
func (l *lazyTransaction) BlobGas() uint64 {
return l.LazyTransaction.BlobGas
}
// resolvedTransaction implements resolveableTransaction for a transaction
// that was resolved in the first place. used for building payloads with
// tx set overrides
type resolvedTransaction struct {
*types.Transaction
}
func (r *resolvedTransaction) Gas() uint64 {
return r.Transaction.Gas()
}
func (r *resolvedTransaction) Resolve() *types.Transaction {
return r.Transaction
}
// transactionQueue represents a list of transactions
// which are queued for inclusion.
type transactionQueue interface {
// Shift removes all transactions from the sender of the
// current highest-priority transaction for inclusion
Shift()
// Pop removes the highest-priority transaction from
// the queue.
Pop()
// HasBlobTxs returns true if the sender of the current
// highest-priority transaction has queued blob transactions.
HasBlobTxs() bool
// ClearBlobTxs removes all blob txs from the sender of the
// current highest-priority transaction from the queue.
ClearBlobTxs()
}
// transactionSource is a source that the miner can
type transactionSource interface {
// Peek returns the next transaction which should be evaluated for inclusion
// and a transactionQueue for the sender account.
Peek() (resolveableTransaction, transactionQueue)
}
// plainTxQueue implements transactionQueue for a set of non-blob transactions
type plainTxQueue struct {
txs *transactionsByPriceAndNonce
}
func (q *plainTxQueue) Shift() { q.txs.Shift() }
func (q *plainTxQueue) Pop() { q.txs.Pop() }
// HasBlobTxs always returns false: plain queues never carry blob transactions.
func (q *plainTxQueue) HasBlobTxs() bool { return false }
// ClearBlobTxs is a no-op for plain queues and always returns false.
func (q *plainTxQueue) ClearBlobTxs() {}
// blobTxQueue implements transactionQueue for a set of blob transactions
type blobTxQueue struct {
txs *transactionsByPriceAndNonce
}
func (q *blobTxQueue) Shift() {
if q.txs != nil {
q.txs.Shift()
}
}
func (q *blobTxQueue) Pop() {
if q.txs != nil {
q.txs.Pop()
}
}
// HasBlobTxs always returns true: this queue exclusively holds blob transactions.
func (q *blobTxQueue) HasBlobTxs() bool { return true }
func (q *blobTxQueue) ClearBlobTxs() {
if q.txs != nil {
q.txs.Clear()
}
}
// feeOrderedTxSource implements transactionSource for the standard strategy
// of transaction inclusion: prioritize by profitability.
type feeOrderedTxSource struct {
plainQueue *plainTxQueue
blobQueue *blobTxQueue
}
// newFeeOrderedTxSource creates a feeOrderedTxSource from separate plain and blob
// transaction sets. Either argument may be nil (e.g. when there are no blob
// transactions pending).
func newFeeOrderedTxSource(plain, blob *transactionsByPriceAndNonce) *feeOrderedTxSource {
s := &feeOrderedTxSource{
plainQueue: &plainTxQueue{txs: plain},
blobQueue: &blobTxQueue{txs: blob},
}
return s
}
// Peek returns the next most profitable queued transaction
func (s *feeOrderedTxSource) Peek() (resolveableTransaction, transactionQueue) {
pTx, pTip := s.plainQueue.txs.Peek()
bTx, bTip := s.blobQueue.txs.Peek()
switch {
case pTx == nil && bTx == nil:
return nil, nil
case pTx == nil:
return &lazyTransaction{bTx}, s.blobQueue
case bTx == nil:
return &lazyTransaction{pTx}, s.plainQueue
default:
if bTip.Gt(pTip) {
return &lazyTransaction{bTx}, s.blobQueue
} else {
return &lazyTransaction{pTx}, s.plainQueue
}
}
}
// orderedTxSource implements transactionSource and transactionQueue.
// The transaction set and order is based on a pre-determined list.
type orderedTxSource struct {
// txs ordered as they are intended to be included in a payload
txs types.Transactions
// txs ordered by sender and nonce
orderedTxs map[common.Address][]*types.Transaction
signer types.Signer
}
func newOrderedTxSource(txs types.Transactions, signer types.Signer) (*orderedTxSource, error) {
orderedTxs := make(map[common.Address][]*types.Transaction)
for _, tx := range txs {
from, _ := signer.Sender(tx)
if _, ok := orderedTxs[from]; !ok {
orderedTxs[from] = []*types.Transaction{tx}
continue
}
// validate that no transactions from the same sender have conflicting nonces.
senderTxs := orderedTxs[from]
for _, senderTx := range senderTxs {
if tx.Nonce() == senderTx.Nonce() {
return nil, fmt.Errorf("conflicting transaction nonces")
}
}
var (
insertionPoint int
senderTx *types.Transaction
)
for insertionPoint, senderTx = range senderTxs {
if senderTx.Nonce() > tx.Nonce() {
break
}
}
orderedTxs[from] = slices.Insert(senderTxs, insertionPoint, tx)
}
return &orderedTxSource{txs: txs, signer: signer, orderedTxs: orderedTxs}, nil
}
func (s *orderedTxSource) Pop() {
from, _ := s.signer.Sender(s.txs[0])
s.txs = slices.DeleteFunc(s.txs, func(tx *types.Transaction) bool {
txSender, _ := s.signer.Sender(tx)
return txSender == from
})
delete(s.orderedTxs, from)
}
func (s *orderedTxSource) Shift() {
delTx := s.txs[0]
sender, _ := s.signer.Sender(delTx)
// application order of txs might not be by nonce,
// so deletion from the nonce-ordered set requires
// search
s.orderedTxs[sender] = slices.DeleteFunc(s.orderedTxs[sender], func(tx *types.Transaction) bool {
return delTx.Hash() == tx.Hash()
})
s.txs = s.txs[1:]
}
func (s *orderedTxSource) HasBlobTxs() bool {
for _, tx := range s.txs {
if tx.Type() == types.BlobTxType {
return true
}
}
return false
}
func (s *orderedTxSource) ClearBlobTxs() {
var remainingTxs types.Transactions
for _, tx := range s.txs {
if tx.Type() != types.BlobTxType {
remainingTxs = append(remainingTxs, tx)
}
}
s.txs = remainingTxs
}
func (s *orderedTxSource) Peek() (resolveableTransaction, transactionQueue) {
if len(s.txs) == 0 {
return nil, nil
}
return &resolvedTransaction{s.txs[0]}, s
}