Skip to content

bal-devnet-7_warmup: refresh onto bal-devnet-7 (pulls in deep-storage-fold fix #21945) - #22129

Merged
AskAlexSharov merged 89 commits into
alex/bal-devnet-7_warmupfrom
alex/bal-devnet-7_warmup2
Jul 1, 2026
Merged

AskAlexSharov merged 89 commits into
alex/bal-devnet-7_warmupfrom
alex/bal-devnet-7_warmup2

Conversation

@sudeepdino008

Copy link
Copy Markdown
Member

Refreshes bal-devnet-7_warmup onto bal-devnet-7, pulling in the parallel deep-storage-fold trie-root fix #21945 (and its feature line #21709, #21941).

sstore_bloated MGas/s

variant base (par-kvi, BAL) ethrex now (+#21945)
sstore_bloated (F) — new slots 92 461 399
sstore_bloated (T) — existing slots 7 82 30

Notes

Rendered as a merge, so review the conflict resolutions rather than the upstream commits:

  • commitment_convert.go / _blackbox_test.go: took bal-devnet-7's merged form ([3.6] cmd: erigon commitment convert #21933) over the pre-merge draft.
  • db/seg/decompress.go: comment-only, took bal-devnet-7 (db/seg: drop redundant posTable.ptrs pointer array #21927).
  • rawdbreset/reset_stages.go: combined out-of-tx ClearTables with bal-devnet-7's branchCache invalidation.
  • stage_execute.go: kept warmup's PruneExecutionStage (haveMore signature, required by callers). bal-devnet-7's execution: share prune timeout budget #20860 prune-timeout budget-sharing is not carried into this function.
  • cmd/integration/commands/stages.go: took bal-devnet-7's stageExec (drops the removed --no-commit flag), adapted its prune calls to the haveMore signature.

lystopad and others added 30 commits June 16, 2026 12:04
…Fulu+ (#21842)

## Problem

`block_importing_latency` (Caplin) reads a **flat 0 for hours** in
Grafana — it's effectively never recorded.

It's a gauge set only by `ObserveBlockImportingLatency`, whose **only**
caller (`collectOnBlockLatencyToUnixTime`) was nested inside the
**Deneb-only blob data-availability branch** of `OnBlock`, behind:
`checkDataAvaiability && BlobKzgCommitments.Len() > 0 && !elHasBlobs &&
version is Deneb/Electra (not Fulu) && highestSeen < slot`.

Two of those gates kill it:
- **Fulu+** (current mainnet) takes the Fulu/PeerDAS branch, which never
calls it → the gauge stays at its `0` default. This is the flat line.
- Even pre-Fulu it only fired for a current-slot block carrying blobs
the EL didn't already have — a rare race at tip — and skipped blob-less
blocks entirely.

So the panel value is not a real "0 ms"; the setter just isn't reached.

## Fix

Move the single call to the **fork-agnostic `highestSeen` tip-advance**
in `OnBlock`, which runs for every new head block on all forks
(Fulu/Gloas included). The helper's existing `slot == GetCurrentSlot()`
guard still skips backfill/sync blocks, so the gauge measures
head-arrival latency rather than block age.

```go
if block.Block.Slot > f.highestSeen.Load() {
    f.highestSeen.Store(block.Block.Slot)
    f.highestSeenRoot.Store(common.Hash(blockRoot))
    collectOnBlockLatencyToUnixTime(f.ethClock, block.Block.Slot) // fork-agnostic
}
```

Net effect: `block_importing_latency` now populates every slot the node
imports a current-slot head block, on all forks.

## Tests

`TestCollectOnBlockLatency` covers the helper's contract (records for a
current-slot block, skips a past/backfill slot).

This change is a one-call relocation — the emitting helper itself is
unchanged. A full cross-fork `OnBlock` integration test was
intentionally not added, as exercising it requires substantial
fork-choice/engine/PeerDAS scaffolding to make a test block "current";
flagging per the repo's TDD-pragmatism guidance. `make lint` and `make
erigon` clean.

## Scope

Affects `release/3.4`, `release/3.5`, and `main` (the call placement is
identical on all three). Will cherry-pick to the release branches.

---------

Co-authored-by: kewei <kewei.train@gmail.com>
…#21252)

Bumps
[actions/create-github-app-token](https://github.com/actions/create-github-app-token)
from 3.1.1 to 3.2.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/actions/create-github-app-token/releases">actions/create-github-app-token's
releases</a>.</em></p>
<blockquote>
<h2>v3.2.0</h2>
<h2><a
href="https://github.com/actions/create-github-app-token/compare/v3.1.1...v3.2.0">3.2.0</a>
(2026-05-12)</h2>
<h3>Features</h3>
<ul>
<li>add support for enterprise-level GitHub Apps (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/263">#263</a>)
(<a
href="https://github.com/actions/create-github-app-token/commit/952a2a7073df6bfa5f49bc469ec895b6ec1acea4">952a2a7</a>)</li>
<li>support full repository names in <code>repositories</code> input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/372">#372</a>)
(<a
href="https://github.com/actions/create-github-app-token/commit/85eb8dd41472213aed25d1a126460e0069138ab6">85eb8dd</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>deps:</strong> bump <code>@​actions/core</code> from 3.0.0
to 3.0.1 in the production-dependencies group (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/364">#364</a>)
(<a
href="https://github.com/actions/create-github-app-token/commit/43e5c345bfd4d4f3ecea019ad0042001a09dd857">43e5c34</a>)</li>
<li>validate private-key input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/376">#376</a>)
(<a
href="https://github.com/actions/create-github-app-token/commit/f24bbd89643991c0de27ae823c01791b2c6bafdd">f24bbd8</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/actions/create-github-app-token/blob/main/CHANGELOG.md">actions/create-github-app-token's
changelog</a>.</em></p>
<blockquote>
<h1>Changelog</h1>
<h2><a
href="https://github.com/actions/create-github-app-token/compare/v3.1.1...v3.2.0">3.2.0</a>
(2026-05-12)</h2>
<h3>Features</h3>
<ul>
<li>add support for enterprise-level GitHub Apps (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/263">#263</a>)
(<a
href="https://github.com/actions/create-github-app-token/commit/952a2a7073df6bfa5f49bc469ec895b6ec1acea4">952a2a7</a>)</li>
<li>support full repository names in <code>repositories</code> input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/372">#372</a>)
(<a
href="https://github.com/actions/create-github-app-token/commit/85eb8dd41472213aed25d1a126460e0069138ab6">85eb8dd</a>)</li>
</ul>
<h3>Bug Fixes</h3>
<ul>
<li><strong>deps:</strong> bump <code>@​actions/core</code> from 3.0.0
to 3.0.1 in the production-dependencies group (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/364">#364</a>)
(<a
href="https://github.com/actions/create-github-app-token/commit/43e5c345bfd4d4f3ecea019ad0042001a09dd857">43e5c34</a>)</li>
<li>validate private-key input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/376">#376</a>)
(<a
href="https://github.com/actions/create-github-app-token/commit/f24bbd89643991c0de27ae823c01791b2c6bafdd">f24bbd8</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/actions/create-github-app-token/commit/bcd2ba49218906704ab6c1aa796996da409d3eb1"><code>bcd2ba4</code></a>
chore(main): release 3.2.0 (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/370">#370</a>)</li>
<li><a
href="https://github.com/actions/create-github-app-token/commit/f24bbd89643991c0de27ae823c01791b2c6bafdd"><code>f24bbd8</code></a>
fix: validate private-key input (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/376">#376</a>)</li>
<li><a
href="https://github.com/actions/create-github-app-token/commit/363531b6d972a60a00b3f1e6bb139e5e6c764cd9"><code>363531b</code></a>
docs: capitalize Git as a proper noun in README (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/374">#374</a>)</li>
<li><a
href="https://github.com/actions/create-github-app-token/commit/fd2801133e469d2950f2c5af5e591d6b2ad833c8"><code>fd28011</code></a>
docs: update procedure to configure Git (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/287">#287</a>)</li>
<li><a
href="https://github.com/actions/create-github-app-token/commit/85eb8dd41472213aed25d1a126460e0069138ab6"><code>85eb8dd</code></a>
feat: support full repository names in <code>repositories</code> input
(<a
href="https://redirect.github.com/actions/create-github-app-token/issues/372">#372</a>)</li>
<li><a
href="https://github.com/actions/create-github-app-token/commit/c9aabb83728c3bd519212fa657ebc07e1f2a5dec"><code>c9aabb8</code></a>
build(deps-dev): bump yaml from 2.8.3 to 2.8.4 in the
development-dependencie...</li>
<li><a
href="https://github.com/actions/create-github-app-token/commit/e02e816e5591415258a53bf735aff57977dcd5e2"><code>e02e816</code></a>
build(deps-dev): bump undici from 7.24.6 to 8.2.0 (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/366">#366</a>)</li>
<li><a
href="https://github.com/actions/create-github-app-token/commit/8d835bfd37aa48fcb8e709925115857568d98bc4"><code>8d835bf</code></a>
build(deps-dev): bump esbuild from 0.27.4 to 0.28.0 in the
development-depend...</li>
<li><a
href="https://github.com/actions/create-github-app-token/commit/952a2a7073df6bfa5f49bc469ec895b6ec1acea4"><code>952a2a7</code></a>
feat: add support for enterprise-level GitHub Apps (<a
href="https://redirect.github.com/actions/create-github-app-token/issues/263">#263</a>)</li>
<li><a
href="https://github.com/actions/create-github-app-token/commit/43e5c345bfd4d4f3ecea019ad0042001a09dd857"><code>43e5c34</code></a>
fix(deps): bump <code>@​actions/core</code> from 3.0.0 to 3.0.1 in the
production-dependenc...</li>
<li>Additional commits viewable in <a
href="https://github.com/actions/create-github-app-token/compare/1b10c78c7865c340bc4f6099eb2f838309f1e8c3...bcd2ba49218906704ab6c1aa796996da409d3eb1">compare
view</a></li>
</ul>
</details>
<br />

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com>
Co-authored-by: lystopad <oleksandr.lystopad@erigon.tech>
`Signal()` was called without holding `queueMutex`, creating a window
where the signal fires after the inner goroutine checked `waitCtx.Err()
== nil` but before it called `queueCond.Wait()`. The signal is silently
consumed and the inner goroutine blocks forever — manifesting as
`TestTipEventsNewBlockHashesEmitsEvent` timing out after 1h in CI.

Fix: acquire the mutex before `Signal()`. `sync.Cond.Signal` does not
require the mutex, but calling it under the lock prevents the race: the
outer goroutine blocks until the inner is either inside `Wait()` (where
it will be woken up) or has already exited the loop.
…#21774)

## Summary

- `request_generator.go`: replace `base64.StdEncoding.EncodeToString`
with `hexutil.Encode` for the `start` parameter — the server expects a
`0x`-prefixed hex string, not base64
- `bench1.go`, `bench3.go`, `bench9.go`, `account_range_verify.go`:
replace the initial page cursor from `common.Hash{}[:]` (32 bytes,
rejected by the server) to `common.Address{}[:]` (20 bytes)
- `request_generator_test.go`: update test cases to use 20-byte
addresses and hex-encoded expected strings

## Test plan

- [ ] `go test ./cmd/rpctest/rpctest/... -run
TestRequestGenerator_accountRange` passes
- [ ] `go test ./rpc/jsonrpc/... -run TestAccountRange` passes
- [ ] `make lint` clean
This is **PR #1 of a 3-PR perf stack**. It consolidates state caching
across all modes — sync, tip-tracking, integration and testing — so the
state cache is either **on and completely trustworthy**, or **off to
measure** — never off because it unexpectedly breaks things.

The caches (the account/storage `StateCache` and the commitment
`BranchCache`) are an **internal implementation detail of
`SharedDomains`**. No external entity accesses or mutates them directly:
callers drive state through `Flush` / `Commit` / `GetLatest` /
`DomainPut`, and the full cache lifecycle (population, invalidation,
commit-gating) is owned inside `SharedDomains`.

## What this PR contains

### `BranchCache` — single aggregator-scope commitment cache
- Aggregator-lifetime cache: pinned root slot + bounded LRU tail, behind
the `sd.mem` chain so unwinds and fork-validations see consistent state.
- Wired into the trie read + encoder write paths.
- Tx-precise unwind invalidation: entries are stamped with their per-key
write `txNum`; `sd.Unwind` evicts everything above the unwind watermark
(`BranchCache.UnwindTo`).

### One switch for all caches
The `BranchCache` is a *type of* state cache, so it rides the existing
`USE_STATE_CACHE` toggle rather than getting its own env. One operator
switch turns **all** caching off — the relevant operation when bisecting
a state-root mismatch, where an operator shouldn't have to reason about
the interaction of several independent caches. (This is a deliberate
deviation from the review's "add a separate `BranchCache` kill-switch"
suggestion — flagged for confirmation.)

### The BranchCache reflects only committed state — by construction
The BranchCache can never hold a value a failed commit rolled back:
`SharedDomains.Commit` flushes the in-memory batch into the tx, commits,
and **only then** applies the flushed commitment branches to the cache
(the flush is implicit in committing; a failed commit applies nothing).
Plain `Flush` — callers that own their own commit, e.g. offline tools —
never touches the cache; read-through populates from committed files.
The caches are an internal detail of `SharedDomains`; nothing else
writes to them.

> **StateCache no-poisoning is #21386, not this PR.** Unlike the
BranchCache, the account/storage StateCache is an *in-flight,
cross-transaction* cache — it holds prior txs' not-yet-committed writes
within a batch, and later txs read them from it. So it can't be made
commit-safe by simply invalidating on write (that breaks cross-tx reads
in serial exec). Its no-poisoning is the txNum/epoch rework in #21386;
this PR keeps its existing ValidateAndPrepare/unwind invalidation.
### BUG #21138 — parallel-exec from-0 wrong trie root
`ResetExec` wipes the commitment DB table; the aggregator's in-memory
`BranchCache` could still reference the just-deleted trie nodes, so a
from-0 re-exec served stale entries when computing block 0's commitment
→ wrong root, dropping genesis-allocated balances no later block touched
(mainnet block 46147, `0xA1E4380A3B1f749673E270229993eE55F35663b4`).
Fix: `ResetExec` clears the aggregator's `BranchCache`.
`TestFromZero_GenesisAllocPreservedAfterResetReExec` passes on current
`main`; the test's value here is keeping *this PR's* cache safe across
reset, not fixing a live `main` bug.

## Follow-ups (the rest of the stack)
- **#21386 (PR #2 of the stack) — StateCache LRU + Mode rework:**
consistency + no performance drop-off at a 1 GB cache for long-running
nodes; re-adds the warm StateCache repopulation deferred above, under
its `txNum`/`epoch` model.
- **Pinning** (the stack's third step) — **no PR yet**; in progress and
under test on branch
[`mh/branch-cache-trunk-pin`](https://github.com/erigontech/erigon/tree/mh/branch-cache-trunk-pin),
to be **re-benchmarked before merge**.
- **#21739** — interface-unification follow-up: collapse the duck-typed
`GetLatest` variants into a single metered, `txNum`-returning
`GetLatest`.
## Testing
Behaves identically across parallel and serial exec — confirmed in CI
across both exec modes. Unit coverage for the BranchCache (tiers,
tx-precise `UnwindTo`, commit-gated population) plus the
engine/exec-module FCU commit paths.

Given today's changes (the commit-gating of both caches), we will do
another A/B performance run before merging.

---------

Co-authored-by: Mark Holt <erigon@dev-bm-e3-ethmainnet-n4.erigon.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
## What

`erigon seg rm-state --step` now accepts an `N+` form meaning "from step
N to the latest available step":

- `--step 5+` — remove everything from step 5 to the latest
- `--step 5-10` — unchanged

Previously the only way to express "from step N to the end" was an
arbitrarily large upper bound like `--step 5-9999999`.

## How

The `--step` parsing is extracted into `parseStepRange(stepRange,
maxAvailableStep)`. For the `N+` form it parses the prefix as `from` and
sets `to` to the highest `toStep` among the state files already scanned
by `DeleteStateSnapshots` (respecting any `--domain` filter). The
`from-to` form is unchanged. Only the range parsing changed — the
file-removal logic is untouched.

## Tests

- `Test_parseStepRange` — table-driven unit test covering both formats
and error cases (empty, `+` only, bad prefix, non-numeric).
- `Test_DeleteStateSnaps_StepRange_FromPlus` — end-to-end: `192+`
removes the `192-224` merged file plus its sub-ranges and keeps `0-128`
/ `128-192`, identical to `192-224` since 224 is the highest step.

`make lint` clean; `go test ./cmd/utils/app/` passing.

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
…ng round-trip failure (#21839)

## Summary

- Fixes FuzzRLP encode-decode round-trip failure for nested RLP lists
(input `c2c23030` decoded and re-encoded as `c0`)
- Root cause: `Stream.Kind()` read `listLimit` before `readKind()`
consumed header bytes via `willRead()`, leaving a stale (too-large)
limit that masked `ErrElemTooLarge` checks
- Fix: re-read `listLimit` after `readKind()` so the size check uses the
correct post-consumption remaining bytes

## Test plan

- [x] `go test -run=FuzzRLP/29859ba08ac1b7a2 ./execution/types/` passes
- [x] `go test -count=3 -race ./execution/types/...` passes all 3 runs
- [x] `go build ./...` compiles without errors
- [x] No test files modified

Co-authored-by: erigon-copilot[bot] <erigon-copilot[bot]@users.noreply.github.com>
Co-authored-by: Giulio Rebuffo <giulio.rebuffo@gmail.com>
## Summary

Fixes the data column sidecar miss path so expected misses no longer
produce noisy download logs, while keeping the wire behavior compatible
with peers that signal an empty multi-chunk response by closing without
a response code.

Closes #21670

## Details

- Return structured req/resp `resource unavailable` responses for
by-root misses and valid pre-Fulu by-root requests, while validating
malformed pre-Fulu by-root requests first so they return `invalid
request`.
- Restore by-range empty-success responses to a zero-byte close,
including zero-count, all-pre-Fulu, all-future, and missing-sidecar
ranges.
- In the httpreqresp client bridge, synthesize success code 0 with an
empty body when a negotiated multi-chunk protocol returns `io.EOF`
before any response-code byte. Single-chunk EOFs, partial reads, and
stream errors still surface as HTTP 400.
- Keep handler `responseErr` propagation for real storage/write errors.
Across the Sentinel HTTP/gRPC boundary those transport failures can
still be flattened by gRPC, while structured peer response codes are
surfaced as typed `PeerResponseError` values.
- Bound peer error-message decoding to a 10-byte varint prefix and a
256-byte decoded message.
- Downgrade expected data column sidecar misses to trace while keeping
other download errors at debug.

## Validation

- `go test ./cl/sentinel/httpreqresp ./cl/sentinel/service
./cl/sentinel/handlers ./cl/das -count=1`
- `make lint`
…21861)

## Problem

`PruneAncientBlocks` calls `rawdb.PruneBlocks(tx, canDeleteTo, 1)` in a
loop up to `limit` times — and `limit` is **10,000** during the initial
sync cycle. Each call:

- opens a fresh `kv.Headers` cursor (one Go-heap cursor allocation per
pruned block), and
- allocates an 8-byte seek key via `hexutil.EncodeTs(1)`.

Inside the loop body, `common.Copy(k)` and `make([]byte, 8)` allocate
**once per pruned block** — tens of millions of allocations over a full
initial sync, independent of call count.

## Change

- **`db/rawdb` (`PruneBlocks`)**: reuse a single `keyBuf` for the four
point-deletes (instead of `common.Copy(k)` per block), hoist `txIDBytes`
above the loop, and replace `EncodeTs(1)` with a local seek buffer. The
loop body now allocates **0 per pruned block**.
- **`snapshotsync/freezeblocks` (`PruneAncientBlocks`)**: batch up to
`pruneBlocksBatch` (1000) blocks per `PruneBlocks`/`PruneHeimdall` call
(one cursor + one seek per batch), with an early `!progressed` break so
it stops as soon as there's nothing left rather than spinning to
`limit`. `timeout` is still checked between batches.

`canDeleteTo` is fixed for the duration of the call, so batching deletes
exactly the same `[blockFrom, min(canDeleteTo, blockFrom+N))` range the
per-block loop did.

## Effect (initial cycle, limit=10000)

- cursor opens + per-call seek/`EncodeTs` allocs: ~10,000 → ~10 per
`PruneAncientBlocks` call
- per-block allocations during pruning: ~2/block → 0/block

## Status

WIP — wiring up an allocation benchmark to quantify the win.
based on: #21777

- `.bt` file format change
- backwards compatible

In PR:
- reduce .Build() RAM usage by building `ef_offsets` eagerly off-heap
and streaming `nodes` straight to the file (no big in-mem structs)
- Add `M` inside file metadata
- Removed `di` from file (node `i` has `di = i*M`, recomputed on read)
- metadata stored in `Footer` of file
- only the `EF` section is 4kb-aligned (for mmap/SIMD-friendly reads);
`nodes` are byte-parsed, so left unaligned; footer is 8-byte aligned
- completely removed `ETL` usage: because now `AddKey()` can write
directly to file, also `Footer` written at the end (when all required
fields calculated).
- but keep 1 leading byte - just for legacy format detection

----- 
I realized that for building `.bt` we don't really need ETL (keys
already always sorted, keys_count we know from decompressor, etc...).
And I realized more - maybe other places in Erigon also can be more
streaming-friendly (can build without ETL - in 1-pass).

To generalize idea we need move "header metadata" to "footer" - then if
metadata storing `keys_count` we can write it at the end:

- Store metadata at `Footer`: wins at write-once by append-only
use-cases.
- Store metadata at `Header`: wins at forward-stream consumers (sockets,
tar to tape), mutable page-addressed files (SQLite, Postgres, InnoDB —
when mutate/extend file Footer will change its offset).

`Footer` style file `.bt`:
```
[ leading_byte:u8 ]                                                      # 1 byte: legacy-format detection
[ nodes: (key_len:u16 | key)* ]                                          # variable length. unaligned (byte-parsed)
[ EF: elias-fano of all key offsets ]                                    # variable length. 4kb-aligned
[ footer: keys_count:u64 | M:u64 | ef_offset:u64 ]                       # 24 bytes. 8-byte-aligned
[ ANCHOR: footer_len:u32 | flags:u16 | format_version:u16 | magic:u64 ]  # 16 bytes. Fixed length
```

Build such files will be much more streaming-style-friendly. Don't need
`etl` all incoming keys only to calculate `keys_count` (like we do now
in `.bt`).

On file open:
- Validate the `magic` first (right format + not truncated) ->
fail-fast. Not u16 - because probability of collision is high.
- `format_version` is next to the `magic` - then it will allow change
ANCHOR format/len in the future.

(yes, exactly in our case: EF inside .bt requires to know key_count in
advance - but we already have it - and maybe in future we can change
it).

Also `Footer` allowing easy to add `checksum` to metadata fields in
future, etc...
…ecution unwind (#21848)

Forward-port of #21847 to `main` (which carries #21825).

Same change as #21847: in `UnwindExecutionStage`, prune the in-RAM
overlay to the
**committed** boundary (`Min(s.BlockNumber+1)`) on the no-op-disk-unwind
path
instead of `Min(u.UnwindPoint+1)`, and restructure into an explicit
`disk-unwind` / `overlay-only` `if`/`else` with a shared tail. See
#21847 for the
full rationale (yperbasis #1 boundary, AskAlexSharov structure,
yperbasis #2
state-cache note).

## main-specific adaptation (and a latent bug this surfaced)

`main`'s disk-unwind path calls `doms.ResetPendingUpdates()` (discards
deferred
commitment updates from the failed execution — `release/3.4` has no such
call).
The pre-refactor `u.UnwindPoint >= s.BlockNumber` **early return skipped
it** —
exactly the "the early return skips important biz-logic" failure mode
@AskAlexSharov flagged. The restructure **hoists `ResetPendingUpdates()`
ahead of
the branch so it runs on both paths**, so a block that failed its gas
check
mid-batch no longer leaks deferred commitment updates into the
re-execution.

The test file is `main`-only here (the release/3.4
`TestFindExecutedDiffsetAtHeight…`
test was never forward-ported), and `common.Address`/`common.Hash` are
raw arrays
on `main`, so the test slices them with `[:]`.

## Verification on main + this patch

- red→green: with the merged `Min(u.UnwindPoint+1)` boundary the test
fails (the
`(committed, unwindPoint]` write `0x1122` survives); with this PR it
passes.
- `go build ./execution/stagedsync/...` — clean
- `go test ./execution/stagedsync/ -run
TestUnwindExecutionStage_PrunesUncommittedOverlayWrite` — pass
- `gofmt` / `golangci-lint run ./execution/stagedsync/` — 0 issues

Relates to #21681.

Co-authored-by: Claude <noreply@anthropic.com>

---------

Co-authored-by: JkLondon <me@ilyamikheev.com>
Co-authored-by: Claude <noreply@anthropic.com>
…ts in execution witness (#21854)

Fixes #21810.

`debug_executionWitness` omitted the exclusion proof for a storage slot
read during a block that resolves to zero, when the slot's path diverges
at a folded extension. The diverging extension was emitted with a bare
hash child, so a strict stateless verifier cannot descend it to prove
the slot absent. Erigon's own stateless verifier accepts such a witness,
which is why this went unnoticed.

## Fix
Materialize the branch behind the extension in `toWitnessTrie`
(`witnessMaterializeBranch`). The branch hashes to the same value, so
the witness root is unchanged — the witness just carries the
previously-missing branch node, guarded by a `subRoot == cell.hash`
check.

## Test

`Test_WitnessTrie_GenerateWitness/AbsentStorageSlotDivergingAtFoldedExtension`
builds a storage trie with an extension and witnesses an absent slot
diverging inside it. A strict sparse-trie walk over the witness rejects
it before the fix and accepts it after.

## Validation
Verified on mainnet blocks 25302399 (the one in the issue),
25302398/400, 25310000, 25315000, 25320000: the witness now passes
external stateless validation. Witness node count for 25302399: 10789 ->
10806; commitment root unchanged.
…ng trie root regression (#21867)

**[SharovBot]**

## Summary

Reverts the `listLimit` re-read introduced in #21839 (cecf3ad) which
caused `invalid block: wrong trie root` regressions across all CI
stage-exec-test jobs.

## Root cause of regression

After `readKind()` calls `willRead()` to consume the 1-byte type header,
`listLimit` drops by 1. The re-read then compares the declared element
`s.size` against `(original - 1 byte)`, causing the `ErrElemTooLarge`
guard to fire spuriously for elements that exactly fill the remaining
list space. This aborted block execution with corrupted state and
produced wrong trie roots.

## Failing CI jobs (commit cecf3ad)

- `stage-exec-test (from-0, serial)` — `invalid block: wrong trie root,
block=513814`
- `stage-exec-test (from-0, parallel)` — `invalid block: wrong trie
root, block=263641`
- `stage-exec-test (resume-nonchaintip, serial)` — `invalid block: wrong
trie root, block=25314648`
- `RPC Integration Tests - nethermind` — `eth_call` diff mismatches on
all test cases

## Fix

Revert `decode.go` to read `listLimit` before `readKind()` (original
behaviour). The pre-`readKind()` read is correct: the `ErrElemTooLarge`
guard compares the *declared* element size against the *available* space
before any consumption.

The fuzz corpus entry (`29859ba08ac1b7a2`) is kept so the edge case is
preserved for future investigation.

## Test plan

- [x] `go build ./...` passes
- [x] `go test -count=3 -race ./execution/rlp/... ./execution/types/...`
passes all 3 runs
- [x] No test files modified

Closes regression introduced by #21839.

Co-authored-by: erigon-copilot[bot] <erigon-copilot[bot]@users.noreply.github.com>
Co-authored-by: Giulio Rebuffo <giulio.rebuffo@gmail.com>
… fence tags (#21841)

Weekly docs maintenance (w25) — prose-quality pass on `main`. Doc-only,
no code touched. Companion to #21840 (same fixes on `release/3.4`, per
the dual-commit rule), plus two `main`-only fence fixes for pages that
don't exist on `release/3.4`.

### Changes
- **docker-compose.md** — "shared between **erigon** and RPC Daemon" →
**Erigon** (mid-sentence proper noun).
- **tls-authentication.md**, **installation/index.mdx** — "RPC daemon" →
**RPC Daemon** (prose form of the module name).
- **how-to-run-a-polygon-node.md** — fix a mashed flag value:
`--bor.heimdall=https://heimdall-api.polygon.technologyspecifies` →
`…polygon.technology`.
- **ethereum-with-an-external-cl.mdx** — expand **EL** → **Execution
Layer (EL)** on first use.
- **architecture.md**, **database.md** (`main`-only) — tag untyped code
fences as ` ```text ` (Block-STM pipeline diagram, datadir tree).
- Regenerated `llms.txt` / `llms-full.txt` bundles to match.

### Verification
- `npm run build` (Docusaurus, `onBrokenLinks: 'throw'`) — green.
- `generate-llms.py --check` — OK (4 bundles match, 74 pages).
- Editorial-artifact scan — clean.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Bloxster <gianni.morselli@erigon.tech>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: yperbasis <andrey.ashikhmin@gmail.com>
… (#21871)

Cherry-pick of #21830 to `main`.

Switch `RecSplit.bucketCollector` from `LargeSortableBuffers` to
`SmallSortableBuffers` to reduce peak memory usage during `.bt` index
builds.

The original squash diff also added an unused `newIndexFromMemory`
helper that depends on the `Index.readers` `sync.Pool` field present
only on the `performance` branch; it is omitted here so the change
builds on `main`.
…y, no reflection) (#21858)

On Bloatnet i noticed "GC stop-the-world spike"

The win is from **not using `[]any` casting and not using reflection**:

- the old `Hash()` built a `[]any{...}` of the tx fields (slice alloc +
boxing each `uint64`/pointer into an interface), then
- handed it to the **reflection-based** `rlp.Encode`, which walks the
values dynamically and allocates as it goes.

### Applied to

`LegacyTx`, `AccessListTx`, `DynamicFeeTransaction`, `BlobTx`,
`SetCodeTransaction`, `AccountAbstractionTransaction` — every tx type
that already has an `encodePayload`.

### Results (`BenchmarkTxHash`, M4 Max — allocs/op)

| type | before | after |
|---|---|---|
| legacy | 8 | 1 |
| accesslist | 9 | 1 |
| dynamicfee | 9 | 1 |
| blob | 10 | 1 |
| setcode | 22 | 1 |
| aa | 13 | 1 |

### Two behavioral notes

- **`BlobTx.encodePayload`** previously dereferenced `To`
unconditionally (it had a non-nil-`To` precondition enforced by the
`ErrNilToFieldTx` guards in `MarshalBinary`/`EncodeRLP`). It now encodes
`To` via `EncodeOptionalAddress` — **byte-identical** for the
always-present `To` of a valid blob tx, and consistent with its own
`payloadSize` (which already sizes `To` optionally).
`MarshalBinary`/`EncodeRLP` keep their `ErrNilToFieldTx` guards, so
their external contract is unchanged.
- **`AccountAbstractionTransaction`'s hash value changes.** Its old
`[]any` path encoded `SenderAddress` (`accounts.Address` =
`unique.Handle`, which has unexported fields) through reflection, which
silently **dropped the sender from the hash**. `Hash()` now goes through
`encodePayload` and equals `keccak256` of the canonical `MarshalBinary`
bytes. AA (RIP-7560) is not live, so this is a latent-bug fix rather
than a consensus change.
…e GLOAS head (#21694)

## Summary

Compute the GLOAS fork-choice head (`getHeadGloas`) from the incremental
`indexedWeightStore` instead of the full-scan `weightStore`. The index
is
maintained on every GLOAS attestation (see #21698) but was not
previously read
for head selection. Because that maintenance is GLOAS-gated, the index
is cold
at the pre-GLOAS→GLOAS transition; `getHeadGloas` seeds it from
`latestMessages`
on first use so it is not missing votes cast before activation.

**Scoped to GLOAS only — pre-GLOAS fork choice is unchanged.**

## Changes

- `getHeadGloas` scores via `headWeightStore(cs)` (the index), fetching
the
justified checkpoint state before acquiring `f.mu` (a nil state degrades
to
zero attestation weight, as before — same pattern as pre-GLOAS
`getHead`).
- `indexedWeightStore` is now **structural**: it stores only
`(validator, slot, payloadPresent)` per target root and reads balance +
active/slashed status **fresh** from the per-query justified checkpoint
state
in `GetAttestationScore`, instead of caching balances that go stale when
the
checkpoint advances. `IndexVote` no longer does a per-vote
`getCheckpointState`
  lookup.
- `RemoveVote` is now **allocation-free** (in-place compaction). It runs
per
attestation on the GLOAS vote path, so the previous fresh-slice-per-call
was a
CPU/GC hotspot under the fork-choice lock on high-validator-count
networks.
- `onNewFinalized` drops indexed votes for finalized-away roots.
- On the first `getHeadGloas`, `headWeightStore` seeds the index once
from the
full `latestMessages` snapshot (`seedFromLatestMessages`,
clear-then-rebuild).
Index maintenance only begins at GLOAS activation, so without the seed
the head
  would undercount attestation weight at the transition until validators
  re-attest.
- Removed dead index API: the unused `GetIndexedWeightStore` accessor,
`GetWeightStore` (orphaned once `getHeadGloas` switched to
`headWeightStore`;
`NewWeightStore` stays for `isHeadWeak`), `Invalidate` (cleared votes
but left
`seeded` set, so any future caller would silently undercount weights),
and the
  write-only `version` counter.

## Testing

- `TestIndexedWeightStoreMatchesFullScan`: starts from a **cold** index,
has
`headWeightStore` seed it, then asserts it returns the same `GetWeight`
/
`GetAttestationScore` as the trusted full-scan `weightStore` for every
node in
  the filtered tree (non-vacuous) — exercising the real seed path.
- `TestSeedFromLatestMessagesIsIdempotent`: the seed mirrors
`latestMessages`
  once and is a no-op thereafter (no double-counting).
- `TestRemoveVoteCompactsInPlace`: removal keeps the other voters and
drops the
  root once empty.
- `TestPruneFinalizedDropsFinalizedAndUnknownRoots`: votes targeting
roots at or
below the finalized slot, or missing from the fork graph, are dropped;
votes
  for live roots above it survive.
- `TestOnNewFinalizedPrunesIndexedVotes`: the `onNewFinalized` wiring
empties the
index once finalization passes every indexed root. Both pruning tests
were
  mutation-checked: they fail against a no-op `pruneFinalized`.
- Existing fork-choice tests and #21698's GLOAS/pre-GLOAS maintenance
tests pass;
  `go test ./cl/phase1/forkchoice/...` and scoped `golangci-lint` clean.

## Follow-up

The indexed store and its transition seed are superseded by the
delta-propagation
rework tracked in #21704 (the canonical proto-array model both Prysm and
Lighthouse ship).

---------

Co-authored-by: noop <noop@noop>
Co-authored-by: kewei <kewei.train@gmail.com>
… input (#21877)

Fixes an OSS-Fuzz report (issue 525088104, target `fuzz_ef32_double`,
crash type Overwrites-const-input).

The EliasFano fuzzers grew the input with `in = append(in, in...)`. The
fuzzing engine passes the input as a read-only buffer (often with spare
capacity), so the first append could write into it — ASAN flags this as
"Overwrites-const-input". Cloning the input with `bytes.Clone` before
growing keeps all appends on our own buffer. Applied to all four
EliasFano fuzzers (ef16/ef32 × single/double), which share the pattern.

**Scope:** fuzz-target only. The EliasFano implementation is unaffected
(it decodes freshly-allocated cumKeys/position slices, not the input),
so there is no runtime/consensus impact and nothing ships in any
released binary.
…21862)

## What
`erigon seg ls` now works even when chaindata is unavailable (e.g.
snapshot-only datadirs, cold storage, post-mortem analysis).

## How
One `doLS` path with graceful degradation — no branching into two code
paths:

- `tryOpenChaindata` replaces panicking `MustOpen()`, returns nil if
chaindata missing/locked
- Block + bor snaps opened directly from filesystem (always works)
- State aggregator opened via new `tryOpenAgg` — skipped with a log
message if unavailable (e.g. no salt files)
- Caplin snaps only opened when chain name is known
- `openAgg` refactored into panic wrapper over `tryOpenAgg` — existing
callers unchanged

## Test
`TestSegLS_NoChaindata` — asserts no panic/error on empty datadir.

## Verified
- `make lint` clean (×2)
- `make erigon integration` builds
- `erigon seg ls --datadir=/tmp/empty` logs degradation messages, exits
0
…run CDN download (#21882)

## Problem

`test-hive-eest`'s `eels` simulators download the fixtures tarball from
the GitHub release CDN **inside the image build** (`consume cache
--input <url>`). With hive's `--docker.nocache`, that download repeats
on **every run** — slow, exposed to transient release-CDN 504s, and
bypassing the hardened `tools/test-fixtures.sh` entirely.

## Change

Feed the fixtures from cache + a hardened fetch, and hand them to the
sim locally:

- **Restore** the EEST tarballs from the base-branch cache warmed by
`cache-warming-eest-fixtures.yml` — **restore-only** (the warmer owns
saving, so no per-ref duplicates).
- **Fallback** on a cache miss: the hardened `tools/test-fixtures.sh`
(5-min exponential backoff).
- **Stage** the matrix tarball into the eels sim build context and pass
`--sim.buildarg fixtures=/fixtures`, so the sim's `consume` reads a
local directory instead of downloading.

The `branch` build-arg (devnet `consume`/execution-specs selection) is
unchanged.

## Hive dependency (now merged)

The local-fixtures eels Dockerfiles landed in **erigontech/hive#2**
(merged into `yperbasis/client-pool`). The hive `ref` is pinned to that
merge commit (`9afe5f15`).

## Validation

End-to-end verified on a real runner via `workflow_dispatch` (run
27152648786): exact-key **cache hit** (no CDN download), staged tarball
picked up by the eels build, and `consume engine` ran the full
`paris+shanghai` shard — **3573 tests, 0 failed**. The
fixtures-directory layout was also confirmed against
`execution-specs`/`consume` locally (it accepts the extracted dir and
recursively builds its index).
#21883)

Bumps
[webpack-dev-server](https://github.com/webpack/webpack-dev-server) from
5.2.4 to 5.2.5.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/webpack/webpack-dev-server/releases">webpack-dev-server's
releases</a>.</em></p>
<blockquote>
<h2>v5.2.5</h2>
<h3>Patch Changes</h3>
<ul>
<li>Skip the HMR WebSocket path when forwarding upgrade requests to
user-defined proxies, so custom proxy WebSocket upgrades are no longer
intercepted by the dev server. (by <a
href="https://github.com/bjohansebas"><code>@​bjohansebas</code></a> in
<a
href="https://redirect.github.com/webpack/webpack-dev-server/pull/5680">#5680</a>)</li>
</ul>
</blockquote>
</details>
<details>
<summary>Changelog</summary>
<p><em>Sourced from <a
href="https://github.com/webpack/webpack-dev-server/blob/main/CHANGELOG.md">webpack-dev-server's
changelog</a>.</em></p>
<blockquote>
<h2>5.2.5</h2>
<h3>Patch Changes</h3>
<ul>
<li>Skip the HMR WebSocket path when forwarding upgrade requests to
user-defined proxies, so custom proxy WebSocket upgrades are no longer
intercepted by the dev server. (by <a
href="https://github.com/bjohansebas"><code>@​bjohansebas</code></a> in
<a
href="https://redirect.github.com/webpack/webpack-dev-server/pull/5680">#5680</a>)</li>
</ul>
<p>All notable changes to this project will be documented in this file.
See <a
href="https://github.com/conventional-changelog/standard-version">standard-version</a>
for commit guidelines.</p>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/webpack/webpack-dev-server/commit/c3ee325819f64ceb77f85dcf727b6b5ede85cbc4"><code>c3ee325</code></a>
chore(release): new release (<a
href="https://redirect.github.com/webpack/webpack-dev-server/issues/5682">#5682</a>)</li>
<li><a
href="https://github.com/webpack/webpack-dev-server/commit/60173be90873b187b41fc2009a4de253732988a1"><code>60173be</code></a>
feat: add changeset validation and release workflow (<a
href="https://redirect.github.com/webpack/webpack-dev-server/issues/5680">#5680</a>)</li>
<li><a
href="https://github.com/webpack/webpack-dev-server/commit/948d5e6089bebcd801dac2cbe3ed4f80b64f117a"><code>948d5e6</code></a>
fix(proxy): match the HMR upgrade path exactly like the ws server (<a
href="https://redirect.github.com/webpack/webpack-dev-server/issues/5678">#5678</a>)</li>
<li><a
href="https://github.com/webpack/webpack-dev-server/commit/93e8996124332a6c94c4d3e0f8e5f2cf95321c67"><code>93e8996</code></a>
fix: skip HMR websocket path when forwarding upgrades to user-defined
proxies...</li>
<li>See full diff in <a
href="https://github.com/webpack/webpack-dev-server/compare/v5.2.4...v5.2.5">compare
view</a></li>
</ul>
</details>
<details>
<summary>Maintainer changes</summary>
<p>This version was pushed to npm by <a
href="https://www.npmjs.com/~GitHub%20Actions">GitHub Actions</a>, a new
releaser for webpack-dev-server since your current version.</p>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=webpack-dev-server&package-manager=npm_and_yarn&previous-version=5.2.4&new-version=5.2.5)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/erigontech/erigon/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [undici](https://github.com/nodejs/undici) from 7.25.0 to 7.28.0.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/nodejs/undici/releases">undici's
releases</a>.</em></p>
<blockquote>
<h2>v7.28.0</h2>
<h1>⚠️ Security Release</h1>
<p>This release line addresses <strong>7 security advisories</strong>,
all shipped in <strong>v7.28.0</strong>.</p>
<blockquote>
<p><strong>Action required:</strong> Upgrade to <strong>undici
7.28.0</strong> or later.</p>
<pre lang="sh"><code>npm install undici@^7.28.0
</code></pre>
</blockquote>
<p>The v7 line is <strong>not</strong> affected by GHSA-38rv-x7px-6hhq
(CVE-2026-9675), which is
an 8.x-only regression.</p>
<blockquote>
<p><strong>Note on GHSA-hm92-r4w5-c3mj:</strong> this fix shipped in
<strong>v7.28.0</strong>, not the
earlier 7.2x line — the vulnerable single-pool code was still present
through
<code>v7.27.2</code>. The per-origin pool fix is
<a
href="https://github.com/nodejs/undici/commit/3805b8f8"><code>3805b8f8</code></a>
(<a
href="https://redirect.github.com/nodejs/undici/pull/5041">#5041</a>).</p>
</blockquote>
<h2>Summary</h2>
<table>
<thead>
<tr>
<th>Advisory</th>
<th>CVE</th>
<th>Severity (CVSS)</th>
<th>Fixed in</th>
<th>Fix commit</th>
</tr>
</thead>
<tbody>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-vxpw-j846-p89q">GHSA-vxpw-j846-p89q</a></td>
<td>CVE-2026-12151</td>
<td>High (7.5)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/8cb10f98"><code>8cb10f98</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-vmh5-mc38-953g">GHSA-vmh5-mc38-953g</a></td>
<td>CVE-2026-9697</td>
<td>High (7.4)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/04201f89"><code>04201f89</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-hm92-r4w5-c3mj">GHSA-hm92-r4w5-c3mj</a></td>
<td>CVE-2026-6734</td>
<td>High (7.5)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/3805b8f8"><code>3805b8f8</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-pr7r-676h-xcf6">GHSA-pr7r-676h-xcf6</a></td>
<td>CVE-2026-9678</td>
<td>Moderate (5.9)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/85a24055"><code>85a24055</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-p88m-4jfj-68fv">GHSA-p88m-4jfj-68fv</a></td>
<td>CVE-2026-9679</td>
<td>Moderate (5.9)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/d0574cc4"><code>d0574cc4</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-g8m3-5g58-fq7m">GHSA-g8m3-5g58-fq7m</a></td>
<td>CVE-2026-11525</td>
<td>Low (3.7)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/d0574cc4"><code>d0574cc4</code></a></td>
</tr>
<tr>
<td><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-35p6-xmwp-9g52">GHSA-35p6-xmwp-9g52</a></td>
<td>CVE-2026-6733</td>
<td>Low (3.7)</td>
<td>7.28.0</td>
<td><a
href="https://github.com/nodejs/undici/commit/ea8930cf"><code>ea8930cf</code></a></td>
</tr>
</tbody>
</table>
<hr />
<h2>High severity</h2>
<h3>WebSocket DoS via fragment count bypass — CVE-2026-12151</h3>
<p><strong><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-vxpw-j846-p89q">GHSA-vxpw-j846-p89q</a></strong>
· CWE-400, CWE-770
<strong>Fix:</strong> <a
href="https://github.com/nodejs/undici/commit/8cb10f98"><code>8cb10f98</code></a>
<em>websocket: limit the number of fragments in a message</em> (part of
backport <a
href="https://github.com/nodejs/undici/commit/a027a4a0"><code>a027a4a0</code></a>
<em>Backport WebSocket maxPayloadSize fixes to v7.x</em>, <a
href="https://redirect.github.com/nodejs/undici/pull/5423">#5423</a>)</p>
<p>A malicious WebSocket server can stream a large number of small or
empty
continuation frames. Undici enforced a limit on cumulative payload size
but did
not limit the <em>number</em> of fragments per message, leading to
unbounded memory
growth and denial of service.</p>
<ul>
<li><strong>Affected:</strong> applications using <code>new
WebSocket(...)</code> or <code>WebSocketStream</code>
against untrusted endpoints.</li>
<li><strong>Workaround:</strong> none — upgrade is required.</li>
</ul>
<h3>TLS certificate validation bypass in SOCKS5 ProxyAgent —
CVE-2026-9697</h3>
<p><strong><a
href="https://github.com/nodejs/undici/security/advisories/GHSA-vmh5-mc38-953g">GHSA-vmh5-mc38-953g</a></strong>
· CWE-295</p>
<!-- raw HTML omitted -->
</blockquote>
<p>... (truncated)</p>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/nodejs/undici/commit/f9eba0ad9134e1c0977848476bba9d49734696e4"><code>f9eba0a</code></a>
Bumped v7.28.0 (<a
href="https://redirect.github.com/nodejs/undici/issues/5430">#5430</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/a027a4a04c6c055877d1abaf5f60ee4917e7e01f"><code>a027a4a</code></a>
Backport WebSocket maxPayloadSize fixes to v7.x (<a
href="https://redirect.github.com/nodejs/undici/issues/5423">#5423</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/8cb10f983eb6005dd53f3744d95d3b6d7dbcee0f"><code>8cb10f9</code></a>
websocket: limit the number of fragments in a message</li>
<li><a
href="https://github.com/nodejs/undici/commit/04201f8947041f0f4f2ac865dbdb1677e46a8844"><code>04201f8</code></a>
fix: honor requestTls when proxy is SOCKS5</li>
<li><a
href="https://github.com/nodejs/undici/commit/fcd642ff613ea9030dec87cf622e68d4b1ae9847"><code>fcd642f</code></a>
fix(socks5): preserve dispatch backpressure return value (<a
href="https://redirect.github.com/nodejs/undici/issues/5166">#5166</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/bc98c97906abf26fa1e959b2f6111b53ade0e18f"><code>bc98c97</code></a>
fix(socks5): use configured connector in Socks5ProxyAgent (<a
href="https://redirect.github.com/nodejs/undici/issues/5168">#5168</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/9e1c74372a2b27cacd92d27c13a83a6d84f10e0e"><code>9e1c743</code></a>
fix(socks5): encode embedded IPv4 tails in IPv6 literals correctly (<a
href="https://redirect.github.com/nodejs/undici/issues/5099">#5099</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/376c8be27cb40cc17ccaad6b6ebb317fa7148d65"><code>376c8be</code></a>
fix(socks5): enforce authenticated state before CONNECT (<a
href="https://redirect.github.com/nodejs/undici/issues/5097">#5097</a>)</li>
<li><a
href="https://github.com/nodejs/undici/commit/3805b8f8518882991044048c256e005dc3c10a85"><code>3805b8f</code></a>
fix(socks5-proxy-agent): use per-origin pools to prevent cross-origin
routing...</li>
<li><a
href="https://github.com/nodejs/undici/commit/85a240551c9feb8b8a0ecc56c84b2b3015add8a9"><code>85a2405</code></a>
fix(cache): trim qualified field names</li>
<li>Additional commits viewable in <a
href="https://github.com/nodejs/undici/compare/v7.25.0...v7.28.0">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=undici&package-manager=npm_and_yarn&previous-version=7.25.0&new-version=7.28.0)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/erigontech/erigon/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…sTrie (#21892)

`debug_executionWitness` blinded the surviving sibling of a collapsing
storage branch on a zero-value write (#21869). On a slot delete from a
two-child branch, `toWitnessTrie` emitted the sibling extension but left
the branch it points to a bare `HashNode`. reth's `stateless` verifier
can't re-form the collapsed branch from a blinded node, so it returns
`BlindedNode` and the post-state root diverges.

Repro: mainnet block 25335936, zero-write to slot `0x43cb…`, sibling
`0x43cd` blinded → `SparseTrieError(BlindedNode(Nibbles(0x43cd)))`.

Fix: in exclusion-proof (legacy) mode, materialize that branch via
`witnessMaterializeBranch` with a subtrie-root check instead of a
`HashNode`. Same hash, witness root unchanged; canonical mode stays
minimal. The absent-slot path and this one now share
`witnessMaterializeBranchChild`.

Test:
`Test_WitnessTrie_GenerateWitness/CollapseSiblingExtensionMustBeMaterialized`
— two-child storage branch, sibling is an extension over a sub-branch,
delete the other child, assert the sub-branch is a `FullNode` not a
`HashNode`. End-to-end: block 25335936 witness passes reth `stateless`
(VALID; state nodes 16395 → 16396).

Fixes #21869
Bumps [github.com/go-chi/chi/v5](https://github.com/go-chi/chi) from
5.2.3 to 5.2.4.
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/go-chi/chi/commit/6eb35881c0e438ffb663ddbad3a61babaa5e5d8a"><code>6eb3588</code></a>
middleware: harden RedirectSlashes handler (<a
href="https://redirect.github.com/go-chi/chi/issues/1044">#1044</a>)</li>
<li><a
href="https://github.com/go-chi/chi/commit/de0d16e6d23092aeef0b6e78f146799369160651"><code>de0d16e</code></a>
Update comment about min Go version (<a
href="https://redirect.github.com/go-chi/chi/issues/1023">#1023</a>)</li>
<li><a
href="https://github.com/go-chi/chi/commit/9fb4a15daa6d4ccd5e7286c1227d58872f89f4cb"><code>9fb4a15</code></a>
update reverseMethodMap in RegisterMethod (<a
href="https://redirect.github.com/go-chi/chi/issues/1022">#1022</a>)</li>
<li><a
href="https://github.com/go-chi/chi/commit/51c977c2da872d16d05531d5bc49ccd027599ce2"><code>51c977c</code></a>
Refactor to use atomic type (<a
href="https://redirect.github.com/go-chi/chi/issues/1019">#1019</a>)</li>
<li><a
href="https://github.com/go-chi/chi/commit/563ab118626b47810852303c3a60c2106a6bc23c"><code>563ab11</code></a>
Refactor graceful shutdown example (<a
href="https://redirect.github.com/go-chi/chi/issues/994">#994</a>)</li>
<li><a
href="https://github.com/go-chi/chi/commit/a52c582b532cd261dbedc4c811d809d6e024c1ff"><code>a52c582</code></a>
Bump minimum Go and use new features (<a
href="https://redirect.github.com/go-chi/chi/issues/1017">#1017</a>)</li>
<li>See full diff in <a
href="https://github.com/go-chi/chi/compare/v5.2.3...v5.2.4">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=github.com/go-chi/chi/v5&package-manager=go_modules&previous-version=5.2.3&new-version=5.2.4)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/erigontech/erigon/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.10 to
3.4.11.
<details>
<summary>Release notes</summary>
<p><em>Sourced from <a
href="https://github.com/cure53/DOMPurify/releases">dompurify's
releases</a>.</em></p>
<blockquote>
<h2>DOMPurify 3.4.11</h2>
<ul>
<li>Fixed an issue with a leaky config for hooks via
<code>setConfig</code>, thanks <a
href="https://github.com/trace37labs"><code>@​trace37labs</code></a></li>
<li>Bumped vulnerable development dependencies to arrive at plain 0 with
<code>npm audit</code></li>
<li>Updated the <code>osv-scanner</code> suppression list as no
vulnerable dependencies are left for now</li>
<li>Updated up the linting tool-chain and removed now-redundant lint
directives</li>
<li>Updated the documentation is several spots, README, wiki, etc.</li>
<li>Bumped several dependencies where possible</li>
</ul>
</blockquote>
</details>
<details>
<summary>Commits</summary>
<ul>
<li><a
href="https://github.com/cure53/DOMPurify/commit/0cae5187403132f96a6d357649e4b15633fc210a"><code>0cae518</code></a>
release: 3.4.11 (<a
href="https://redirect.github.com/cure53/DOMPurify/issues/1494">#1494</a>)</li>
<li>See full diff in <a
href="https://github.com/cure53/DOMPurify/compare/3.4.10...3.4.11">compare
view</a></li>
</ul>
</details>
<br />


[![Dependabot compatibility
score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=dompurify&package-manager=npm_and_yarn&previous-version=3.4.10&new-version=3.4.11)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)

Dependabot will resolve any conflicts with this PR as long as you don't
alter it yourself. You can also trigger a rebase manually by commenting
`@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits
that have been made to it
- `@dependabot show <dependency name> ignore conditions` will show all
of the ignore conditions of the specified dependency
- `@dependabot ignore this major version` will close this PR and stop
Dependabot creating any more for this major version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop
Dependabot creating any more for this minor version (unless you reopen
the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop
Dependabot creating any more for this dependency (unless you reopen the
PR or upgrade to it yourself)
You can disable automated security fix PRs for this repo from the
[Security Alerts
page](https://github.com/erigontech/erigon/network/alerts).

</details>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
…s) (#21875)

## Problem

The only heap-resident structure of an open `.bt` is `BpsTree`'s
pivot-key cache. On a mainnet node it was ~1.3 GB
(`btindex.decodeListNodes` in heap profiles): ~44M pivots × 32 B/node
(`Node{key []byte (24B), di uint64 (8B)}`), dominated by the storage
domain. The pivot keys already point into mmap (no heap copy) — the cost
was purely the per-node struct.

## Change

Replace `mx []Node` with:
- `keysBlob []byte` — the `[keyLen:u16][key]` records; mmap-backed for
on-disk files (zero heap), heap only in `WarmUp`.
- `nodeOfft []uint64` — byte offset of each pivot record (the only
per-node heap cost).
- `nodeStride uint64` — `di` is derived as `nodeDi(i) = i*nodeStride`,
not stored.

Net: **32 B → 8 B per node (~4×)**, i.e. ~1.3 GB → ~340 MB. No on-disk
format change — `decodeNodes`/`decodeListNodesV0` now return offsets
into the existing mmap'd nodes section instead of materializing structs.

`uint64` (not `uint32`) offsets: a single `.bt`'s nodes section is
bounded by its file size, and the largest storage file is already ~3.5
GB — near the uint32 4 GB ceiling and growing.

`di` is uniformly `i*M` for all real files (footer and released-3.4
legacy). For legacy v0 files the stride is recovered from the on-disk
`di` so a file opened with a different `M` than it was written with
stays correct.

## Notes

- Lookup cost stays flat: the old code already read pivot key bytes from
mmap; the offset path adds only a 2-byte length read on the same cache
line, and the offsets array is more cache-friendly. `bs`/`Get`
benchmarks: 0 allocs, timings unchanged.
- Structured to be range-friendly for a future PrefixIndex (#20180)
rebase: buckets can hold index ranges into `nodeOfft` with derived `di`.

Result: 
Bloatnet: `1.18G -> 0.028G`

---------

Co-authored-by: moskud <sudeepdino008@gmail.com>
Cherry-pick of #21889 to main.

- The 3.4.4 release-notes block is inserted between the in-development
3.5.0 section and the released 3.4.3 section (main already carries the
3.4.3 date, so the change is purely additive).
- The 3.5.0 "Full Changelog" range now bases on `v3.4.4` instead of
`v3.4.3`, since 3.4.4 is the release immediately preceding 3.5.0.
## What

`checkErrListEnd` (used by the EIP-7928 Block Access List decoder and
block-body decoding) detected end-of-list with `errors.Is(err,
rlp.EOL)`. A nested BAL decoder returns a *wrapped* `EOL` on malformed
input — e.g. an account list with no Address (`AccountChanges.DecodeRLP`
→ `"read Address: %w"` wrapping `rlp.EOL`). `errors.Is` matched the
wrapped EOL and treated it as a clean end-of-list, so the **production**
decoder `DecodeBlockAccessListBytes` **silently truncated** a malformed
BAL to empty instead of erroring: `0xc1c0` (a list with one empty
account) decoded to an empty BAL with no error on `main`.

Fix: match the bare sentinel (`err == rlp.EOL`) so a wrapped EOL
propagates as a real error.

## Severity (corrected from the original description)

This is a **real correctness fix, not no-op hardening** — thanks
@yperbasis for catching this. `DecodeBlockAccessListBytes` is the
production decoder, called from `NewPayload` (`engine_server.go:374`),
where the header commitment is `crypto.HashData` over the **raw** BAL
bytes (`:388`) — so the decoder is the only gate. Once Glamsterdam
activates, silently truncating malformed input would be an accept/reject
divergence vs a strict client. **No mainnet impact today** (BALs are
pre-mainnet, Glamsterdam devnets only). Same spirit as #21818 ("reject
malformed RLP instead of dropping elements").

The rlp reflection slice decoder already used exact `== EOL` and is
unchanged; block-body callers return a bare EOL at genuine end-of-list,
so the change is scoped to the only vulnerable site.

## Test

`TestBlockAccessListRejectsAddresslessAccount` now goes through
`DecodeBlockAccessListBytes` (the production path) so it actually
exercises `checkErrListEnd` — verified red→green (FAIL on `main` without
the fix, PASS with it). Full `execution/types` suite green; `make lint`
clean.

Co-authored-by: Andrew Ashikhmin <34320705+yperbasis@users.noreply.github.com>
…d build semaphore (#21526)

## Problem

- block retirement holds snapshotBuild semaphore (`snBuildAllowed`,
default size 1) across both collation and merge.
- this semaphore is also shared by state aggregator.
- when a large block merge runs (100k blocks), it blocks state
collation, causing steps_in_db to accumulate (specially during catchup
phase).
- depends on #21545

Observed on a minimal node: an **8.2 GB / ~19-minute** `025100→025200`
transactions merge held the semaphore the whole time, starving state
collation — `stepsInDB` climbed to **3.82** (chaindata bloats while
collation waits).

## Change

- Release the semaphore after the collate phase and run the merge in its
**own goroutine, off the semaphore** — mirroring how the aggregator runs
`MergeLoop`.
- The fast dump stays serialized against state-snapshot building
(preserving the I/O-throttle intent); the slow merge no longer starves
state collation.

## Effect

With the same 8.2 GB block-tx merge in flight, state collation now
proceeds concurrently — `stepsInDB` stays **< 2** (was 3.82). The CLI
`retire` path calls `MergeBlocks` explicitly so its behavior is
unchanged.

## Tests

- `TestBlockMergeRunsWithoutSemaphore` — merge completes while the
semaphore is fully held (regression-guards re-acquisition).
- `TestRetireBlocksInBackgroundReleasesSemaphore` — background retire
releases the semaphore, no leak.

Draft — full `make lint` + integration verification pending.

---------

Co-authored-by: Sudeep Kumar <sudeep.kumar@erigon.tech>
domiwei and others added 16 commits June 24, 2026 10:05
## Summary
- Fix data race between `GetHead` (read) and `onTickPerSlot` (write) on
`ForkChoiceStore.headHash`/`headSlot`
- The fast-path cache hit in `GetHead` was reading struct fields
**after** releasing `RLock`, racing with `onTickPerSlot` clearing
`headHash` under the write lock
- Copy `headHash` and `headSlot` into locals while `RLock` is still
held, return the locals after unlock

Fixes #21936

## Test plan
- [x] `go test -race ./cl/phase1/forkchoice/...` — pass
- [x] `go build -race ./cl/...` — pass
- [x] `make lint` — 0 issues
…21981)

Fixes a storage-state corruption left behind by `DomainRoTx.unwind` when
an unwound range crosses a domain-step boundary. Found via the
StateChurn unwind/reorg integration tests being added in #21973 (part of
#21860).

## Root cause

When a key is modified at **several domain steps inside the unwound
range**, the merged diffset carries one entry per step. `unwind` wrote a
restore `Put` for **every** step's diff to the same `unwindStep`. In the
**DupSort** values table those become *separate* dup entries under the
key — e.g. `^step+""` and `^step+\x01` — and `getLatestFromDb` returns
the **smallest** dup, which is often an empty tombstone from a *higher*
step rather than the value as of the unwind target.

Only the **lowest** in-range step's value is `value@txNumUnwindTo`; the
higher steps' values are intermediate states *inside* the range. The fix
restores once, on the last (lowest-step) diff per key (diffs are sorted
by key then descending step). The `LargeValues` path was already correct
— MDBX `Put` overwrites at the same key, so last-wins already yields the
lowest step — and is left untouched.

## Why it was hidden

At the production step size a normal unwind (≤ `MaxReorgDepth` blocks)
stays within a single step, so each key has at most one in-range diff
and the bug never triggers. It surfaces when the unwound range **crosses
a step boundary**:

- small step sizes (as the regression test forces continuously), or
- a reorg landing just past a freshly-filed step.

Symptom: after such an unwind the committed state is internally
inconsistent (a later read returns a stale/empty value for a key that
was modified across the boundary). This matches the
unwind/reorg-regression history behind #21860.

## The fix

```go
// ...only the lowest step's value is the value at txNumUnwindTo. Restore once,
// on the last (lowest-step) diff — else the DupSort table keeps several dups at
// unwindStep and getLatestFromDb returns the smallest.
lastForKey := i+1 == len(domainDiffs) || domainDiffs[i+1].Key[:len(domainDiffs[i+1].Key)-8] != keyStr[:len(keyStr)-8]
if value != nil && lastForKey {
    valsCursor.Put(fullKey, append(unwindStepBytes, value...))
}
```

## Regression test

`TestDomain_MultiStepUnwindMatchesGroundTruth` (deterministic, ~0.4s):
mirrors StateChurn at the domain level (a dense sum key + sparse ring
keys, values in {0,1,2} with 0 = delete), files+prunes early steps,
unwinds a multi-step range above the file boundary, and asserts every
key equals the tracked ground truth and that the ring values still sum
to the sum key. **Red before the fix, green after.**

## Validation

- Regression test: red → green.
- Existing `db/state` domain/aggregator/unwind/collate/prune suite:
pass, no regression.
- Full `make lint`: clean.
## Problem

`BlockAssembler` builds the block access list (BAL) by accumulating each
transaction's versioned IO. Done with the general pairwise
`VersionedIO.Merge`, every transaction allocates a fresh `VersionedIO`
and copies the **entire** accumulator, so accumulating a block is
**O(n²)** in time and allocations. For value-transfer-heavy blocks
(cheap txs, many per block) this dominates block-build time.

## Approach

Fold each transaction's recorded reads, writes and accesses **directly
into the accumulator at its tx index**, via
`IntraBlockState.MergeTxIOInto`:

- No intermediate per-tx `VersionedIO` is materialized — the assembler
no longer builds a throwaway one per transaction.
- Each fold touches only that tx's slot (O(1) amortized), so building a
block's BAL is **O(total entries)** rather than O(n²).
- Folds **merge** into the slot rather than overwrite it, so system-call
phases that share a block-level index accumulate correctly.
- The three per-tx slices (`inputs`/`outputs`/`accessed`) grow in
lockstep to a common length, so the accumulator stays rectangular
regardless of fold order.
- The assembler's `recordTxIO`/`clearTxIO` helpers drop their
now-redundant `*VersionedIO` parameter and gate on `ba.HasBAL()`
directly.

## Impact

Building a block full of ether transfers on a ~2 TB mainnet-shadow
("bloatnet") state:

| txs/block | before | after |
|-----------|--------|-------|
| 4000      | ~6.9 s | ~0.8 s |
| 8000      | ~28 s  | ~1.0 s |

Build time goes from O(n²) to ~linear; the per-transaction accumulator
copy and the throwaway per-tx `VersionedIO` allocation are both gone.
The `newPayload` (import/validation) path computes its BAL separately
and is unaffected.

## Correctness

- `TestVersionedIO_mergeTxEquivalentToMerge` checks the in-place fold
against repeated `Merge` (kept as an independent oracle) both by
whole-BAL hash and channel by channel — reads, writes and accesses
compared at every index, including the begin-system tx at index -1 and
two txs at the same index — plus that the per-tx slices stay equal
length. The per-channel checks are needed because a non-system access
(and a balance read) contribute no BAL field, so an
overwrite-instead-of-merge bug would otherwise still hash-match.
- `execution/state`, `execution/exec`, `engineapi` (BAL / builder /
payload), `execution/stagedsync` BAL, and `execution/vm/runtime` tests
pass; `make lint` clean.
- The BAL embedded in built blocks is byte-for-byte what the prior
`Merge`-based accumulation produced, and `newPayload` recomputes the BAL
independently as a cross-check.
## Problem

`codeBitmap` (JUMPDEST analysis) scans bytecode one byte at a time. Only
PUSH opcodes contribute data bits, so runs of non-PUSH bytes have
nothing to mark — yet they're still walked byte-by-byte. This is worst
for the EIP-2780 `test_ether_transfers_onchain_receivers`
**`diff_to_unique_code_jumpdest_contract`** case: each receiver is a
unique 24 KiB contract that is almost entirely JUMPDEST padding, and
(being unique code) the per-codehash analysis cache can't help, so every
transfer rescans 24 KiB.

## Change

Detect whether an 8-byte word contains a PUSH opcode with a **SWAR**
(SIMD-within-a-register) test — `(b & 0xe0) == 0x60` for every byte via
the classic has-zero-byte trick — and skip whole PUSH-free words. A word
containing a PUSH falls back to the canonical byte loop, advanced at
least to the next 8-byte boundary so the peek is amortised over ≥8 bytes
and **PUSH-dense code is not penalised**.

This is the portable-Go analogue of Nethermind's SIMD jumpdest scan.
Erigon's *data*-bitmap representation makes a PUSH-free word a pure skip
(nothing to write), so the fast path is just `pc += 8`.

## Benchmarks (`execution/vm`)

| Case | before | after |
|---|---|---|
| 24 KiB all-JUMPDEST (unique-code receiver) | ~5.9 µs | **~2.5 µs
(2.3×)** |
| no-PUSH 1.2 MB | ~290 µs | **~125 µs (2.3×)** |
| real Solidity contract (PUSH-dense) | ~306 ns | ~298 ns (neutral) |

## Correctness

New `TestCodeBitmapSWAREquivalence` fuzzes the result against the
byte-at-a-time reference across jumpdest-heavy, push-dense and
uniform-random code (20k cases) plus boundary edge cases (lone /
trailing PUSH spanning word boundaries), asserting byte-identical
bitvecs. Existing `TestJumpDestAnalysis` and the full `execution/vm`
suite pass; `make lint` clean.

## Scope

This speeds up the analysis itself; on the end-to-end EIP-2780 import
it's a small CPU fraction of an I/O-bound (commitment) workload, so it
won't move the headline MGas/s much on its own — it's a free,
zero-regression win that's most visible on JUMPDEST-heavy / low-PUSH
code. Opened as draft for review.
)

Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
## Problem

The **Kurtosis GLOAS Tests** jobs (`gloas_gloas-caplin-mixed_test`,
`gloas_gloas-three-cl-mixed_test`) flake / time out. The enclave runs
`ethpandaops/assertoor:master`, which moved onto the
`glamsterdam-devnet-6` branch (EIP-8282, 2026-06-18) and now expects a
**5-list** `ExecutionRequests` (`+ builder_deposits`, `+
builder_exits`). The clients pinned here are devnet-5 and still emit the
**3-list** layout, so assertoor can no longer decode their gloas blocks:

- SSZ (lighthouse-/prysm-mirrored erigon blocks):
`Message.Body.ParentExecutionRequests: unexpected end of SSZ: not enough
data for fixed fields (have 12, needed 20)` — 3 offsets present, 5
expected.
- JSON (caplin): `builder_deposits: missing`.

Only pre-gloas (epoch-0 / Fulu) proposals decode, so
`block-proposal-check` hangs whenever a client pair doesn't draw an
epoch-0 proposer slot, and the job hits the action timeout. On `main`
the single caplin pair is most exposed (lighthouse/prysm pairs sometimes
still decode), so it fails less often than `release/3.5` but the skew is
the same.

## Fix

Pin the kurtosis assertoor image to `master-2231b3e` (2026-06-11) — the
last pre-EIP-8282 `master` build, which is gloas-aware but still 3-list,
matching the devnet-5 clients. No released tag works: `v0.1.2` predates
assertoor's gloas decoding (added 2026-05-19), and assertoor publishes
no `glamsterdam-devnet-5` image.

Mirrors #22009 (the `release/3.5` stopgap).

## Follow-up

Stopgap only. The durable fix is implementing EIP-8282 in erigon and
moving the clients to devnet-6, tracked in #22008 — at which point this
pin reverts to `:master`.
## Problem

The `parallel starting` log line prints a garbage `limit`:

```
[6/8 Execution] parallel starting from=0 to=21715999 limit=18446744073709551615 ...
```

`18446744073709551615` is `math.MaxUint64`: the log computed `limit` as
`startBlockNum+blockLimit-1`, which underflows when `blockLimit == 0`
(the "no per-cycle limit" case — e.g. `integration stage_exec` from
block 0, where `LoopBlockLimit` is 0).

## Fix

Log the effective last block of the cycle:
- `maxBlockNum` when there's no per-cycle limit (`blockLimit == 0`),
- otherwise `min(startBlockNum+blockLimit-1, maxBlockNum)`.

This mirrors how the serial path already computes `toBlockNum`
(`exec3_serial.go`). Log-only change — execution behavior is unaffected.

Now logs:
```
[6/8 Execution] parallel starting from=0 to=21715999 limit=21715999 ...
```
…21920)

## Problem

`Peer.handle` runs in the per-peer read loop for **every inbound
subprotocol message**. With metrics enabled it did, per message:

```go
m := fmt.Sprintf("%s_%s_%d_%#02x", ingressMeterName, proto.Name, proto.Version, msg.Code-proto.offset)
metrics.GetOrCreateGauge(m).SetUint32(msg.meterSize)
metrics.GetOrCreateGauge(m + "_packets").Set(1)
```

That allocated, per message: the `Sprintf` string, the `+ "_packets"`
string, two `&gauge{}` wrappers from `GetOrCreateGauge`, and the
variadic arg boxing — and each `GetOrCreateGauge` also took a mutex +
map lookup. The gauge identity only depends on `(proto.Name,
proto.Version, relativeCode)`, all stable for the lifetime of a
`protoRW`.

## Change

Cache the gauges per `protoRW`, indexed by the protocol-relative message
code. The first message for a code creates and caches the gauges
(preserving the original lazy registration — no zero-valued metrics for
codes that never arrive); subsequent messages just index the slice and
call `Set`.

Behavior is identical: same gauge names, same values. Safe without
locking because `handle` only runs in the single per-peer read-loop
goroutine and each peer owns its own `protoRW` instances (confirmed
under `-race`).

## Numbers

Benchmarked on the actual code path (`benchmem`, Apple M4 Max):

| | allocs/op | bytes/op | ns/op |
|---|---|---|---|
| Before | 5 | 128 B | ~152 ns |
| After (cache warm) | 0 | 0 B | ~3.3 ns |

**5 allocations → 0 per inbound message**, ~46× faster per call.

## Tests

- `TestProtoRWMeterIngress` — verifies the byte/packet gauges still
receive the correct values.
- `TestProtoRWMeterIngressNoAllocs` — asserts `0 allocs/op` after warmup
(regression guard).
…Witness (#22000)

`debug_executionWitness` drops the 20-byte preimage of an accessed
account that exists in the parent state but is emptied and EIP-161
state-cleared in-block. `collectAccessedState` gates `keys[]` on
`accountExists()` (post-state), so a deleted-in-block account is skipped
— yet its leaf is still in the parent-state witness trie. A verifier
treating `keys[]` as the closed accessed set then can't route to it.

Fix: gate on pre- **or** post-state existence (`existedPreBlock`).
Never-existed and created-then-deleted accounts stay excluded; slot
preimages were never gated.

Repro: mainnet 25350549, `0x16fd7629978addaf41c426601176c37977a0faa7`
(0.36 ETH → 0, nonce 0, no code → EIP-161 cleared).

| | keys | addr in keys | state | codes |
|---|------|------|------|------|
| before | 1516 | no | 8700 | 302 |
| after | 1517 | yes | 8700 | 302 |

One preimage added, witness-trie nodes unchanged. reth `stateless`
returns VALID before and after — it routes via the present leaf, so this
is a `keys[]`-completeness bug, not re-exec/root.

Test: `TestCollectAccessedState_KeysIncludeDeletedPreExisting`.

Closes #21979.
…atches (#22002)

## Motivation

`seg reset` removed a `.torrent` whose infohash didn't match the
preverified set (a locally-built file whose bytes differ from the
published copy) but **kept the data file it described** — leaving an
unverified local build on disk with no torrent, for the downloader to
reconcile later. That delete-and-re-fetch handoff is what surfaces as
the recurring `[agg] gap in visible files` warning after a reset (see
#21876).

The relevant `decideRemove` path already had an acknowledged `// TODO:
missing or incorrect torrent delete data file?`.

## Change

| before | after |
|---|---|
| incorrect torrent → remove torrent, **keep** data file | incorrect
torrent → remove torrent **and** the data file it vouched for |

- When reset removes an in-preverified torrent (incorrect/corrupt
infohash), it now also removes the corresponding data file, so the
downloader re-fetches the canonical copy deterministically instead of
racing on stale content.
- A data file is walked before its sibling `.torrent` (`os.ReadDir`
sorts; `x.ef` < `x.ef.torrent`), so the retraction is
order-deterministic.
- Test helper `makeEntries` now writes file contents, so a torrent
fixture with a controllable infohash can exercise the mismatch path.

## Test

`TestIncorrectTorrentRemovesDataFile` builds a torrent with a mismatched
infohash and asserts both the torrent and its data file are removed. Red
before the change (data file survived), green after.

## Scope note

This fixes reset's internal consistency — it no longer retains a file
whose hash it just determined is wrong. It does not change the inherent
transient warning while a missing file is being (re)downloaded; it makes
the re-download deterministic and avoids leaving known-stale data when
the downloader is disabled.
…out (#21999)

## What

`FuzzLongestMatch`'s brute-force validation oracle was `O(len(data) ×
numKeys × keyLen)`. Since `data` grows up to ~8× `len(test)` (each test
chunk appends a key plus its reverse), the coverage-guided fuzzer could
drive the oracle past the 60s OSS-Fuzz timeout.

Fixes OSS-Fuzz issue 527099028 (`erigon:fuzz_patricia_longest_match:
Timeout`).

## Root cause

The timeout is in the **fuzz harness**, not production code. Timing a
320KB-data × 4000-key input:

| Stage | Time |
|---|---|
| `MatchFinder3.FindLongestMatches` (prod) | ~23 ms |
| `ACMatcher.FindLongestMatches` (prod) | ~15 ms |
| **brute-force oracle (test)** | **10.5 s** |

The production matchers are bounded by trie depth (max key length), so
they stay linear even on degenerate all-zeros / prefix-nested input.
Only the test oracle was quadratic.

## Fix

Replace the inner per-key scan with an independent byte-trie walk —
`O(len(data) × maxKeyLen)`, identical greedy longest-match semantics.
It's a separate implementation from the AC/MF3 code under test, so it
remains a valid oracle.

## Verification

- Element-by-element identical output to the old oracle (verified on a
100k-match input).
- ~175× faster on that input: 10.5s → 60ms.
- Seed corpus passes; a 40s `-fuzz` run did 2.4M execs with no
crashes/timeouts and still gained coverage.
- `make lint` clean.

Test-only change, so no TDD cycle applies (per CLAUDE.md: this is a
fuzz-harness performance fix, not a behavior change).
## Motivation

Domain purification — the `compact_domains` (alias `purify_domains`)
integration command — rewrites domain `.kv` files with repeated keys
dropped. A key live at step X may then exist only in an earlier file, so
a file ending at step X no longer holds the state present at step X.

That broken invariant breaks tooling that works off the latest on-disk
state:
- **commitment rebuild**
- **`seg rm-state --latest`** (and working off the resulting state)

## Change

Removes the command and everything exclusive to it:
- `compactDomains` cobra command + its flags (`--out`,
`--replace-in-datadir`, `--build-idx`, `--min-skip-ratio[-l0]`,
`--from`, `--to`)
- helpers `makeCompactableIndexDB`, `makeCompactDomains`
- the `RELEASE_INSTRUCTIONS.md` step that ran it

`read_domains` / `requestDomains` in the same file are untouched. The
`db/integrity` comments about purification stay — already-published
files remain purified, so that handling is still needed.

Net: -409 lines. `make lint` clean; `cmd/integration` builds + vets.
Since #21625, the compressor's cover phase uses only the Aho-Corasick
matcher. The old suffix-array `PatriciaTree` / `MatchFinder1/2/3` path
has no remaining callers — it's dead code (the sole repo-wide importer,
`db/seg/parallel_compress.go`, uses AC exclusively).

## Change
- delete `patricia_tree.go`, `patricia_flat.go`, and
`patricia_flat_test.go`
- relocate the `Match` / `Matches` types (the only part AC still needs)
into `aho_corasick.go`
- trim `patricia_fuzz_test.go` to the AC + brute-force-oracle path; drop
`FuzzPatricia` (it fuzzed the removed `node`)

Net −1069 LOC. `db/seg` and `db/seg/patricia` build and tests pass.

## Closes #21626
The prefix-loss bug (`Insert` drops an existing key when a proper prefix
is inserted later) lived in the now-removed `PatriciaTree.Insert`. The
AC matcher is unaffected — it's validated against a brute-force oracle
in `FuzzLongestMatch`, which this PR keeps.
Reduces gossip subscription log noise at startup.

- Per-topic `[GossipManager] Subscribed to topic` is demoted from INFO
to DEBUG — it previously emitted one INFO line per topic (hundreds,
counting attestation/sync-committee/data-column subnets).
- `registerGossipService` now returns `subscribed`/`expired` counts
instead of logging per service, and `RegisterGossipServices` aggregates
them into a single `INFO [GossipManager] Registered services
subscribed=A expired=B` line.

`expired` counts subnet-fanned topics (attestation, sync committee,
data-column sidecar) that default to a past expiry and are subscribed
later by other modules; `subscribed` counts topics subscribed
immediately.
Brings in the streaming/parallel deep-storage-fold correctness fix #21945
(and its feature line #21709, #21941) plus the rest of bal-devnet-7.

Conflict resolutions:
- db/state/commitment_convert{.go,_blackbox_test.go}: took bal-devnet-7 (#21933, merged form) over warmup's pre-merge draft.
- db/seg/decompress.go: took bal-devnet-7 comment (same change #21927).
- execution/stagedsync/rawdbreset/reset_stages.go: combined warmup's out-of-tx ClearTables with bal-devnet-7's branchCache invalidation.
- execution/stagedsync/stage_execute.go: kept warmup's PruneExecutionStage (haveMore signature, required by callers); bal-devnet-7's #20860 prune-timeout budget-sharing not carried into this function.
- cmd/integration/commands/stages.go: took bal-devnet-7's stageExec (drops removed --no-commit flag), adapted its PruneExecutionStage calls to the haveMore signature.
@AskAlexSharov
AskAlexSharov merged commit f023970 into alex/bal-devnet-7_warmup Jul 1, 2026
11 checks passed
@AskAlexSharov
AskAlexSharov deleted the alex/bal-devnet-7_warmup2 branch July 1, 2026 12:52
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.