perf(core/txpool,miner): speed up pending transaction ordering with uint256 #29008 (#2159)

Switch LazyTransaction gas caps from *big.Int to *uint256.Int and convert once at pending retrieval time.

In miner ordering, keep fee comparisons on uint256 and precompute TRC21 gas price in uint256 form to avoid repeated big-int conversions in heap comparisons.

Also update affected tests and call sites in legacypool/worker/helper paths.

Compatibility: LazyTransaction exported field types changed (GasFeeCap/GasTipCap).
This commit is contained in:
Daniel Liu 2026-03-11 11:20:56 +08:00 committed by GitHub
parent b5eec529d0
commit 8850835f6b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 45 additions and 35 deletions

View file

@ -618,8 +618,8 @@ func (pool *LegacyPool) Pending(minTip *uint256.Int, baseFee *uint256.Int) map[c
Hash: txs[i].Hash(),
Tx: txs[i],
Time: txs[i].Time(),
GasFeeCap: txs[i].GasFeeCap(),
GasTipCap: txs[i].GasTipCap(),
GasFeeCap: uint256.MustFromBig(txs[i].GasFeeCap()),
GasTipCap: uint256.MustFromBig(txs[i].GasTipCap()),
Gas: txs[i].Gas(),
}
}

View file

@ -35,9 +35,9 @@ type LazyTransaction struct {
Hash common.Hash // Transaction hash to pull up if needed
Tx *types.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
Time time.Time // Time when the transaction was first seen
GasFeeCap *uint256.Int // Maximum fee per gas the transaction may consume
GasTipCap *uint256.Int // Maximum miner tip per gas the transaction can pay
Gas uint64 // Amount of gas required by the transaction
}

View file

@ -166,8 +166,8 @@ func (p *testTxPool) Pending(minTip *uint256.Int, baseFee *uint256.Int) map[comm
Hash: tx.Hash(),
Tx: tx,
Time: tx.Time(),
GasFeeCap: tx.GasFeeCap(),
GasTipCap: tx.GasTipCap(),
GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
})
}
}

View file

@ -23,27 +23,30 @@ import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/holiman/uint256"
)
var trc21GasPriceUint = uint256.MustFromBig(common.TRC21GasPrice)
// txWithMinerFee wraps a transaction with its gas price or effective miner gasTipCap
type txWithMinerFee struct {
tx *txpool.LazyTransaction
from common.Address
fees *big.Int
fees *uint256.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)
func newTxWithMinerFee(tx *txpool.LazyTransaction, from common.Address, baseFee *uint256.Int) (*txWithMinerFee, error) {
tip := new(uint256.Int).Set(tx.GasTipCap)
if baseFee != nil {
if tx.GasFeeCap.Cmp(baseFee) < 0 {
return nil, types.ErrGasFeeCapTooLow
}
effectiveTip := new(big.Int).Sub(tx.GasFeeCap, baseFee)
if tip.Cmp(effectiveTip) > 0 {
tip = effectiveTip
tip = new(uint256.Int).Sub(tx.GasFeeCap, baseFee)
if tip.Gt(tx.GasTipCap) {
tip = tx.GasTipCap
}
}
return &txWithMinerFee{
@ -68,14 +71,14 @@ func (s txByPriceAndTime) Less(i, j int) bool {
i_price := s.txs[i].fees
if tx := s.txs[i].tx.Resolve(); tx != nil && tx.To() != nil {
if _, ok := s.payersSwap[*tx.To()]; ok {
i_price = common.TRC21GasPrice
i_price = trc21GasPriceUint
}
}
j_price := s.txs[j].fees
if tx := s.txs[j].tx.Resolve(); tx != nil && tx.To() != nil {
if _, ok := s.payersSwap[*tx.To()]; ok {
j_price = common.TRC21GasPrice
j_price = trc21GasPriceUint
}
}
@ -112,7 +115,7 @@ 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
baseFee *uint256.Int // Current base fee
}
// newTransactionsByPriceAndNonce creates a transaction set that can retrieve
@ -121,6 +124,11 @@ type transactionsByPriceAndNonce struct {
// 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, payersSwap map[common.Address]*big.Int, baseFee *big.Int) (*transactionsByPriceAndNonce, types.Transactions) {
// Convert the basefee from header format to uint256 format
var baseFeeUint *uint256.Int
if baseFee != nil {
baseFeeUint = uint256.MustFromBig(baseFee)
}
// Initialize a price and received time based heap with the head transactions
heads := txByPriceAndTime{
txs: make([]*txWithMinerFee, 0, len(txs)),
@ -137,7 +145,7 @@ func newTransactionsByPriceAndNonce(signer types.Signer, txs map[common.Address]
}
}
if len(normalTxs) > 0 {
wrapped, err := newTxWithMinerFee(normalTxs[0], from, baseFee)
wrapped, err := newTxWithMinerFee(normalTxs[0], from, baseFeeUint)
if err != nil {
delete(txs, from)
continue
@ -157,7 +165,7 @@ func newTransactionsByPriceAndNonce(signer types.Signer, txs map[common.Address]
txs: txs,
heads: heads,
signer: signer,
baseFee: baseFee,
baseFee: baseFeeUint,
}, specialTxs
}

View file

@ -27,6 +27,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/holiman/uint256"
)
func TestTransactionPriceNonceSortLegacy(t *testing.T) {
@ -90,8 +91,8 @@ func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) {
Hash: tx.Hash(),
Tx: tx,
Time: tx.Time(),
GasFeeCap: tx.GasFeeCap(),
GasTipCap: tx.GasTipCap(),
GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
})
}
expectedCount += count
@ -155,8 +156,8 @@ func TestTransactionTimeSort(t *testing.T) {
Hash: tx.Hash(),
Tx: tx,
Time: tx.Time(),
GasFeeCap: tx.GasFeeCap(),
GasTipCap: tx.GasTipCap(),
GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
})
}
// Sort the transactions and cross check the nonce ordering
@ -193,11 +194,11 @@ func TestNewTransactionsByPriceAndNonce_SpecialSeparation(t *testing.T) {
genNormalTx := func(nonce uint64, key *ecdsa.PrivateKey) *txpool.LazyTransaction {
tx, _ := types.SignTx(types.NewTransaction(nonce, common.HexToAddress("0x1234567890123456789012345678901234567890"), big.NewInt(1), 21000, big.NewInt(1), nil), signer, key)
return &txpool.LazyTransaction{Tx: tx, Hash: tx.Hash(), Time: tx.Time(), GasFeeCap: tx.GasFeeCap(), GasTipCap: tx.GasTipCap()}
return &txpool.LazyTransaction{Tx: tx, Hash: tx.Hash(), Time: tx.Time(), GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()), GasTipCap: uint256.MustFromBig(tx.GasTipCap())}
}
genSpecialTx := func(nonce uint64, key *ecdsa.PrivateKey) *txpool.LazyTransaction {
tx, _ := types.SignTx(types.NewTransaction(nonce, common.BlockSignersBinary, big.NewInt(1), 21000, big.NewInt(1), nil), signer, key)
return &txpool.LazyTransaction{Tx: tx, Hash: tx.Hash(), Time: tx.Time(), GasFeeCap: tx.GasFeeCap(), GasTipCap: tx.GasTipCap()}
return &txpool.LazyTransaction{Tx: tx, Hash: tx.Hash(), Time: tx.Time(), GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()), GasTipCap: uint256.MustFromBig(tx.GasTipCap())}
}
testCases := []struct {

View file

@ -145,7 +145,7 @@ type worker struct {
mu sync.Mutex
coinbase common.Address
extra []byte
tip *big.Int // Minimum tip needed for non-local transaction to include them
tip *uint256.Int // Minimum tip needed for non-local transaction to include them
snapshotMu sync.RWMutex // The lock used to protect the block snapshot and state snapshot
snapshotBlock *types.Block
@ -176,7 +176,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
mux: mux,
coinbase: config.Etherbase,
extra: config.ExtraData,
tip: config.GasPrice,
tip: uint256.MustFromBig(config.GasPrice),
txsCh: make(chan core.NewTxsEvent, txChanSize),
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize),
@ -233,7 +233,7 @@ func (w *worker) setGasTip(tip *big.Int) error {
}
// Copy the value to avoid external mutation through shared pointers.
w.tip = new(big.Int).Set(tip)
w.tip = uint256.MustFromBig(tip)
log.Info("Worker tip updated", "tip", w.tip)
return nil
}
@ -404,8 +404,8 @@ func (w *worker) update() {
Hash: tx.Hash(),
Tx: tx,
Time: tx.Time(),
GasFeeCap: tx.GasFeeCap(),
GasTipCap: tx.GasTipCap(),
GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
})
}
feeCapacity := w.current.state.GetTRC21FeeCapacityFromState()
@ -866,7 +866,7 @@ func (w *worker) commitNewWork() {
if header.BaseFee != nil {
baseFee = uint256.MustFromBig(header.BaseFee)
}
pending := w.eth.TxPool().Pending(uint256.MustFromBig(w.tip), baseFee)
pending := w.eth.TxPool().Pending(w.tip, baseFee)
txs, specialTxs = newTransactionsByPriceAndNonce(w.current.signer, pending, feeCapacity, header.BaseFee)
}
}

View file

@ -30,6 +30,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/holiman/uint256"
)
func newBlockingSubscription() event.Subscription {
@ -132,8 +133,8 @@ func TestWorkerCheckPreCommitXDPoSMismatch(t *testing.T) {
}
func TestWorkerSetGasTipValidation(t *testing.T) {
w := &worker{tip: big.NewInt(1)}
old := new(big.Int).Set(w.tip)
w := &worker{tip: uint256.NewInt(1)}
old := new(uint256.Int).Set(w.tip)
tests := []struct {
name string
@ -163,12 +164,12 @@ func TestWorkerSetGasTipCopiesValue(t *testing.T) {
if err := w.setGasTip(input); err != nil {
t.Fatalf("setGasTip failed: %v", err)
}
if w.tip == input {
t.Fatal("worker tip shares pointer with input")
if w.tip == nil {
t.Fatal("worker tip was not set")
}
input.Add(input, big.NewInt(1))
if w.tip.Cmp(big.NewInt(2*params.GWei)) != 0 {
if w.tip.Cmp(uint256.NewInt(2*params.GWei)) != 0 {
t.Fatalf("worker tip mutated via input pointer: have %v", w.tip)
}
}