This commit is contained in:
Sina M 2026-05-19 12:09:19 +09:00 committed by GitHub
commit 47c57749a2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 93 additions and 9 deletions

View file

@ -326,6 +326,7 @@ type BlockChain struct {
flushInterval atomic.Int64 // Time interval (processing time) after which to flush a state
triedb *triedb.Database // The database handler for maintaining trie nodes.
codedb *state.CodeDB // The database handler for maintaining contract codes.
jumpDestCache vm.JumpDestCache // Shared JUMPDEST analysis cache for block processing
txIndexer *txIndexer // Transaction indexer, might be nil if not enabled
hc *HeaderChain
@ -408,6 +409,7 @@ func NewBlockChain(db ethdb.Database, genesis *Genesis, engine consensus.Engine,
db: db,
triedb: triedb,
codedb: state.NewCodeDB(db),
jumpDestCache: NewJumpDestCache(),
triegc: prque.New[int64, common.Hash](nil),
chainmu: syncx.NewClosableMutex(),
bodyCache: lru.NewCache[common.Hash, *types.Body](bodyCacheLimit),
@ -2179,7 +2181,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
// Disable tracing for prefetcher executions.
vmCfg := bc.cfg.VmConfig
vmCfg.Tracer = nil
bc.prefetcher.Prefetch(block, throwaway, vmCfg, &interrupt)
bc.prefetcher.Prefetch(block, throwaway, bc.jumpDestCache, vmCfg, &interrupt)
blockPrefetchExecuteTimer.Update(time.Since(start))
if interrupt.Load() {
@ -2225,7 +2227,7 @@ func (bc *BlockChain) ProcessBlock(ctx context.Context, parentRoot common.Hash,
// Process block using the parent state as reference point
pstart := time.Now()
pctx, _, spanEnd := telemetry.StartSpan(ctx, "bc.processor.Process")
res, err := bc.processor.Process(pctx, block, statedb, bc.cfg.VmConfig)
res, err := bc.processor.Process(pctx, block, statedb, bc.jumpDestCache, bc.cfg.VmConfig)
spanEnd(&err)
if err != nil {
bc.reportBadBlock(block, res, err)

View file

@ -160,7 +160,7 @@ func testBlockChainImport(chain types.Blocks, blockchain *BlockChain) error {
if err != nil {
return err
}
res, err := blockchain.processor.Process(context.Background(), block, statedb, vm.Config{})
res, err := blockchain.processor.Process(context.Background(), block, statedb, nil, vm.Config{})
if err != nil {
blockchain.reportBadBlock(block, res, err)
return err

76
core/jumpdest.go Normal file
View file

@ -0,0 +1,76 @@
// 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 core
import (
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/lru"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/metrics"
)
var (
jumpDestHitMeter = metrics.NewRegisteredMeter("chain/cache/jumpdest/hit", nil)
jumpDestMissMeter = metrics.NewRegisteredMeter("chain/cache/jumpdest/miss", nil)
)
const (
// jumpDestBuckets is the number of independent LRU shards. Code hashes
// are dispatched by the low bits of the first byte to spread load across
// shards and reduce mutex contention from the parallel prefetcher.
jumpDestBuckets = 8
// jumpDestBucketSize is the per-shard byte budget.
jumpDestBucketSize = 8 * 1024 * 1024
)
// shardedJumpDestCache is a thread-safe, byte-bounded LRU of JUMPDEST analysis
// bitmaps, sharded into independent buckets to reduce lock contention. It is
// owned by BlockChain and shared across block processing and prefetching,
// keyed by the immutable contract code hash.
type shardedJumpDestCache struct {
buckets [jumpDestBuckets]struct {
dest *lru.SizeConstrainedCache[common.Hash, vm.BitVec]
}
}
// NewJumpDestCache constructs the analysis cache.
func NewJumpDestCache() vm.JumpDestCache {
c := new(shardedJumpDestCache)
for i := range c.buckets {
c.buckets[i].dest = lru.NewSizeConstrainedCache[common.Hash, vm.BitVec](jumpDestBucketSize)
}
return c
}
// Load retrieves the cached jumpdest analysis for the given code hash.
func (c *shardedJumpDestCache) Load(hash common.Hash) (vm.BitVec, bool) {
bucket := &c.buckets[hash[0]&(jumpDestBuckets-1)]
v, ok := bucket.dest.Get(hash)
if ok {
jumpDestHitMeter.Mark(1)
} else {
jumpDestMissMeter.Mark(1)
}
return v, ok
}
// Store saves the jumpdest analysis for the given code hash.
func (c *shardedJumpDestCache) Store(hash common.Hash, b vm.BitVec) {
bucket := &c.buckets[hash[0]&(jumpDestBuckets-1)]
bucket.dest.Add(hash, b)
}

View file

@ -49,7 +49,7 @@ func newStatePrefetcher(config *params.ChainConfig, chain *HeaderChain) *statePr
// Prefetch processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb, but any changes are discarded. The
// only goal is to warm the state caches.
func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool) {
func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, jumpDestCache vm.JumpDestCache, cfg vm.Config, interrupt *atomic.Bool) {
var (
fails atomic.Int64
header = block.Header()
@ -94,6 +94,9 @@ func (p *statePrefetcher) Prefetch(block *types.Block, statedb *state.StateDB, c
// Execute the message to preload the implicit touched states
evm := vm.NewEVM(NewEVMBlockContext(header, p.chain, nil), stateCpy, p.config, cfg)
defer evm.Release()
if jumpDestCache != nil {
evm.SetJumpDestCache(jumpDestCache)
}
// Convert the transaction into an executable message and pre-cache its sender
msg, err := TransactionToMessage(tx, signer, header.BaseFee)

View file

@ -62,7 +62,7 @@ func (p *StateProcessor) chainConfig() *params.ChainConfig {
// Process returns the receipts and logs accumulated during the process and
// returns the amount of gas that was used in the process. If any of the
// transactions failed to execute due to insufficient gas it will return an error.
func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error) {
func (p *StateProcessor) Process(ctx context.Context, block *types.Block, statedb *state.StateDB, jumpDestCache vm.JumpDestCache, cfg vm.Config) (*ProcessResult, error) {
var (
config = p.chainConfig()
receipts = make(types.Receipts, 0, len(block.Transactions()))
@ -86,6 +86,9 @@ func (p *StateProcessor) Process(ctx context.Context, block *types.Block, stated
evm = vm.NewEVM(context, tracingStateDB, config, cfg)
)
defer evm.Release()
if jumpDestCache != nil {
evm.SetJumpDestCache(jumpDestCache)
}
// Run the pre-execution system calls
PreExecution(ctx, block.BeaconRoot(), block.ParentHash(), config, evm, block.Number(), block.Time())
// Iterate over and process the individual transactions

View file

@ -68,7 +68,7 @@ func ExecuteStateless(ctx context.Context, config *params.ChainConfig, vmconfig
validator := NewBlockValidator(config, nil) // No chain, we only validate the state, not the block
// Run the stateless blocks processing and self-validate certain fields
res, err := processor.Process(ctx, block, db, vmconfig)
res, err := processor.Process(ctx, block, db, nil, vmconfig)
if err != nil {
return common.Hash{}, common.Hash{}, err
}

View file

@ -41,7 +41,7 @@ type Prefetcher interface {
// Prefetch processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb, but any changes are discarded. The
// only goal is to pre-cache transaction signatures and state trie nodes.
Prefetch(block *types.Block, statedb *state.StateDB, cfg vm.Config, interrupt *atomic.Bool)
Prefetch(block *types.Block, statedb *state.StateDB, jumpDestCache vm.JumpDestCache, cfg vm.Config, interrupt *atomic.Bool)
}
// Processor is an interface for processing blocks using a given initial state.
@ -49,7 +49,7 @@ type Processor interface {
// Process processes the state changes according to the Ethereum rules by running
// the transaction messages using the statedb and applying any rewards to both
// the processor (coinbase) and any included uncles.
Process(ctx context.Context, block *types.Block, statedb *state.StateDB, cfg vm.Config) (*ProcessResult, error)
Process(ctx context.Context, block *types.Block, statedb *state.StateDB, jumpDestCache vm.JumpDestCache, cfg vm.Config) (*ProcessResult, error)
}
// ProcessResult contains the values computed by Process.

View file

@ -151,7 +151,7 @@ func (eth *Ethereum) hashState(ctx context.Context, block *types.Block, base *st
if current = eth.blockchain.GetBlockByNumber(next); current == nil {
return nil, nil, fmt.Errorf("block #%d not found", next)
}
_, err := eth.blockchain.Processor().Process(ctx, current, statedb, vm.Config{})
_, err := eth.blockchain.Processor().Process(ctx, current, statedb, nil, vm.Config{})
if err != nil {
return nil, nil, fmt.Errorf("processing block %d failed: %v", current.NumberU64(), err)
}