Skip to content

core, miner, consensus/bor, eth, triedb/pathdb: pipelined state root computation for block import#2180

Open
pratikspatil024 wants to merge 70 commits into
developfrom
pipelined-src
Open

core, miner, consensus/bor, eth, triedb/pathdb: pipelined state root computation for block import#2180
pratikspatil024 wants to merge 70 commits into
developfrom
pipelined-src

Conversation

@pratikspatil024

@pratikspatil024 pratikspatil024 commented Apr 1, 2026

Copy link
Copy Markdown
Member

Description

Overlaps state-root computation (SRC) of block N with transaction execution of block N+1 during block import. Execution of N+1 opens state at the last committed trie root plus an in-memory FlatDiff overlay of block N's mutations, while a background SRC goroutine commits block N and computes its root. Disabled by default — enabled with [pipeline] enable-import-src = true.

Built on top of the delayed SRC PoC: instead of just deferring SRC, it pipelines SRC with the next block's work.

Measured impact

Mainnet full node (n2d-standard-16, path scheme, BlockSTM enforce), ~64k-block catch-up windows, block-weight and traffic-character controlled across adjacent weekday ranges:

configuration agg mgasps vs baseline
pipeline off 291.0
pipeline on 346.4 +19.0%
pipeline off + producewitnesses 255.3
pipeline on + producewitnesses 293.7 +15.0%

Per-block overhead beyond raw execution dropped from ~45–50ms to ~18ms. Zero root_mismatch, pipeline fallbacks, or execution errors across the benchmark campaign (~500k blocks imported under the pipeline).

How it works — import

When importing block N (pipeline active):

  1. Execute block N at the last committed root + FlatDiff overlay from block N−1
  2. Run ValidateStateCheap (gas, bloom, receipt root — no IntermediateRoot)
  3. Extract block N's FlatDiff via CommitSnapshot (~1ms, no trie hashing)
  4. Collect the previous SRC(N−1): verify its root, write + announce the witness, trie GC
  5. Write block metadata and advance the head immediately (sync protocol sees it)
  6. Store the FlatDiff so StateAt/RPC reads (eth_call, eth_estimateGas, pending reads) are correct during the pipeline window
  7. Spawn SRC(N) in the background — it overlaps with block N+1's execution
  8. Continue to the next block without waiting for SRC(N)

Two supporting optimizations ship with the pipeline:

  • pathdb trie-node index (triedb/pathdb/lookup_nodes.go): a reference-counted (owner, path, hash) → blob map over live diff layers. reader.Node resolves in one probe instead of walking up to 128 diff layers per read (the walk measured ~44% of process CPU during catch-up). The hash is part of the key, so a hit is correct by construction; a definitive miss reads the disk layer directly. This index is always on (not gated by the pipeline flag) — it is a read-path cache whose misses fall through to the existing walk, adding ~250MB steady-state at 128 diff layers.
  • Warm-node handoff (core/state/warm_snapshot.go, pipeline.warm-snapshot, default on): when witnesses are produced, the execution-side trie prefetcher is detached and handed to SRC, which builds an immutable hash-verified snapshot of the loaded trie nodes and consults it before pathdb. No effect when witnesses are off.

Review-sensitive properties

  • The head advances before the state root is verified. ValidateStateCheap gates insertion; the full root check happens asynchronously in the SRC goroutine. A mismatch fires chain/imports/pipelined/root_mismatch (a hard alarm that must stay 0) and errors the pipeline. Sync compensates: hasPendingPipelinedHeadState keeps the node in full-sync while a head-state commit is in flight.
  • WIT protocol behavior: WitnessReadyEvent now push-announces witness availability to stateless peers (replacing a 10s poll); GetWitness serving waits for in-flight SRC (bounded, with a header-existence DoS guard) and reads uncached so peer traffic can't evict import-critical cache entries.
  • Witness completeness: the SRC uses a trie-only reader (NewTrieOnly) so every read walks the MPT and proof paths land in the witness; parity tests assert proof-node-set equality and stateless replay (ProcessBlockWithWitnesses) across all configurations. PropagateReadsTo in checkAndCommitSpan captures the validator-contract proof nodes read via a copied statedb; EIP-2935 accounts are touched into the read set.
  • Lifecycle: one SRC goroutine + one auto-collection goroutine per pipelined block, registered on bc.wg; pending SRC is flushed on reorg, gap, or error.
  • No database schema change (BlockChainVersion unchanged); nothing forces a resync.

Production-side (miner) pipelining: landed but disabled

miner/pipeline.go contains the speculative-sealing counterpart (FlatDiff extraction after FinalizeForPipeline, background SRC, speculative N+1 build, async chain write). It is hard-disabled: isPipelineEligible returns false unconditionally and no config exposes it (the worker/pipeline/enabled gauge is always 0). Pre-Rio seal-recovery interaction makes speculative Prepare fail; re-enablement is future work and the gating logic is preserved in comments. Reviewers can treat the miner path as dormant code with test coverage.

Config

[pipeline]
enable-import-src = false   # master gate (default: disabled)
import-src-logs   = false   # verbose pipeline logging
warm-snapshot     = true    # witness-producing nodes only; leave on

Notable negative results baked into the design (so nobody re-litigates them): the exec-side trie prefetcher and the speculative block prefetcher are load-bearing via shared-cache warming — disabling either regresses 25–75% — so neither is configurable (pipeline.exec-prefetch existed briefly on the feature branch and was removed).

Executed tests

  • Parity suites (hash + path schemes, -race): pipelined vs sequential roots/hashes, witness proof-node-set parity + stateless replay, FlatDiff mutation/overlay suites, self-destruct integration, pathdb node-index unit tests (fork refcounting, deletion markers, definitive-miss semantics), tests/bor integration.
  • Mainnet catch-up benchmark campaign (2026-07-10 → 2026-07-21): 12 instrumented legs covering the pipeline × witness matrix with per-leg lane analysis (exec / SRC / commit / overlap) and CPU+mutex profiling.
  • RPC correctness under pipelined import: a deterministic window test (core/pipelined_window_test.go, SRC goroutine held open via test hook) pins the overlay/trie semantics of the "head advanced, root not yet committed" interval; an end-to-end battery (tests/bor/pipelined_rpc_test.go) drives 12 read methods against a live-syncing importer at pinned heights and latest, checks sync-time vs settled response identity, and verifies full per-height parity vs a non-pipelined BP including cryptographic account-proof verification. Handlers that need committed trie nodes (eth_getProof, debug_storageRangeAt) gate on WaitForPipelinedStateCommit instead of erroring transiently inside the window.
  • Diffguard mutation testing (50% sampling): 89.9% overall kill rate, 93.6% on tier-1 logic.
  • Pending before ready-for-review: 24h tip-following soak, kurtosis devnet e2e with reorgs, witness-sync devnet check.

Quality gates — declared deviations

Three CI gates fail for structural reasons; each is intentional and listed here rather than suppressed:

  • Cognitive complexity / function size (diffguard, Quality metrics job): insertChainWithWitnesses, IntermediateRoot, getStateObject, and updateTrie exceed thresholds. All four are legacy hot-path giants that this PR extends in place; refactoring them is high-risk consensus-path churn that belongs in a dedicated cleanup, not a feature PR. New-code violations were extracted below thresholds (applyFlatMutationFast, runSRCCompute, FinalizeForPipeline, witness-collection loops deduped into addObjectWitness). nodeWalk is a rename of pre-existing upstream logic. The reported core -> core dependency cycle is a tool artifact (self-cycle).
  • SonarCloud duplication (8.2% vs ≤3%): 941 of 1039 duplicated lines are test scaffolding (pipelined test variants deliberately share setup with their non-pipelined siblings so the comparison stays literal). Remaining production duplication was removed in this PR (addObjectWitness, commitSprintWork reuse); ValidateStateCheap intentionally mirrors ValidateState's shape.
  • codecov/patch (46% vs 90% target): project coverage is up (+0.02%); the patch metric only counts unit-test instrumentation and misses the integration/e2e suites where most pipeline coverage lives (tests/bor, kurtosis). The 90% patch bar on a 12.7k-line PR spanning hot paths is not attainable without duplicating e2e coverage as unit tests.

Rollout notes

Backwards-compatible; off by default; no coordinated upgrade required. Not consensus-affecting: the pipeline computes the identical root on the identical commit path, one block later in wall-clock, and aborts on mismatch. Operator-facing changes when enabled: witness-ready latency trails the block by one SRC cycle (~210ms end-to-end vs ~143ms inline). Related: #2312 implements an alternative trie-node index on develop; this PR lands first and #2312 rebases.

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.

Tip: disable this comment in your organization's Code Review settings.

@pratikspatil024
pratikspatil024 requested review from a team and cffls April 1, 2026 15:57
@claude

claude Bot commented Apr 1, 2026

Copy link
Copy Markdown

Code Review

Found 6 issues: 4 bugs and 2 security concerns.

Bugs

  1. miner/worker.go:1117writeElapsed always measures ~0 (broken metric)
    writeElapsed is computed immediately after writeStart, before either write call executes. The original code had the write call between writeStart and writeElapsed. The writeBlockAndSetHeadTimer metric will always report approximately zero. Fix: move writeElapsed := time.Since(writeStart) to after the if/else block.

    bor/miner/worker.go

    Lines 1116 to 1123 in 07345ad

    writeStart := time.Now()
    writeElapsed := time.Since(writeStart)
    if task.pipelined {
    _, err = w.chain.WriteBlockAndSetHeadPipelined(block, receipts, logs, task.state, true, task.witnessBytes)
    } else {
    _, err = w.chain.WriteBlockAndSetHead(block, receipts, logs, task.state, true)
    }
    writeBlockAndSetHeadTimer.Update(writeElapsed)

  2. miner/pipeline.go:380-383 — nil pointer dereference when chainHead == nil
    When chainHead is nil, the || short-circuits to true and enters the if-body, where chainHead.Number.Uint64() panics. This is in the block production path. Per security-common.md: No panics in consensus, sync, or block production paths. Fix: split the nil check from the number check into separate if-blocks.

    bor/miner/pipeline.go

    Lines 379 to 384 in 07345ad

    chainHead := w.chain.CurrentBlock()
    if chainHead == nil || chainHead.Number.Uint64() != blockNNum {
    log.Error("Pipelined SRC: chain head mismatch after waiting", "expected", blockNNum,
    "got", chainHead.Number.Uint64())
    return
    }

  3. core/stateless/witness.go:101NewWitness no longer copies the context header (mutation risk)
    The old code did ctx := types.CopyHeader(context) and zeroed Root/ReceiptHash. The new code stores the caller pointer directly. In miner/worker.go:1196, the raw header pointer is passed — this header is later mutated in place. The Witness will silently see those mutations. See state-security.md threat model.

    func NewWitness(context *types.Header, chain HeaderReader) (*Witness, error) {
    // When building witnesses, retrieve the parent header, which will *always*
    // be included to act as a trustless pre-root hash container
    var headers []*types.Header
    if chain != nil {
    parent := chain.GetHeader(context.ParentHash, context.Number.Uint64()-1)
    if parent == nil {
    return nil, errors.New("failed to retrieve parent header")
    }
    headers = append(headers, parent)
    }
    // Create the witness with a reconstructed gutted out block
    return &Witness{
    context: context,
    Headers: headers,
    Codes: make(map[string]struct{}),
    State: make(map[string]struct{}),
    chain: chain,
    }, nil
    }

  4. miner/pipeline.go:124SetLastFlatDiff stores a provisional header hash that never matches
    env.header.Hash() lacks both Root and the seal signature. In PostExecutionStateAt, the comparison uses the sealed header — so FlatDiff overlay path is never taken. The txpool falls back to StateAt(header.Root) which may fail if SRC hasn't committed. Same issue at lines 521 and 783.

    bor/miner/pipeline.go

    Lines 123 to 125 in 07345ad

    w.chain.SetLastFlatDiff(flatDiff, env.header.Hash())
    // Note: this counts block N as "entering the pipeline." If Prepare() fails

Security Concerns

  1. core/stateless/witness.go:56 — pre-state root validation anchored to untrusted witness data
    The old ValidateWitnessPreState took a caller-supplied expectedPreStateRoot. The new version fetches the parent using witness.context.ParentHash (from the witness itself). For peer-received witnesses, no call site verifies witness.context.ParentHash == block.ParentHash(). A malicious peer could bypass the pre-state root check. Per state-security.md and security-common.md peer-triggerable escalation.

    // Get the witness context header (the block this witness is for).
    contextHeader := witness.Header()
    if contextHeader == nil {
    return fmt.Errorf("witness context header is nil")
    }
    // Get the parent block header from the chain.
    parentHeader := headerReader.GetHeader(contextHeader.ParentHash, contextHeader.Number.Uint64()-1)
    if parentHeader == nil {
    return fmt.Errorf("parent block header not found: parentHash=%x, parentNumber=%d",
    contextHeader.ParentHash, contextHeader.Number.Uint64()-1)
    }
    // Get witness pre-state root (from first header which should be parent).
    witnessPreStateRoot := witness.Root()
    // Compare with actual parent block's state root.
    if witnessPreStateRoot != parentHeader.Root {
    return fmt.Errorf("witness pre-state root mismatch: witness=%x, parent=%x, blockNumber=%d",
    witnessPreStateRoot, parentHeader.Root, contextHeader.Number.Uint64())
    }
    return nil

  2. core/blockchain.go:4402SpawnSRCGoroutine uses raw go func() without panic recovery
    The old code used bc.wg.Go(func() { ... }) for lifecycle-safe goroutine management. The new code uses bc.wg.Add(1) + raw go func(). If the goroutine panics, the process crashes without graceful shutdown. Per security-common.md: No panics in block production paths.

    bor/core/blockchain.go

    Lines 4399 to 4410 in 07345ad

    pending.wg.Add(1)
    bc.wg.Add(1)
    go func() {
    defer bc.wg.Done()
    defer pending.wg.Done()
    tmpDB, err := state.New(parentRoot, bc.statedb)
    if err != nil {
    log.Error("Pipelined SRC: failed to open tmpDB", "parentRoot", parentRoot, "err", err)
    pending.err = err
    return

@claude

claude Bot commented Apr 1, 2026

Copy link
Copy Markdown

Code Review

Found 5 issues in miner/worker.go and miner/pipeline.go. Checked for bugs and CLAUDE.md compliance.


1. Bug: writeElapsed always ~0ns (miner/worker.go L1116-L1123)

writeElapsed := time.Since(writeStart) is computed immediately after writeStart := time.Now(), before either WriteBlockAndSetHeadPipelined or WriteBlockAndSetHead executes. writeBlockAndSetHeadTimer always records ~0, and workerMgaspsTimer (line 1148) reports inflated MGas/s. Fix: move writeElapsed := time.Since(writeStart) to after the if/else block.


2. Bug: nil pointer dereference (miner/pipeline.go L379-L384)

When chainHead is nil, the || short-circuits into the if-body, but chainHead.Number.Uint64() in log.Error dereferences nil and panics. Per CLAUDE.md: No panics in consensus, sync, or block production paths. Fix: split into two if-checks.


3. Bug: unchecked type assertion (miner/pipeline.go L335-L341)

borEngine, _ := w.engine.(*bor.Bor) discards the ok boolean. If w.engine is not *bor.Bor, borEngine is nil and borEngine.AssembleBlock(...) panics. The same assertion at line 96 correctly checks ok. Fix: check ok and return early.


4. Bug: goroutine leak on 5 return paths (miner/pipeline.go L293-L345)

initialFillDone channel (line 293) goroutine is not drained on return paths at lines 345, 357, 371, 373, 383. Only WaitForSRC error (line 331) and happy path (line 390) drain it. Fix: defer drain after line 293.


5. Bug: trie DB race after SpawnSRCGoroutine (miner/pipeline.go L206-L229)

SpawnSRCGoroutine called at line 213 launches a goroutine doing CommitWithUpdate. If StateAtWithFlatDiff fails (line 219) or GetHeader returns nil (line 228), fallbackToSequential does IntermediateRoot inline on the same parent root concurrently. The comments at lines 206-211 identify this as causing missing trie node / layer stale errors but only guard the Prepare() case. Fix: WaitForSRC() before fallbackToSequential, or move spawn after preconditions.

@codecov

codecov Bot commented Apr 2, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 45.88369% with 1433 lines in your changes missing coverage. Please review.
✅ Project coverage is 54.09%. Comparing base (55cac19) to head (ccedec8).

Files with missing lines Patch % Lines
miner/pipeline.go 5.42% 749 Missing ⚠️
miner/worker.go 61.80% 89 Missing and 21 partials ⚠️
core/state/warm_snapshot.go 44.85% 63 Missing and 12 partials ⚠️
core/state/statedb.go 82.60% 63 Missing and 9 partials ⚠️
core/blockchain_reader.go 50.35% 64 Missing and 5 partials ⚠️
core/state/trie_prefetcher.go 22.22% 51 Missing and 5 partials ⚠️
consensus/bor/bor.go 43.75% 54 Missing ⚠️
triedb/pathdb/reader.go 32.25% 41 Missing and 1 partial ⚠️
tests/bor/helper.go 63.15% 21 Missing and 7 partials ⚠️
core/block_validator.go 20.00% 14 Missing and 6 partials ⚠️
... and 17 more

❌ Your patch check has failed because the patch coverage (45.88%) is below the target coverage (90.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@             Coverage Diff             @@
##           develop    #2180      +/-   ##
===========================================
+ Coverage    54.07%   54.09%   +0.01%     
===========================================
  Files          907      911       +4     
  Lines       162012   165335    +3323     
===========================================
+ Hits         87615    89442    +1827     
- Misses       68984    70380    +1396     
- Partials      5413     5513     +100     
Files with missing lines Coverage Δ
core/blockchain.go 64.87% <ø> (+1.79%) ⬆️
core/blockchain_insert.go 78.50% <100.00%> (+1.27%) ⬆️
core/evm.go 93.67% <100.00%> (+19.65%) ⬆️
core/stateless/encoding.go 63.49% <ø> (ø)
core/stateless/witness.go 44.64% <100.00%> (+6.02%) ⬆️
core/txpool/blobpool/blobpool.go 54.87% <100.00%> (ø)
core/types/block.go 43.19% <ø> (ø)
eth/ethconfig/config.go 78.94% <ø> (ø)
eth/peer.go 95.80% <100.00%> (ø)
internal/cli/server/flags.go 100.00% <100.00%> (ø)
... and 30 more

... and 20 files with indirect coverage changes

Files with missing lines Coverage Δ
core/blockchain.go 64.87% <ø> (+1.79%) ⬆️
core/blockchain_insert.go 78.50% <100.00%> (+1.27%) ⬆️
core/evm.go 93.67% <100.00%> (+19.65%) ⬆️
core/stateless/encoding.go 63.49% <ø> (ø)
core/stateless/witness.go 44.64% <100.00%> (+6.02%) ⬆️
core/txpool/blobpool/blobpool.go 54.87% <100.00%> (ø)
core/types/block.go 43.19% <ø> (ø)
eth/ethconfig/config.go 78.94% <ø> (ø)
eth/peer.go 95.80% <100.00%> (ø)
internal/cli/server/flags.go 100.00% <100.00%> (ø)
... and 30 more

... and 20 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@lucca30

lucca30 commented Apr 6, 2026

Copy link
Copy Markdown
Contributor

Additionally, the 500ms buffer previously reserved after transaction execution for SRC is removed when the pipeline is active. Transactions now get the full block time for inclusion since SRC runs in the background.

I am okay with the idea of removing the remaining 100ms.

We already reduced this buffer from 500ms to 100ms in v2.7.1, and from what we have seen so far, this remaining time looks small enough that removing it seems reasonable.

My main concern is not the removal of the 100ms itself. My concern is the cost of pipelining SRC with the next block production.

In other words: by doing SRC in parallel with block building, how much do we impact SRC time itself?

Do we expect SRC to remain roughly the same, or does it become meaningfully slower because it is now competing with the next block production? That is the part I would like to understand better.

I think this is basically a TPS vs finality question:

  • on one side, we gain more block time for transaction inclusion, which is good for TPS
  • on the other side, if SRC takes longer to complete, we may delay block completion, which could hurt finality

So I am supportive of the direction, but I think the key question is still:

How much TPS do we gain, and how much finality do we lose, if any, by making SRC fully pipelined with block production?

If the impact on SRC time is only slight, then the tradeoff is probably clearly worth it.

But if SRC time increases materially once it is pipelined with block production, then we should make that tradeoff explicit

@cffls

cffls commented Apr 7, 2026

Copy link
Copy Markdown
Contributor

Do we expect SRC to remain roughly the same, or does it become meaningfully slower because it is now competing with the next block production? That is the part I would like to understand better.

I think SRC will be roughly the same, because the time consuming part, trie nodes prefetching, is already running at the same time with tx execution today, and this PR doesn't change this behavior.

Comment thread core/txpool/txpool.go Outdated
Comment thread core/blockchain.go
Comment thread core/blockchain.go Outdated
Comment thread miner/pipeline.go Outdated
Comment thread miner/pipeline.go Outdated
Comment thread miner/pipeline.go
Comment thread miner/pipeline.go
…r block import

  Overlap SRC(N) with execution of block N+1 on importing/RPC nodes.
  After executing block N, defer IntermediateRoot + CommitWithUpdate to a
  background SRC goroutine and immediately proceed to block N+1 using a
  FlatDiff overlay for state reads. Cross-call persistence allows the SRC
  to run across insertChain boundaries.

  Key changes:
  - Pipeline path in insertChainWithWitnesses with ValidateStateCheap
  - FlatDiff overlay in StateAt, StateAtWithReaders, PostExecutionStateAt
  - Path DB reader chained fallback for concurrent layer flattening
  - Trie-only reader for SRC witness generation (no flat reader bypass)
  - WIT handler waits for pipelined witness before returning empty
  - WitnessReadyEvent for announcing witnesses to stateless peers
  - PropagateReadsTo in checkAndCommitSpan for witness completeness
  - Feature gated: --pipeline.enable-import-src
@pratikspatil024 pratikspatil024 changed the title miner: pipelined state root computation (PoC) miner, core, consensus/bor, eth, triedb: pipelined state root computation Apr 9, 2026
@pratikspatil024 pratikspatil024 changed the title miner, core, consensus/bor, eth, triedb: pipelined state root computation miner, core, consensus/bor, eth, triedb: pipelined state root computation (PoC) Apr 9, 2026
  Adds TestPipelinedImportSRC_SelfDestruct to verify that the FlatDiff
  Destructs check in getStateObject correctly handles self-destructed
  contracts during pipelined import.
  Two fixes for prefetcher errors during pipelined state root computation:

  1. Storage root mismatch: FlatDiff accounts had storage roots from block
     N's post-state, but the prefetcher's NodeReader was at the committed
     parent root (grandparent). Add prefetchRoot field to stateObject that
     stores the grandparent's storage root, read from the flat state reader
     when loading from FlatDiff. Use it consistently across all prefetcher
     interactions.

  2. Layer stale during trie node resolution: SRC's cap() flattens diff
     layers concurrently with prefetcher trie walks. Add nodeFallback to
     reader.Node(), mirroring the existing accountFallback/storageFallback
     pattern — retries via the current base disk layer on errSnapshotStale.
Comment thread triedb/pathdb/reader.go
    A series of fixes for pipelined SRC under EIP-2935/BLOCKHASH aborts and
    abort-heavy devnet load:

    1. Skip pipeline pre-Rio.
       Pre-Rio speculative Prepare walks unsigned speculative headers and can hit
       ecrecover failures on zero-seal Extra data. Disable pipelined SRC before
       Rio so the miner stays on the safe sequential path there.

    2. Move slot waiting fully to Seal and keep abort rebuilds in-slot.
       The miner now always builds block bodies early and uses the slot for tx
       selection, while Bor holds propagation until the target time in Seal().
       Abort-recovery headers carry a miner-local AbortRecovery flag so late
       speculative rebuilds stay in-slot instead of getting pushed to the next
       slot by minBlockBuildTime.

    3. Isolate block-build timeout state per build environment.
       Sequential builds and speculative fills previously shared a worker-global
       timeout flag, so one build's timer could interrupt another build's tx
       selection. Move timeout state onto each environment and make timer cancel
       stop the timer without poisoning the build as timed out.

    4. Improve speculative fill behavior and fix DAG metadata on refill.
       Speculative blocks now take a late refill pass when they are still under
       about 75% full by gas and there is at least 300ms left before the slot,
       not only when fully empty. Keep tx dependency DAG state on the block
       environment across refill passes so multi-pass speculative fills do not
       restart dependency indices from zero and drop metadata with
       non-sequential transaction index errors.

    5. Harden abort recovery and mined-block propagation.
       After speculative aborts, requeue normal work through the standard worker
       path instead of re-entering commitWork recursively. On the networking
       side, mined inline blocks now still announce correctly when witness data
       is already cached but the async block write is not yet visible in the DB.

    6. Add regression coverage and clean up logs.
       Add tests for Bor timing behavior, speculative refill decisions,
       per-build interrupt isolation, DAG metadata persistence across refill
       passes, cached-witness announcement, and BLOCKHASH(N) abort-flag
       behavior. Also remove duplicate EIP-2935 abort logs and fix negative
       seal-delay logging so slightly-late blocks no longer print huge wrapped
       unsigned delays.
  Wires a complete metrics suite for A/B comparing pipelined vs non-pipelined
  import and block production on mainnet.

  New pipelined metrics (import):
  - chain/imports/pipelined/{hit,miss,root_mismatch,enabled}
  - chain/imports/witness_ready_end_to_end — apples-to-apples end-to-end timer,
    fires in both modes (primary A/B KPI)

  New pipelined metrics (build):
  - worker/pipelineSpeculativeCommitted, pipelineSRCWait, pipelineSealDuration
  - worker/pipelineAnnounceEarlinessMs (signed ms — PIP-66 earliness signal)
  - worker/pipelineSpeculativeAborts/{blockhash,src_failed,fallback}
  - worker/build_to_announce — producer-side end-to-end, both modes
  - worker/pipeline/enabled

  Parity wiring for legacy metrics so dashboards work in both modes:
  - chain/inserts, account/storage read + hash + update + commit timers,
    snapshot/triedb commits, stateCommitTimer, blockBatchWriteTimer,
    witnessEncode/DbWrite — emitted from the pipelined branch (main statedb or
    SRC goroutine's tmpDB as appropriate)
  - worker/writeBlockAndSetHead — emitted from inlineSealAndBroadcast's async
    write goroutine
  - pipelineAnnounceEarlinessMs and pipelineSpeculativeCommittedCounter also
    emitted from resultLoop for the sealBlockViaTaskCh path

  Throughput and overlay observability:
  - chain/{gas_used_per_block,txs_per_block,mgasps} + chain/witness/size_bytes
  - worker/chain/{gas_used_per_block,txs_per_block}
  - state/flatdiff/{account_hits,storage_hits} — FlatDiff overlay effectiveness

  Metrics that have no clean pipelined semantic (chain/validation, chain/write,
  worker/commit, worker/finalizeAndAssemble, worker/intermediateRoot) are left
  unemitted in pipelined mode with inline comments documenting the reason and
  pointing to the closest pipeline equivalent.
@claude

claude Bot commented Apr 21, 2026

Copy link
Copy Markdown

Code Review

Found 3 issues in core/blockchain.go. Checked for bugs and CLAUDE.md compliance.

  1. Missing ValidateReorg guard in pipelined import path (HIGH) — The non-pipelined path checks forker.ValidateReorg per-block before writing to prevent a race with newly-whitelisted Heimdall milestones/checkpoints. The pipelined path skips this check, allowing a block that conflicts with a freshly-anchored milestone to be accepted as chain head.

  2. writeHeadBlock called without chainmu in auto-collection goroutine (HIGH) — On root mismatch, the recovery goroutine calls writeHeadBlock(parentBlock) which has a documented contract requiring the mu mutex to be held. The goroutine does not acquire it, creating a data race on chain head state.

  3. flushPendingImportSRC error silently discarded (HIGH) — The function returns a meaningful error (SRC failure or root mismatch from a previously-committed block) but the error is discarded with _ in the block-error cleanup path. Every other call site handles this error.

@claude

claude Bot commented Apr 21, 2026

Copy link
Copy Markdown

Inline Review Comments

Since inline comments could not be posted via the review API, here are the detailed findings with line references:


Issue 1 (HIGH): Missing ValidateReorg guard in pipelined import pathcore/blockchain.go:3457

The non-pipelined path (lines 3618-3631) calls forker.ValidateReorg per-block immediately before writing to DB to prevent a race where a Heimdall milestone/checkpoint is whitelisted during block execution. The pipelined path skips this check entirely, calling writeBlockAndSetHeadPipelined without a preceding ValidateReorg. A block that conflicts with a freshly-anchored Heimdall milestone or checkpoint can be accepted as chain head in pipelined mode. The upfront check at line 3103 runs before execution begins and does not close this window.

Fix: Add the same per-block ValidateReorg guard before writeBlockAndSetHeadPipelined.

CLAUDE.md: blockchain-security.md and consensus-security.md


Issue 2 (HIGH): writeHeadBlock called without chainmu in auto-collection goroutinecore/blockchain.go:3507

writeHeadBlock has a documented contract at line 1733: "this function assumes that the mu mutex is held!". The auto-collection goroutine calls it without acquiring chainmu. After insertChainWithWitnesses returns and releases chainmu, this goroutine may still be running. If a root mismatch is detected, writeHeadBlock is called without the mutex — while another goroutine could concurrently acquire chainmu for a new InsertChain call. This is a data race on chain head state.

Fix: Acquire bc.chainmu.Lock() before calling writeHeadBlock in the error recovery path.

CLAUDE.md: security-common.md — "Shared mutable state protected by mutex or atomic operations"


Issue 3 (HIGH): flushPendingImportSRC error silently discardedcore/blockchain.go:3407

flushPendingImportSRC() returns a meaningful error (state root mismatch or SRC failure from a previously-committed block). Discarding it with _ means a block with a bad state root could persist undetected. Every other call site handles this error (line 1784, line 3328).

Fix: Replace _ = bc.flushPendingImportSRC() with if err := bc.flushPendingImportSRC(); err != nil { log.Error(...) } consistent with other call sites.

CLAUDE.md: security-common.md — "Error values checked — never discard errors with _ in security-sensitive paths"

pratikspatil024 and others added 3 commits April 22, 2026 23:31
…ions for diffguard compliance

Decompose large pipelined-src-authored functions into focused helpers so
every function owned by this branch sits under diffguard's 50-line /
complexity-10 limits. Pure structural refactor — no behavior change.

miner/pipeline.go:
- commitSpeculativeWork (599) → orchestrator (35) + specSession struct
  with ~18 methods (setupInitial, waitForSRCAndSealBlockN, runOneIteration,
  prepareNextIteration, sealCurrentAndAdvance, shiftToNext, etc.)
- inlineSealAndBroadcast (100) → 35 + sealViaPrivateChannel,
  rebindReceiptsToSealedBlock, announceInlineSealedBlock
- commitPipelined (59) → 37 + buildSpeculativeReq, spawnSRCForFinalBlock
- sealBlockViaTaskCh (52) → 48 (reuses spawnSRCForFinalBlock)

miner/worker.go:
- fillTransactions (59) → 47 + commitTxMaps
- makeEnv (51) → 38 + resolveStateFor
- updateTxDependencyMetadata (68) → 32 + buildTxDependencyArray

Pre-existing develop functions where pipelined-src had grown the body
are reduced back close to or below their develop size by extracting the
added branches:
- commitWork (67 → 36) via clearPendingWorkOnExit + maybeStartPrefetch
- resultLoop (191 → 124; develop was 123) via emitExecutionMetrics,
  emitCommitMetrics, writeTaskBlock, announceTaskBlock
- mainLoop (135 → 120; develop was 116) via handleSpeculativeWork
- buildAndCommitBlock (93 → 83; develop was 80) via submitForSealing

core/state/statedb.go:
- CommitSnapshot (95, complexity 40) → 30 + captureMutation,
  captureObjectStorage, captureReadOnlyAccount, captureNonExistentRead
- ApplyFlatDiffForCommit (49, complexity 20) → 16 + applyFlatMutation
- ApplyFlatDiff (36, complexity 11) → 13 + applyFlatAccountOverlay
- TouchAllAddresses (25, complexity 11) → 12 + touchAddressAndStorage,
  mutatedStorageKeys

core/blockchain.go:
- SpawnSRCGoroutine (127, complexity 35) → 13 + runSRCCompute,
  openSRCStateDB, preloadFlatDiffReads, emitSRCStateDBMetrics,
  encodeAndCachePendingWitness
- writeBlockAndSetHeadPipelined (108, complexity 29) → 16 +
  writePipelinedBlockBatch, writeBorStateSyncLogs, resolveWriteStatus,
  emitPipelinedWriteEvents
- handleImportTrieGC (52, complexity 16) → 21 + capTrieIfDirty,
  maybeFlushChosen, dereferenceUpTo
- waitForPipelinedWitness (complexity 11) → 9 + waitForPendingSRCWitness,
  pollWitnessCache

core/evm.go:
- SpeculativeGetHashFn (complexity 12) → 17 + newPendingBlockNResolver

core/blockchain.go insertChainWithWitnesses pipelined branch (had grown
+222 lines on top of develop's 452) → +42 via buildPipelineImportOpts,
persistPipelinedImport, collectPrevImportSRCIfAny, emitStateSyncFeed,
runImportAutoCollection, verifyImportSRCRoot, publishImportWitness,
emitPipelinedImportParityMetrics.

core/blockchain.go ProcessBlock pipelined branches (+22 lines) → +4
via pipelineReaderRoot, applyFlatDiffOverlayToAll, validateStateForPipeline.

eth/peer.go:
- doWitnessRequest (pipelined-src pushed from 38 → 65) → 32 +
  awaitWitnessResponse extracting the goroutine body

eth/handler_wit.go:
- handleGetWitness (pipelined-src pushed from 70 → 91) → 66 +
  resolveWitnessSizes consolidating per-hash size resolution (rawdb +
  header-existence DoS guard + SRC cache fallback)

tests/bor/helper.go:
- InitMinerWithPipelinedSRC (65) → 32 + newPipelineTestNode (17),
  importValidatorKey (11)
- InitImporterWithPipelinedSRC (64) → 31 (same helpers)

  Mutation coverage. Ran diffguard in diff-scoped mode (-base develop
  -include-paths <module>) across every module pipelined-src touches and
  filled the gaps it surfaced:

  - core/state: adds core/state/statedb_pipeline_mutations_test.go with 41
    targeted tests that kill 24 of 28 mutation survivors in pipelined-src
    FlatDiff code (statedb.go lines 2031-2330, 2492-2499). The 4 remaining
    are equivalent mutants — Finalise removes destructed addrs before the
    guarded branches can fire (2114, 2163), a zero-length loop produces
    the same output with or without the guard (2141), and an empty-slice
    map entry is observationally equivalent to a missing entry (2150).
    Covers CommitSnapshot and its capture helpers, ApplyFlatDiff +
    applyFlatAccountOverlay, ApplyFlatDiffForCommit + applyFlatMutation,
    NewWithFlatBase, TouchAllAddresses + touchAddressAndStorage +
    mutatedStorageKeys, WasStorageSlotRead, and PropagateReadsTo — 14 of
    15 functions at 100% line coverage (captureReadOnlyAccount at 90.9%).

  - core/stateless: extends witness_test.go with 3 tests targeting
    ValidateWitnessPreState's expectedBlock guard (ParentHash and Number
    checks that defend against a malicious peer substituting a witness
    for a different block / fork). Previous tests all passed nil for
    expectedBlock, leaving the entire anti-forgery branch uncovered.

  - eth/filters: adds TestResolveBlockNumForRangeCheck and
    TestCheckBlockRangeLimit (16 subcases) to api_test.go covering the
    RPC range-limit DoS guard at the unit level (sentinel resolution,
    span-at-limit boundary, sum-vs-span distinction). Extends
    TestInvalidGetRangeLogsRequest in filter_system_test.go to also
    exercise GetBorBlockLogs with an inverted range — previously only
    GetLogs was covered.

  Per-module mutation scores after this coverage: miner 96%, consensus/bor
  100% (41/41), core 100% (447/447), core/state 86% (24/28 equivalent),
  core/stateless 100% (8/8), core/txpool 100% (12/12), tests/bor 100%
  (43/43), triedb/pathdb 100% (20/20). eth at 53% — remaining survivors
  are in auto-generated gen_config.go boilerplate (36), P2P dispatcher
  cancel-channel plumbing  awaitWitnessResponse goroutine cleanup
  (3); documented as accepted gaps requiring complex mock infrastructure
  for diminishing security return.

Remaining diffguard violations in miner and core are pre-existing
develop functions (commitTransactions, insertChainWithWitnesses,
newWorkLoop, NewBlockChain, ProcessBlock, writeBlockWithState, etc.)
that were over threshold on develop before pipelined-src. Their
pipelined-src deltas are now small (+1 to +42 lines) and out of scope
for this PR.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
  Renames (reviewer nits):
  - PostExecutionStateAt → PostExecState (BlockChain + txpool/legacypool/
    blobpool interfaces + test mocks).
  - ResetSpeculativeState → SetSpeculativeState, SpeculativeResetter →
    SpeculativeSetter (the method overwrites, it doesn't revert).

  Dedup between writeBlockAndSetHead and writeBlockAndSetHeadPipelined.
  Both paths now share resolvePostWriteStatus(block, stateless) for
  fork-choice + reorg (stateless flag preserves the errInvalidNewChain
  escape for fast-forward sync), emitPostWriteEvents for the feed sends,
  and writeBorStateSyncLogs for the pre-Madhugiri bor receipt. ~80 lines
  of duplicated fork-choice + event logic removed; writeBlockAndSetHead
  drops from ~70 to ~11 lines. Batch bodies intentionally not merged —
  witness source (statedb.Witness() vs pre-encoded bytes) and trie-commit
  timing genuinely differ.

  Miner coinbase unification: extracted resolveCoinbase(blockNumber,
  fallback) used by makeHeader (fallback=genParams.coinbase) and the
  speculative header builders (fallback=etherbase()). Divergence between
  the speculative and real header would cause a state root mismatch, so
  single-sourcing this is security-meaningful. Rest of buildInitialSpecHeader
  kept separate from makeHeader (placeholder parent, deterministic bor
  period timestamp, static GasCeil, no engine.Prepare); comment documents
  why unifying further would hurt readability.

  Pipelined import correctness fixes (core/blockchain.go):
  1. persistPipelinedImport now runs the Heimdall milestone/checkpoint
     ValidateReorg guard before writeBlockAndSetHeadPipelined — mirrors
     the non-pipelined path. Without it, a milestone whitelisted during
     block execution could be bypassed.
  2. verifyImportSRCRoot wraps its writeHeadBlock revert in chainmu
     TryLock/Unlock. The call ran in the auto-collection goroutine
     without the mutex, racing any concurrent InsertChain on head state.
     Skips + warns if chainmu is closed (shutdown).
  3. flushPendingImportSRC error in insertChain's ProcessBlock error
     path no longer discarded with `_`; logged like the other two call
     sites.

  Linter: dropped two `tc := tc` loop-var copies in eth/filters/api_test.go
  (copyloopvar, redundant since Go 1.22).
Copilot AI review requested due to automatic review settings July 21, 2026 11:06
@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
8.2% Duplication on New Code (required ≤ 3%)

See analysis details on SonarQube Cloud

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 65 out of 66 changed files in this pull request and generated 2 comments.

Comment thread core/blockchain.go
Comment thread eth/handler.go
…port

Verify the read-only RPC surface on a node importing with pipelined SRC,
and close the one gap found: handlers that open tries directly by root
(eth_getProof, debug_storageRangeAt) failed transiently when queried at
the head whose SRC hadn't committed yet, since proofs need trie nodes the
FlatDiff overlay cannot provide.

- WaitForPipelinedStateCommit: bounded wait on the pending import's
  collectedCh; no-op for any root other than the pending head's. Wired
  into eth_getProof (via a new ethapi.Backend method) and
  debug_storageRangeAt, converting the transient error into a few ms of
  latency. Overlay-served value reads are untouched.
- core/pipelined_window_test.go: holds the SRC goroutine open via a
  test-only hook to pin window semantics deterministically — overlay
  reads correct, raw trie opens fail, wait gate blocks then releases,
  post-settle values identical, proofs verify. Both schemes, race-clean.
- tests/bor/pipelined_rpc_test.go: BP -> pipelined importer sync with a
  live tx stream; strict 12-method battery at pinned heights and latest
  (getProof now strict), sync-time vs settled response identity, full
  per-height parity vs a non-pipelined BP incl. cryptographic account
  proof verification.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 22, 2026 07:36

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 1 comment.

Comment thread eth/handler_wit.go Outdated
Conflict: consensus/bor/bor.go — #2314 extracted commitSprintWork and our
branch added FinalizeForPipeline/AssembleBlock at the same location; both
kept verbatim.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 22, 2026 07:44
@pratikspatil024

Copy link
Copy Markdown
Member Author

@claude review

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 2 comments.

Comment thread core/state/state_object.go Outdated
Comment thread tests/bor/pipelined_rpc_test.go Outdated

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No new bugs found this run. Given the scale and consensus-critical nature of this pipelined state-root PR (73 files, block-import head-advance-before-root-verify, witness protocol changes), this still needs human sign-off rather than a shadow approval.

What was reviewed: the FlatDiff/PostExecState overlay and prefetch-root consistency paths, the pipelined-import auto-collection/root-verification flow, and the WIT-protocol witness-serving changes (resolveWitnessSizes, GetWitness pipelined wait, minedBroadcastLoop goroutine lifecycle, warm-snapshot Verkle handling) flagged by Copilot — none of these rose to a confirmed bug: the witness-wait/size-resolution paths are bounded by the header-existence + 64-block recency gate already added, and the goroutine-lifecycle/Verkle concerns didn't reproduce a concrete failure on inspection.

Extended reasoning...

No bugs were found by the bug-hunting system in this run. This PR (pipelined SRC for block import) is large (73 files) and touches consensus-critical paths: block head advancement before state-root verification, FlatDiff overlay masking for txpool/RPC reads, witness generation/serving over the WIT protocol, and prefetcher root handling. Prior runs found several real bugs (missing ValidateReorg guard, writeHeadBlock without chainmu, discarded flushPendingImportSRC error, nil-deref in pipeline.go, stale witness header copy) which the author has since fixed per their replies in the thread.

This run's bug hunters specifically re-examined the four still-open Copilot findings (resolveWitnessSizes read-before-budget-check and cache pollution, GetWitness's unbounded wait, minedBroadcastLoop's untracked goroutine, warm-snapshot behavior under Verkle) and did not confirm any of them as bugs beyond what the author already addressed via the header-existence/64-block-recency gate. That gate bounds the size-resolution and wait paths to a single in-flight SRC block rather than arbitrary hashes, which is why these did not reproduce as a real DoS or correctness issue on inspection.

Given the severity classification in this repo's own security rules (peer/RPC-triggerable issues in consensus/sync paths are escalated), and the sheer surface area of this diff (state package internals, bor consensus timing, witness protocol, txpool interaction), a human reviewer with deep context on the pipelined-SRC design should make the final call, especially since this is a new consensus-adjacent subsystem being enabled by a feature flag for the first time.

Bring the new-code diffguard complexity violations under threshold and
remove production-code duplication:

- statedb: resolveFlatMutationObject extracted from applyFlatMutationFast;
  the two witness-collection loops in IntermediateRoot deduped into
  addObjectWitness/addWitnessNodes.
- blockchain: preload read-surface histograms + preloadFlatDiffReads
  extracted from runSRCCompute into recordAndPreloadSRCWitnessReads.
- bor: FinalizeForPipeline now reuses commitSprintWork (extracted on
  develop by #2314) instead of duplicating the sprint-start span and
  state-sync commits; this also sets BorConsensusTime on the pipelined
  path, matching FinalizeAndAssemble.

Behavior-preserving; legacy-grown giants (insertChainWithWitnesses,
IntermediateRoot, getStateObject, updateTrie) are declared as explicit
deviations in the PR description.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 23, 2026 04:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

eth/handler_wit.go:143

  • resolveWitnessSizes calls Chain().GetWitness(hash) to fetch witnesses when the persisted size index is missing. GetWitness() populates the chain witness cache (core/blockchain_reader.go:166-183) and may load a full witness before the per-request memory budget checks in handleGetWitness, so peer traffic can evict import-critical cache entries and can force large allocations even for requests that would later be rejected.

Consider adding an uncached “wait for pipelined witness” read path (e.g. extend GetWitnessUncached to also wait, but never cache), and use that here; alternatively, avoid prefetching full witnesses in size resolution and only fetch on-demand after passing the memory-budget guard.

Comment thread core/txpool/legacypool/legacypool.go
Comment thread core/blockchain_reader.go
- PostExecState: match the cached FlatDiff by state root in addition to
  block number (zero stored root — the miner's speculative path — keeps
  the number-only match), so a same-height reorg can't serve a stale
  overlay.
- waitForPendingSRCWitness: cap the collectedCh wait so a stalled SRC
  goroutine can't pin peer/RPC handlers indefinitely.
- resolveWitnessSizes: fetch via new GetWitnessUncachedWait (bounded SRC
  wait without inserting peer-driven reads into the witness cache) and
  cap retained prefetch bytes at MaximumResponseSize.
- updateTrie: surface witness re-read GetStorage failures via setError
  instead of silently producing an incomplete witness.
- minedBroadcastLoop: track the announceMinedBlock goroutine on h.wg so
  handler shutdown doesn't race peer close.
- SetSpeculativeState: publish NewTxsEvent after releasing pool.mu,
  matching the regular promotion flow.
- pipelined_rpc_test: assert fdlimit.Raise succeeds.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 23, 2026 07:42

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 2 comments.

Comments suppressed due to low confidence (1)

consensus/bor/bor_test.go:5120

  • This subtest’s fixed 100ms lower bound can be flaky, and it doesn’t directly assert the intended property (that Prepare(waitOnPrepare=true) waits until the cached parent boundary). Prefer asserting that the call returns at/after parentActual (with parentActual set far enough in the future to tolerate CI variability).
		require.NoError(t, err)
		require.GreaterOrEqual(t, elapsed, 100*time.Millisecond)
		require.NotZero(t, header.Time, "Prepare should still populate header time")

Comment thread triedb/pathdb/reader.go
Comment thread consensus/bor/bor_test.go
The three pipelined integration tests failed on CI with both nodes at
zero peers for the full 60s window. Root cause: the dial scheduler
dedupes static nodes by ID (addStaticCh handling ignores re-adds), so
when the first AddPeer captures a port-0 enode — Self() publishes its
TCP port asynchronously after the listener starts — the "self-healing"
re-add loop never updates the record and the dialer keeps dialing a dead
address forever. On top of that, a transiently failed dial parks the
target in dial history for 35s, so a 60s deadline covers barely one
retry. connectAndWaitForPeers now waits for both enodes to publish a
real port before the first AddPeer and allows several history windows.

Also addresses review findings:
- bor_test: the waitOnPrepare subtests assert against the parent slot
  boundary instead of fixed elapsed-time bounds, so loaded runners
  can't flake them.
- pathdb: nodeFallback goes straight to tree.bottom() — nodeWalk's
  primary attempt already is the entry-layer walk, so retrying it
  deterministically hits the same stale disk layer (unlike
  account/storage fallbacks, whose primary attempt is the lookup-index
  path).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 23, 2026 08:12

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (1)

tests/bor/pipelined_rpc_test.go:86

  • Same as above: waiting for importer listener port without a timeout can hang the test indefinitely on startup failures. Please add a bounded wait.

Comment thread tests/bor/pipelined_rpc_test.go
Comment thread miner/speculative_chain_reader.go
Comment thread tests/bor/pipelined_rpc_test.go Outdated
- speculative_chain_reader: guard the parent-number subtraction in GetTd
  against a genesis parent (can't happen on the speculative path, but an
  underflow to MaxUint64 is worth a two-line guard).
- pipelined_rpc_test: handle crypto.GenerateKey errors; drop the
  unbounded listener-wait loops — connectAndWaitForPeers now waits for
  published listener ports under a deadline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 23, 2026 09:32

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 72 out of 73 changed files in this pull request and generated 1 comment.

Comment on lines +57 to +66
// warmKey identifies a trie node by its containing trie's owner (zero for
// the account trie, account hash for storage tries), its path within the
// trie, and the node hash itself. The hash field disambiguates entries that
// share owner+path across different blocks/states.
// warmKey identifies a trie node by owner, path and content hash. It is a
// comparable value type built on the stack — a string-keyed variant would
// allocate on every Lookup, since Go's string(bytes) map-index optimization
// does not apply to struct-literal keys. MPT paths are at most 64 nibbles;
// longer paths cannot be produced by the trie and are simply not indexed
// (Lookup misses fall through to the underlying reader).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants