Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
4ca52a8
Initial plan
Copilot Feb 6, 2026
10734c3
fix: exit with non-zero exit code when background components crash
Copilot Feb 6, 2026
dae7ff9
revert: remove unnecessary .gitignore change
Copilot Feb 6, 2026
98d2e9a
Merge pull request #2 from pinax-network/copilot/fix-exit-code-on-error
matthewdarwin Feb 12, 2026
aae28a4
Initial plan
Copilot Feb 17, 2026
31eb02f
Merge PR #120 from upstream: Add catch-up mode for sync component
Copilot Feb 17, 2026
2b14139
Remove unused tipHash field from stubExecutionClient
Copilot Feb 17, 2026
8a2c241
Merge pull request #4 from pinax-network/copilot/merge-upstream-pr-120
matthewdarwin Feb 17, 2026
546bfc5
Initial plan
Copilot Feb 17, 2026
f449f3a
Add forkchoice finality changeset optimization from upstream PR #121
Copilot Feb 17, 2026
2c0911e
Add erigon binary to .gitignore to prevent accidental commits
Copilot Feb 17, 2026
a7b47d7
Merge pull request #6 from pinax-network/copilot/merge-upstream-pr-121
matthewdarwin Feb 17, 2026
79af34b
Merge branch '0xPolygon:release/3.2' into release/3.2
matthewdarwin Feb 18, 2026
88d678f
Initial plan
Copilot Feb 27, 2026
4adba8f
Cherry-pick commit 81b0f26: remove early-exit based on DirtySpace() a…
Copilot Feb 27, 2026
226095d
Merge pull request #8 from pinax-network/copilot/cherry-pick-commit-8…
matthewdarwin Feb 27, 2026
faa8557
fix: restore pointEvaluation precompile in pre-Lisovo sets
Johnaverse Mar 18, 2026
f46737e
fix: add execution check to RPC endpoints to prevent queries on unexe…
Johnaverse Mar 18, 2026
d9987d6
perf: add idle-time aggressive pruning and tune prune parameters for …
Johnaverse Mar 18, 2026
dc52c8b
Merge branch 'release/3.2' into perf/aggressive-idle-pruning
Johnaverse Mar 18, 2026
994e834
Merge pull request #9 from Johnaverse/perf/aggressive-idle-pruning
Johnaverse Mar 18, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -112,4 +112,5 @@ mdbx.lck

.my

cover.out
cover.out
/erigon
4 changes: 4 additions & 0 deletions cmd/utils/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -1140,6 +1140,10 @@ var (
Usage: "EXPERIMENTAL: enables concurrent trie for commitment",
Value: false,
}
UseForkchoiceFinalityFlag = cli.BoolFlag{
Name: "experimental.use-forkchoice-finality",
Usage: "Skip changeset generation for blocks finalized by forkchoice (e.g., Polygon milestones). Reduces overhead but requires chaindata reset if finality is reverted.",
}
GDBMeFlag = cli.BoolFlag{
Name: "gdbme",
Usage: "restart erigon under gdb for debug purposes",
Expand Down
3 changes: 3 additions & 0 deletions core/vm/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ var PrecompiledContractsPrague = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{0x07}): &bn254ScalarMulIstanbul{},
common.BytesToAddress([]byte{0x08}): &bn254PairingIstanbul{},
common.BytesToAddress([]byte{0x09}): &blake2F{},
common.BytesToAddress([]byte{0x0a}): &pointEvaluation{},
common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{},
common.BytesToAddress([]byte{0x0c}): &bls12381G1MultiExp{},
common.BytesToAddress([]byte{0x0d}): &bls12381G2Add{},
Expand Down Expand Up @@ -235,6 +236,7 @@ var PrecompiledContractsMadhugiri = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{0x07}): &bn254ScalarMulIstanbul{},
common.BytesToAddress([]byte{0x08}): &bn254PairingIstanbul{},
common.BytesToAddress([]byte{0x09}): &blake2F{},
common.BytesToAddress([]byte{0x0a}): &pointEvaluation{},
common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{},
common.BytesToAddress([]byte{0x0c}): &bls12381G1MultiExp{},
common.BytesToAddress([]byte{0x0d}): &bls12381G2Add{},
Expand All @@ -254,6 +256,7 @@ var PrecompiledContractsMadhugiriPro = map[common.Address]PrecompiledContract{
common.BytesToAddress([]byte{0x07}): &bn254ScalarMulIstanbul{},
common.BytesToAddress([]byte{0x08}): &bn254PairingIstanbul{},
common.BytesToAddress([]byte{0x09}): &blake2F{},
common.BytesToAddress([]byte{0x0a}): &pointEvaluation{},
common.BytesToAddress([]byte{0x0b}): &bls12381G1Add{},
common.BytesToAddress([]byte{0x0c}): &bls12381G1MultiExp{},
common.BytesToAddress([]byte{0x0d}): &bls12381G2Add{},
Expand Down
86 changes: 62 additions & 24 deletions core/vm/contracts_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -527,35 +527,73 @@ func TestLisovoCLZOpcode(t *testing.T) {
}
}

// TestPointEvaluationPrecompileRemoval verifies that the pointEvaluation (KZG) precompile
// is present before LisovoPro and removed starting with LisovoPro.
func TestPointEvaluationPrecompileRemoval(t *testing.T) {
// TestPointEvaluationPrecompileAvailability verifies that the pointEvaluation (KZG)
// precompile remains available through Lisovo and is only removed starting with LisovoPro.
func TestPointEvaluationPrecompileAvailability(t *testing.T) {
t.Parallel()

pointEvaluationAddr := common.BytesToAddress([]byte{0x0a})

// Test Lisovo: should have pointEvaluation
lisovoRules := &chain.Rules{
IsLisovo: true,
IsMadhugiriPro: true,
IsMadhugiri: true,
IsBhilai: true,
}
lisovoPrecompiles := Precompiles(lisovoRules)
if _, exists := lisovoPrecompiles[pointEvaluationAddr]; !exists {
t.Error("pointEvaluation (0x0a) should exist in Lisovo precompiles")
testCases := []struct {
name string
rules *chain.Rules
wantExist bool
}{
{
name: "Prague",
rules: &chain.Rules{
IsPrague: true,
},
wantExist: true,
},
{
name: "Madhugiri",
rules: &chain.Rules{
IsMadhugiri: true,
IsBhilai: true,
},
wantExist: true,
},
{
name: "MadhugiriPro",
rules: &chain.Rules{
IsMadhugiriPro: true,
IsMadhugiri: true,
IsBhilai: true,
},
wantExist: true,
},
{
name: "Lisovo",
rules: &chain.Rules{
IsLisovo: true,
IsMadhugiriPro: true,
IsMadhugiri: true,
IsBhilai: true,
},
wantExist: true,
},
{
name: "LisovoPro",
rules: &chain.Rules{
IsLisovoPro: true,
IsLisovo: true,
IsMadhugiriPro: true,
IsMadhugiri: true,
IsBhilai: true,
},
wantExist: false,
},
}

// Test LisovoPro: should not have pointEvaluation
lisovoProRules := &chain.Rules{
IsLisovoPro: true,
IsLisovo: true,
IsMadhugiriPro: true,
IsMadhugiri: true,
IsBhilai: true,
}
lisovoProPrecompiles := Precompiles(lisovoProRules)
if _, exists := lisovoProPrecompiles[pointEvaluationAddr]; exists {
t.Error("pointEvaluation (0x0a) should not exist in LisovoPro precompiles")
for _, testCase := range testCases {
testCase := testCase
t.Run(testCase.name, func(t *testing.T) {
precompiles := Precompiles(testCase.rules)
_, exists := precompiles[pointEvaluationAddr]
if exists != testCase.wantExist {
t.Fatalf("pointEvaluation (0x0a) existence = %t, want %t", exists, testCase.wantExist)
}
})
}
}
2 changes: 1 addition & 1 deletion db/rawdb/rawtemporaldb/accessors_commitment.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func CanUnwindToBlockNum(tx kv.TemporalTx) (uint64, error) {
return 0, err
}
if minUnwindale == math.MaxUint64 { // no unwindable block found
log.Warn("no unwindable block found from changesets, falling back to latest with commitment")
log.Debug("no unwindable block found from changesets, falling back to latest with commitment")
return commitmentdb.LatestBlockNumWithCommitment(tx)
}
if minUnwindale > 0 {
Expand Down
9 changes: 0 additions & 9 deletions db/state/aggregator.go
Original file line number Diff line number Diff line change
Expand Up @@ -1019,15 +1019,6 @@ func (at *AggregatorRoTx) PruneSmallBatches(ctx context.Context, timeout time.Du
fullStat := newAggregatorPruneStat()

for {
if sptx, ok := tx.(kv.HasSpaceDirty); ok && !furiousPrune && !aggressivePrune {
spaceDirty, _, err := sptx.SpaceDirty()
if err != nil {
return false, err
}
if spaceDirty > uint64(statecfg.MaxNonFuriousDirtySpacePerTx) {
return false, nil
}
}
iterationStarted := time.Now()
// `context.Background()` is important here!
// it allows keep DB consistent - prune all keys-related data or noting
Expand Down
2 changes: 1 addition & 1 deletion db/state/statecfg/state_schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ func Configure(Schema SchemaGen, a AggSetters, dirs datadir.Dirs, salt *uint32,
}

const AggregatorSqueezeCommitmentValues = true
const MaxNonFuriousDirtySpacePerTx = 64 * datasize.MB
const MaxNonFuriousDirtySpacePerTx = 128 * datasize.MB

var dbgCommBtIndex = dbg.EnvBool("AGG_COMMITMENT_BT", false)

Expand Down
17 changes: 17 additions & 0 deletions eth/backend.go
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,14 @@ type Ethereum struct {
heimdallService *heimdall.Service
stopNode func() error
bgComponentsEg errgroup.Group
bgComponentMu sync.Mutex
bgComponentErr error
}

func (s *Ethereum) BgComponentError() error {
s.bgComponentMu.Lock()
defer s.bgComponentMu.Unlock()
return s.bgComponentErr
}

func splitAddrIntoHostAndPort(addr string) (host string, port int, err error) {
Expand Down Expand Up @@ -1824,11 +1832,20 @@ func (s *Ethereum) Stop() error {

if err := s.bgComponentsEg.Wait(); err != nil && !errors.Is(err, context.Canceled) {
s.logger.Error("background component error", "err", err)
s.bgComponentMu.Lock()
s.bgComponentErr = err
s.bgComponentMu.Unlock()
}

return nil
}

// BgComponentError returns the first non-context-canceled error from background
// components, if any. It is safe to call after Stop() has returned.
func (s *Ethereum) BgComponentError() error {
return s.bgComponentErr
}

func (s *Ethereum) ChainDB() kv.RwDB {
return s.chainDB
}
Expand Down
1 change: 1 addition & 0 deletions eth/ethconfig/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,7 @@ type Sync struct {

ChaosMonkey bool
AlwaysGenerateChangesets bool
UseForkchoiceFinality bool
MaxReorgDepth uint64
KeepExecutionProofs bool
PersistReceiptsCacheV2 bool
Expand Down
6 changes: 6 additions & 0 deletions execution/eth1/forkchoice.go
Original file line number Diff line number Diff line change
Expand Up @@ -486,6 +486,12 @@ func (e *EthereumExecutionModule) updateForkChoice(ctx context.Context, original
}
}

// Write finalized hash BEFORE execution so shouldGenerateChangeSets() can read it
// during execution stage. This enables the UseForkchoiceFinality optimization.
if finalizedHash != (common.Hash{}) {
rawdb.WriteForkchoiceFinalized(tx, finalizedHash)
}

firstCycle := false
loopIter := 0
for {
Expand Down
48 changes: 42 additions & 6 deletions execution/stagedsync/exec3.go
Original file line number Diff line number Diff line change
Expand Up @@ -467,9 +467,14 @@ func ExecV3(ctx context.Context,
lastFrozenTxNum = uint64((lastFrozenStep+1)*kv.Step(doms.StepSize())) - 1
}

var finalizedBlockNum uint64
if cfg.syncCfg.UseForkchoiceFinality {
finalizedBlockNum = getFinalizedBlockNum(applyTx)
}

Loop:
for ; blockNum <= maxBlockNum; blockNum++ {
shouldGenerateChangesets := shouldGenerateChangeSets(cfg, blockNum, maxBlockNum, initialCycle)
shouldGenerateChangesets := shouldGenerateChangeSets(cfg, finalizedBlockNum, blockNum, maxBlockNum, initialCycle)
changeSet := &changeset2.StateChangeSet{}
if shouldGenerateChangesets && blockNum > 0 {
executor.domains().SetChangesetAccumulator(changeSet)
Expand Down Expand Up @@ -762,9 +767,15 @@ Loop:

timeStart := time.Now()

// allow greedy prune on non-chain-tip
// allow greedy prune on non-chain-tip or when not generating changesets
pruneTimeout := 250 * time.Millisecond
if initialCycle {
if initialCycle || !shouldGenerateChangesets {
// When not generating changesets (finalized blocks), we can afford longer pruning
// since we're generating less data overall
if !initialCycle {
logger.Debug(fmt.Sprintf("[%s] aggressive pruning via forkchoice finality", execStage.LogPrefix()),
"block", blockNum, "finalizedBlock", finalizedBlockNum)
}
pruneTimeout = 10 * time.Hour

if err = executor.tx().(kv.TemporalRwTx).GreedyPruneHistory(ctx, kv.CommitmentDomain); err != nil {
Expand All @@ -787,7 +798,9 @@ Loop:
errExhausted = &ErrLoopExhausted{From: startBlockNum, To: blockNum, Reason: "block batch is full"}
break Loop
}
if !initialCycle && canPrune {
// Skip pruning break if not generating changesets (finalized blocks via UseForkchoiceFinality)
// This allows larger batches similar to initialCycle behavior
if !initialCycle && canPrune && shouldGenerateChangesets {
errExhausted = &ErrLoopExhausted{From: startBlockNum, To: blockNum, Reason: "block batch can be pruned"}
break Loop
}
Expand Down Expand Up @@ -1021,7 +1034,21 @@ func blockWithSenders(ctx context.Context, db kv.RoDB, tx kv.Tx, blockReader ser
return b, err
}

func shouldGenerateChangeSets(cfg ExecuteBlockCfg, blockNum, maxBlockNum uint64, initialCycle bool) bool {
// getFinalizedBlockNum returns the finalized block number from forkchoice state.
// Returns 0 if no finalized block is set or if the hash cannot be resolved to a block number.
func getFinalizedBlockNum(tx kv.Getter) uint64 {
finalizedHash := rawdb.ReadForkchoiceFinalized(tx)
if finalizedHash == (common.Hash{}) {
return 0
}
finalizedNum := rawdb.ReadHeaderNumber(tx, finalizedHash)
if finalizedNum == nil {
return 0
}
return *finalizedNum
}

func shouldGenerateChangeSets(cfg ExecuteBlockCfg, finalizedBlockNum, blockNum, maxBlockNum uint64, initialCycle bool) bool {
if cfg.syncCfg.AlwaysGenerateChangesets {
return true
}
Expand All @@ -1031,6 +1058,15 @@ func shouldGenerateChangeSets(cfg ExecuteBlockCfg, blockNum, maxBlockNum uint64,
if initialCycle {
return false
}
// once past the initial cycle, make sure to generate changesets for the last blocks that fall in the reorg window

// Use forkchoice finalized block if enabled and available (e.g., Polygon milestones).
// Blocks at or before the finalized block don't need changesets since they cannot be reorged.
// WARNING: If finality is later reverted (e.g., faulty milestone purged by hard fork),
// the node will require a chaindata reset to recover.
if cfg.syncCfg.UseForkchoiceFinality && finalizedBlockNum > 0 && blockNum <= finalizedBlockNum {
return false
}

// Fallback: generate changesets for blocks in the reorg window (last MaxReorgDepth blocks)
return blockNum+cfg.syncCfg.MaxReorgDepth >= maxBlockNum
}
Loading