v3.2.0
Two changes to the binary, one of which an operator should read before restarting, and a good deal of
tooling around the edges. No security driver this time.
The tip path batches its seals (#1067)
The backfill path has long accumulated 20,000 rows and cut a segment at a block boundary chosen from
the data. The tip path did not batch at all: it sealed whatever had just finalised, which at tip is a
few blocks carrying a few rows, and each became its own Parquet file. The result was bimodal.
horizon-nest reached 11,763 segment files at a median of 6.3 KB, 80% of them under 20 KB, and every
analytical query paid for each one - a file costs roughly 0.14-0.18 ms to open
(docs/bench/segment-layout.md), and a 6 KB Parquet file is mostly footer. Sealed segments are never
compacted, so the cost only ever grew.
The tip path now uses the same cut as backfill. Finalised rows are held until 20,000 have
accumulated, then sealed at the block that carried the buffer past the threshold, so a segment's
identity is a function of the chain and not of when the node happened to report finalized.
Behaviour worth knowing before you upgrade. Finalised rows now sit in the hot store for longer. A
sparse nest that never sees 20,000 new rows holds them in redb rather than producing a stream of tiny
files. They are served from hot ∪ sealed exactly as before, and the finality watermark still advances
across an empty range; what changes is that more rows are resident between seals, against a cursor
that already sits around 1.3-1.5 GB. Existing segments are immutable and untouched. Like the 3.1.0
layout change, this is forward-only.
l1_block_number on the block row (#1116)
The built-in blocks table carries a new column: the L1 block an L2 block was sequenced against.
Arbitrum reports it in the header nuthatch already fetches when [extract] blocks is on, so keeping
it costs nothing extra. It is the only bridge between the two block spaces, and its absence was
load-bearing: the Graph's EpochManager counts epochs in L1 blocks while an indexed log carries an L2
block, so an epoch boundary had to be guessed from the first observed event, which filed several
thousand blocks' worth of activity against the wrong epoch. The allocations nest proved the mapping
first by hand: 5,407 header fetches, binary-searched, to pin 266 epoch boundaries exactly, after which
the fee fields matched the network subgraph 68 of 68 at a pinned block. That table is baked into a
view and reruns the search on every change. Keeping the column on the block row is what lets a view
derive the same boundary in SQL and maintain it as blocks arrive.
0 on a chain that does not report it, following base_fee_per_gas on a pre-London block: the
honest value for "the field did not exist". Segments sealed before this release lack the column and
are NULL-filled by the union-by-name read, so an existing nest keeps serving without a rebuild. An
added column is why this is 3.2.0 rather than 3.1.1.
Tooling
scripts/deploy-nest.sh(#1060): a deploy that verifies the binary it installed, and a check
that reports what is actually running. Written after the 3.1.0 deploy, where a file named for one
version quietly contained another.scripts/lodestar-parity.sh(#1076, #1118): a field-by-field comparison of a nest against the
Graph Network subgraph at a pinned block. It fails when it cannot look, rather than reporting an
agreement it never checked, and a pin at or below the sealed boundary is now accepted rather than
only one exactly on it.- A scheduled workflow that fails now files an issue (#1130). The previous arrangement was that it
stayed red and nobody was told. - The review-signature gate is retired; Jules approval is the required review (#1094), and the
reviewer is now given the commit range rather than a bare diff, with a certainty per finding (#1056).
Recorded, not built
Four RFCs landed as documents: 0045 (DuckDB stays, so the fifth role is DuckDB's), 0047 (nuthatch and
the lakehouse pattern), 0048 (pricing query access behind a gateway) and 0049 (hardening lessons from
an audited pipeline). None of them changes the binary. The 2026 feature freeze applies in full.
Security
Nothing new. The four RUSTSEC advisories in deny.toml's ignore list were all present at 3.1.0, each
with its written reason, and no new advisory has arrived against this lockfile.
No data migration. A binary swap and a restart, with the seal-batching caveat above.