From 93f1369fa327a53089745c1261f2b5c6ddbabe85 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Thu, 16 Oct 2025 15:24:16 +0800 Subject: [PATCH 1/4] core: prioritize the heavy transaction for prefetching --- core/blockchain.go | 1 + core/state_prefetcher.go | 71 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 69 insertions(+), 3 deletions(-) diff --git a/core/blockchain.go b/core/blockchain.go index 858eceb630..47e26d61dc 100644 --- a/core/blockchain.go +++ b/core/blockchain.go @@ -108,6 +108,7 @@ var ( blockPrefetchInterruptMeter = metrics.NewRegisteredMeter("chain/prefetch/interrupts", nil) blockPrefetchTxsInvalidMeter = metrics.NewRegisteredMeter("chain/prefetch/txs/invalid", nil) blockPrefetchTxsValidMeter = metrics.NewRegisteredMeter("chain/prefetch/txs/valid", nil) + blockPrefetchHeavyTxsMeter = metrics.NewRegisteredMeter("chain/prefetch/txs/heavy", nil) errInsertionInterrupted = errors.New("insertion is interrupted") errChainStopped = errors.New("blockchain is stopped") diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 1c738c1e38..3273fa05e2 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -18,6 +18,7 @@ package core import ( "bytes" + "math/rand" "runtime" "sync/atomic" @@ -29,6 +30,27 @@ import ( "golang.org/x/sync/errgroup" ) +const ( + // heavyTransactionThreshold defines the threshold for classifying a + // transaction as heavy. As defined, the transaction consumes more than + // 20% of the block's GasUsed is regarded as heavy. + // + // Heavy transactions are prioritized for prefetching to allow additional + // preparation time. + heavyTransactionThreshold = 20 + + // heavyTransactionPriority defines the probability with which the heavy + // transactions will be scheduled first for prefetching. + heavyTransactionPriority = 30 +) + +// isHeavyTransaction returns an indicator whether the transaction is regarded +// as heavy or not. +func isHeavyTransaction(txGasLimit uint64, blockGasUsed uint64) bool { + threshold := blockGasUsed * heavyTransactionThreshold / 100 + return txGasLimit >= threshold +} + // statePrefetcher is a basic Prefetcher that executes transactions from a block // on top of the parent state, aiming to prefetch potentially useful state data // from disk. Transactions are executed in parallel to fully leverage the @@ -59,8 +81,49 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c ) workers.SetLimit(max(1, 4*runtime.NumCPU()/5)) // Aggressively run the prefetching - // Iterate over and process the individual transactions - for i, tx := range block.Transactions() { + var ( + processed = make(map[common.Hash]struct{}, len(block.Transactions())) + heavyTxs = make(chan *types.Transaction, len(block.Transactions())) + normalTxs = make(chan *types.Transaction, len(block.Transactions())) + ) + for _, tx := range block.Transactions() { + // Note, the gasLimit is not equivalent with the gasUsed. Theoretically + // we should measure the transaction heaviness based on the gasUsed. + // Unfortunately this field is still unknown without execution, so use + // gasLimit instead. + if isHeavyTransaction(tx.Gas(), block.GasUsed()) { + heavyTxs <- tx + } + normalTxs <- tx + } + blockPrefetchHeavyTxsMeter.Mark(int64(len(heavyTxs))) + + fetchTx := func() (*types.Transaction, bool) { + // Pick the heavy transaction first based on the pre-defined probability + if rand.Intn(100) < heavyTransactionPriority { + select { + case tx := <-heavyTxs: + return tx, false + default: + } + } + // No more heavy transaction, or no priority for them, pick the transaction + // with normal order. + select { + case tx := <-normalTxs: + return tx, false + default: + return nil, true + } + } + for { + tx, done := fetchTx() + if done { + break + } + if _, exists := processed[tx.Hash()]; exists { + continue + } stateCpy := statedb.Copy() // closure workers.Go(func() error { // If block precaching was interrupted, abort @@ -103,7 +166,9 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c // Disable the nonce check msg.SkipNonceChecks = true - stateCpy.SetTxContext(tx.Hash(), i) + // The transaction index is assigned blindly with zero, it's fine + // for prefetching only. + stateCpy.SetTxContext(tx.Hash(), 0) // We attempt to apply a transaction. The goal is not to execute // the transaction successfully, rather to warm up touched data slots. From 575c67cfa776be4a1c4c4e4b7c14bfaddf530659 Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Thu, 18 Dec 2025 16:11:50 +0800 Subject: [PATCH 2/4] core: twist parameters --- core/state_prefetcher.go | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 3273fa05e2..2836598e1f 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -41,13 +41,13 @@ const ( // heavyTransactionPriority defines the probability with which the heavy // transactions will be scheduled first for prefetching. - heavyTransactionPriority = 30 + heavyTransactionPriority = 40 ) // isHeavyTransaction returns an indicator whether the transaction is regarded // as heavy or not. func isHeavyTransaction(txGasLimit uint64, blockGasUsed uint64) bool { - threshold := blockGasUsed * heavyTransactionThreshold / 100 + threshold := min(blockGasUsed*heavyTransactionThreshold/100, params.MaxTxGas/2) return txGasLimit >= threshold } @@ -87,13 +87,16 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c normalTxs = make(chan *types.Transaction, len(block.Transactions())) ) for _, tx := range block.Transactions() { - // Note, the gasLimit is not equivalent with the gasUsed. Theoretically - // we should measure the transaction heaviness based on the gasUsed. - // Unfortunately this field is still unknown without execution, so use - // gasLimit instead. + // Note: gasLimit is not equivalent to gasUsed. Ideally, transaction heaviness + // should be measured using gasUsed. However, gasUsed is unknown prior to + // execution, so gasLimit is used as the indicator instead. This allows transaction + // senders to inflate gasLimit to gain higher prefetch priority, but this + // trade-off is unavoidable. if isHeavyTransaction(tx.Gas(), block.GasUsed()) { heavyTxs <- tx } + // The heavy transaction will also be emitted with the normal prefetching + // ordering, depends on in which track it will be selected first. normalTxs <- tx } blockPrefetchHeavyTxsMeter.Mark(int64(len(heavyTxs))) @@ -125,6 +128,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c continue } stateCpy := statedb.Copy() // closure + workers.Go(func() error { // If block precaching was interrupted, abort if interrupt != nil && interrupt.Load() { From 24d3c430ab3f4e6bc9d86ca398473c7b931304da Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Wed, 31 Dec 2025 10:59:33 +0800 Subject: [PATCH 3/4] core: address comments --- core/state_prefetcher.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index 2836598e1f..c48f4192c1 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -120,15 +120,19 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c } } for { - tx, done := fetchTx() + next, done := fetchTx() if done { break } - if _, exists := processed[tx.Hash()]; exists { + if _, exists := processed[next.Hash()]; exists { continue } - stateCpy := statedb.Copy() // closure + processed[next.Hash()] = struct{}{} + var ( + stateCpy = statedb.Copy() // closure + tx = next // closure + ) workers.Go(func() error { // If block precaching was interrupted, abort if interrupt != nil && interrupt.Load() { From 8f171432d8a7fd278ffc090a2662a6c9caf1da5e Mon Sep 17 00:00:00 2001 From: Gary Rong Date: Sun, 4 Jan 2026 14:07:36 +0800 Subject: [PATCH 4/4] core: calculate threshold once per block --- core/state_prefetcher.go | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/core/state_prefetcher.go b/core/state_prefetcher.go index c48f4192c1..eb3f1a0ddd 100644 --- a/core/state_prefetcher.go +++ b/core/state_prefetcher.go @@ -44,13 +44,6 @@ const ( heavyTransactionPriority = 40 ) -// isHeavyTransaction returns an indicator whether the transaction is regarded -// as heavy or not. -func isHeavyTransaction(txGasLimit uint64, blockGasUsed uint64) bool { - threshold := min(blockGasUsed*heavyTransactionThreshold/100, params.MaxTxGas/2) - return txGasLimit >= threshold -} - // statePrefetcher is a basic Prefetcher that executes transactions from a block // on top of the parent state, aiming to prefetch potentially useful state data // from disk. Transactions are executed in parallel to fully leverage the @@ -85,6 +78,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c processed = make(map[common.Hash]struct{}, len(block.Transactions())) heavyTxs = make(chan *types.Transaction, len(block.Transactions())) normalTxs = make(chan *types.Transaction, len(block.Transactions())) + threshold = min(block.GasUsed()*heavyTransactionThreshold/100, params.MaxTxGas/2) ) for _, tx := range block.Transactions() { // Note: gasLimit is not equivalent to gasUsed. Ideally, transaction heaviness @@ -92,7 +86,7 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c // execution, so gasLimit is used as the indicator instead. This allows transaction // senders to inflate gasLimit to gain higher prefetch priority, but this // trade-off is unavoidable. - if isHeavyTransaction(tx.Gas(), block.GasUsed()) { + if tx.Gas() > threshold { heavyTxs <- tx } // The heavy transaction will also be emitted with the normal prefetching