v3.4.0
Sparse tables stop fanning out into one tiny file per seal cut, and Robinhood Chain is built in.
A nest with many tables should restart onto this; a nest with a handful gains nothing from it.
A sparse table no longer gets a Parquet file per cut (#1150)
The seal cut is global and data-determined: every 20,000 rows across all of a nest's tables, at a
block boundary the chain chose. Until now every table with rows at that cut got its own segment
file, however few rows it had. On a nest with two hundred tables that is a file per table per cut,
and most of them are tiny. Measured on the Perpl nest on Monad, a quarter of the way through its
backfill on 3.3.1: 97,494 segment files from 4,319 cuts, a median of 48 rows per file, 85% of
them under a thousand rows, and a 29 MB manifest rewritten every fifteen seconds. Every /sql
query planned over all of them.
What changed. A table with fewer than 1,000 rows at a cut is written as a provisional
segment: a real content-addressed Parquet file, flagged in the manifest. That table's next seal
reads it back, folds its rows in front of the new ones, writes one segment for the whole span and
removes the provisional file. A table at or over the floor gets a final segment, which is never
reopened. At most one provisional segment per table at any time. The cut itself is unchanged, so a
busy table's segments are byte-identical with and without a sparse neighbour, and a run that
answers short once seals the same content addresses as one that never does.
What it does not do. Segments already on disk as final are not touched: there is no compaction,
and a store written by an earlier release keeps its files. A nest that wants the floor applied to
its whole history is re-indexed from its start block. Older binaries read a manifest written by this
release without complaint and see a provisional segment as an ordinary one; the flag is absent from
the manifest when false.
What it costs. One read of a small Parquet file per sparse table per cut. nuthatch_rows_sealed_total
counts new rows only across a fold.
Robinhood Chain is built in (#1133, RFC-0050)
nuthatch init 0xADDR --chain robinhood works with no key. Chain id 4663, Robinhood's Arbitrum
Orbit L2 on the Nitro stack, so it rides the generic EVM path with no special-casing: a registry
entry, not an adapter. Carve-out four in the brief, for this one chain and nothing else. Aliases
robinhood, robinhood-chain, robinhood-mainnet, rh.
What an operator should know. Measured on 2026-09-04 and recorded on the tracking issue.
- The seal boundary is the
finalizedtag, which sits about twenty minutes behind tip (11,921
blocks at 9.9 blocks/s when measured), with a fallback depth of 15,000 blocks for an endpoint that
stops serving it. The RFC's body recommends thesafetag, about thirteen minutes; there is no
safepolicy in the seal loop and adding one is a seal-loop change, so the trade is recorded in
the RFC's addendum rather than made. - The window is 320 blocks. The chain is busy: 148 logs a block over a thousand sampled blocks,
led by USDG and WETH. - One keyless endpoint serves the chain, and its batch limit is 5. With timestamps on, a
window's timestamp fetch becomes dozens of calls and the endpoint answers 429 to all of them. Use
init --no-timestampson the shipped endpoint, or bring a keyed one. Keylessiniton USDG
resolved its ABI through Sourcify in 2.5 s; 2,000 blocks and 23,953 transfers indexed in 16 s with
timestamps off. - The over-wide-range refusal this endpoint answers with,
-32000 "logs matched by query exceeds limit of 10000"on HTTP 200, is now classified as narrowable. It matched the text fallback only,
so it would have been retried at the same width.
Monad stays at a depth of eight (#1145)
3.3.0's note said not to lower Monad's seal boundary to the finalized tag without the soak the RFC
asked for. The soak ran: 24 hours on all three shipped endpoints, 648,532 reads of finalized and
its receipts, zero short answers. The boundary still does not move, on the merge-safety review's
ground that the tail refetch covers two blocks and Monad's execution deferral is three, so a day of
clean reads is evidence about those endpoints rather than a runtime guarantee. Nothing changes for a
Monad nest; the result and the decision are recorded in RFC-0051's addendum.
For contributors
Debug builds switch off DuckDB's compressed materialisation (#1152). Its D_ASSERT aborted the whole
test binary on a filtered ORDER BY over one segment spanning the filter, which the floor above made
common. Release builds always answered correctly and are unchanged.
No data migration. A binary swap and a restart; a nest gains the floor from its next cut.