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(), Hash: txs[i].Hash(),
Tx: txs[i], Tx: txs[i],
Time: txs[i].Time(), Time: txs[i].Time(),
GasFeeCap: txs[i].GasFeeCap(), GasFeeCap: uint256.MustFromBig(txs[i].GasFeeCap()),
GasTipCap: txs[i].GasTipCap(), GasTipCap: uint256.MustFromBig(txs[i].GasTipCap()),
Gas: txs[i].Gas(), Gas: txs[i].Gas(),
} }
} }

View file

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

View file

@ -23,27 +23,30 @@ import (
"github.com/XinFinOrg/XDPoSChain/common" "github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/txpool" "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types" "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 // txWithMinerFee wraps a transaction with its gas price or effective miner gasTipCap
type txWithMinerFee struct { type txWithMinerFee struct {
tx *txpool.LazyTransaction tx *txpool.LazyTransaction
from common.Address from common.Address
fees *big.Int fees *uint256.Int
} }
// newTxWithMinerFee creates a wrapped transaction, calculating the effective // newTxWithMinerFee creates a wrapped transaction, calculating the effective
// miner gasTipCap if a base fee is provided. // miner gasTipCap if a base fee is provided.
// Returns error in case of a negative effective miner gasTipCap. // Returns error in case of a negative effective miner gasTipCap.
func newTxWithMinerFee(tx *txpool.LazyTransaction, from common.Address, baseFee *big.Int) (*txWithMinerFee, error) { func newTxWithMinerFee(tx *txpool.LazyTransaction, from common.Address, baseFee *uint256.Int) (*txWithMinerFee, error) {
tip := new(big.Int).Set(tx.GasTipCap) tip := new(uint256.Int).Set(tx.GasTipCap)
if baseFee != nil { if baseFee != nil {
if tx.GasFeeCap.Cmp(baseFee) < 0 { if tx.GasFeeCap.Cmp(baseFee) < 0 {
return nil, types.ErrGasFeeCapTooLow return nil, types.ErrGasFeeCapTooLow
} }
effectiveTip := new(big.Int).Sub(tx.GasFeeCap, baseFee) tip = new(uint256.Int).Sub(tx.GasFeeCap, baseFee)
if tip.Cmp(effectiveTip) > 0 { if tip.Gt(tx.GasTipCap) {
tip = effectiveTip tip = tx.GasTipCap
} }
} }
return &txWithMinerFee{ return &txWithMinerFee{
@ -68,14 +71,14 @@ func (s txByPriceAndTime) Less(i, j int) bool {
i_price := s.txs[i].fees i_price := s.txs[i].fees
if tx := s.txs[i].tx.Resolve(); tx != nil && tx.To() != nil { if tx := s.txs[i].tx.Resolve(); tx != nil && tx.To() != nil {
if _, ok := s.payersSwap[*tx.To()]; ok { if _, ok := s.payersSwap[*tx.To()]; ok {
i_price = common.TRC21GasPrice i_price = trc21GasPriceUint
} }
} }
j_price := s.txs[j].fees j_price := s.txs[j].fees
if tx := s.txs[j].tx.Resolve(); tx != nil && tx.To() != nil { if tx := s.txs[j].tx.Resolve(); tx != nil && tx.To() != nil {
if _, ok := s.payersSwap[*tx.To()]; ok { 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 txs map[common.Address][]*txpool.LazyTransaction // Per account nonce-sorted list of transactions
heads txByPriceAndTime // Next transaction for each unique account (price heap) heads txByPriceAndTime // Next transaction for each unique account (price heap)
signer types.Signer // Signer for the set of transactions 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 // 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 // Note, the input map is reowned so the caller should not interact any more with
// if after providing it to the constructor. // 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) { 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 // Initialize a price and received time based heap with the head transactions
heads := txByPriceAndTime{ heads := txByPriceAndTime{
txs: make([]*txWithMinerFee, 0, len(txs)), txs: make([]*txWithMinerFee, 0, len(txs)),
@ -137,7 +145,7 @@ func newTransactionsByPriceAndNonce(signer types.Signer, txs map[common.Address]
} }
} }
if len(normalTxs) > 0 { if len(normalTxs) > 0 {
wrapped, err := newTxWithMinerFee(normalTxs[0], from, baseFee) wrapped, err := newTxWithMinerFee(normalTxs[0], from, baseFeeUint)
if err != nil { if err != nil {
delete(txs, from) delete(txs, from)
continue continue
@ -157,7 +165,7 @@ func newTransactionsByPriceAndNonce(signer types.Signer, txs map[common.Address]
txs: txs, txs: txs,
heads: heads, heads: heads,
signer: signer, signer: signer,
baseFee: baseFee, baseFee: baseFeeUint,
}, specialTxs }, specialTxs
} }

View file

@ -27,6 +27,7 @@ import (
"github.com/XinFinOrg/XDPoSChain/core/txpool" "github.com/XinFinOrg/XDPoSChain/core/txpool"
"github.com/XinFinOrg/XDPoSChain/core/types" "github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto" "github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/holiman/uint256"
) )
func TestTransactionPriceNonceSortLegacy(t *testing.T) { func TestTransactionPriceNonceSortLegacy(t *testing.T) {
@ -90,8 +91,8 @@ func testTransactionPriceNonceSort(t *testing.T, baseFee *big.Int) {
Hash: tx.Hash(), Hash: tx.Hash(),
Tx: tx, Tx: tx,
Time: tx.Time(), Time: tx.Time(),
GasFeeCap: tx.GasFeeCap(), GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
GasTipCap: tx.GasTipCap(), GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
}) })
} }
expectedCount += count expectedCount += count
@ -155,8 +156,8 @@ func TestTransactionTimeSort(t *testing.T) {
Hash: tx.Hash(), Hash: tx.Hash(),
Tx: tx, Tx: tx,
Time: tx.Time(), Time: tx.Time(),
GasFeeCap: tx.GasFeeCap(), GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
GasTipCap: tx.GasTipCap(), GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
}) })
} }
// Sort the transactions and cross check the nonce ordering // 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 { 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) 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 { 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) 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 { testCases := []struct {

View file

@ -145,7 +145,7 @@ type worker struct {
mu sync.Mutex mu sync.Mutex
coinbase common.Address coinbase common.Address
extra []byte 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 snapshotMu sync.RWMutex // The lock used to protect the block snapshot and state snapshot
snapshotBlock *types.Block snapshotBlock *types.Block
@ -176,7 +176,7 @@ func newWorker(config *Config, chainConfig *params.ChainConfig, engine consensus
mux: mux, mux: mux,
coinbase: config.Etherbase, coinbase: config.Etherbase,
extra: config.ExtraData, extra: config.ExtraData,
tip: config.GasPrice, tip: uint256.MustFromBig(config.GasPrice),
txsCh: make(chan core.NewTxsEvent, txChanSize), txsCh: make(chan core.NewTxsEvent, txChanSize),
chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize), chainHeadCh: make(chan core.ChainHeadEvent, chainHeadChanSize),
chainSideCh: make(chan core.ChainSideEvent, chainSideChanSize), 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. // 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) log.Info("Worker tip updated", "tip", w.tip)
return nil return nil
} }
@ -404,8 +404,8 @@ func (w *worker) update() {
Hash: tx.Hash(), Hash: tx.Hash(),
Tx: tx, Tx: tx,
Time: tx.Time(), Time: tx.Time(),
GasFeeCap: tx.GasFeeCap(), GasFeeCap: uint256.MustFromBig(tx.GasFeeCap()),
GasTipCap: tx.GasTipCap(), GasTipCap: uint256.MustFromBig(tx.GasTipCap()),
}) })
} }
feeCapacity := w.current.state.GetTRC21FeeCapacityFromState() feeCapacity := w.current.state.GetTRC21FeeCapacityFromState()
@ -866,7 +866,7 @@ func (w *worker) commitNewWork() {
if header.BaseFee != nil { if header.BaseFee != nil {
baseFee = uint256.MustFromBig(header.BaseFee) 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) 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/core/vm"
"github.com/XinFinOrg/XDPoSChain/event" "github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/params" "github.com/XinFinOrg/XDPoSChain/params"
"github.com/holiman/uint256"
) )
func newBlockingSubscription() event.Subscription { func newBlockingSubscription() event.Subscription {
@ -132,8 +133,8 @@ func TestWorkerCheckPreCommitXDPoSMismatch(t *testing.T) {
} }
func TestWorkerSetGasTipValidation(t *testing.T) { func TestWorkerSetGasTipValidation(t *testing.T) {
w := &worker{tip: big.NewInt(1)} w := &worker{tip: uint256.NewInt(1)}
old := new(big.Int).Set(w.tip) old := new(uint256.Int).Set(w.tip)
tests := []struct { tests := []struct {
name string name string
@ -163,12 +164,12 @@ func TestWorkerSetGasTipCopiesValue(t *testing.T) {
if err := w.setGasTip(input); err != nil { if err := w.setGasTip(input); err != nil {
t.Fatalf("setGasTip failed: %v", err) t.Fatalf("setGasTip failed: %v", err)
} }
if w.tip == input { if w.tip == nil {
t.Fatal("worker tip shares pointer with input") t.Fatal("worker tip was not set")
} }
input.Add(input, big.NewInt(1)) 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) t.Fatalf("worker tip mutated via input pointer: have %v", w.tip)
} }
} }