Skip to content

feat(shred-txfeed): decode Alpenglow block markers; count them and log the ones that void sent transactions (BLO-48422) - #192

Merged
kkroo merged 2 commits into
mainfrom
kkroo/txfeed-alpenglow-markers
Oct 22, 2026
Merged

kkroo merged 2 commits into
mainfrom
kkroo/txfeed-alpenglow-markers

Conversation

@kkroo

@kkroo kkroo commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Part of BLO-48422 (Alpenglow is live on mainnet; bring the shred pipeline up to date).

Why

Mainnet runs Alpenglow from slot 459,653,000. The feature A1pengvu… activated at epoch 1064 (2026-10-21T13:36:05Z), and the switch happened at genesis block 459,652,999 (13:54:00Z).

Every block is now these components, each its own DATA_COMPLETE run:

  1. a BlockHeader marker
  2. entry batches
  3. a BlockFooter marker
  4. a final one-tick batch

Sources: SIMD-0307/0337/0384 and Agave v4.3.2 entry/src/block_component.rs.

A marker opens with a zero entry count, so ParseEntries returned no transactions for it and the feed ignored it silently. That stayed safe, but it hid two things:

  • Coverage. Nothing showed whether the feed sees whole Alpenglow blocks, which each have one header and one footer.
  • Voided transactions. Two components void a slot's earlier transactions after this feed has already sent them:
    • an UpdateParent marker. Under SIMD-0337 fast leader handover, "state changes from transactions before the UpdateParent marker are discarded". Its gate is not active on mainnet yet; Agave 4.4 is expected to bring it.
    • an empty entry batch. Agave: "leaders should only use an empty entry batch to indicate that they are aborting the block".

What

  • txfeed.ParseMarker decodes the wire layout: u64 0, then u16 version 1, then u8 variant (footer 0, header 1, UpdateParent 2, genesis cert 3), then a u16 length, then the body. It returns:
    • the parent slot and block id of a header or UpdateParent;
    • the certified slot and block id of a genesis certificate;
    • the bank hash, producer clock (ns) and client (agave/4.3.2) of a footer.
      An 8-byte empty batch comes back as MarkerAbandon.
  • In shred-txfeed:
    • The stats line gains headers=, footers=, update_parents=, abandoned= and marker_err=. Nothing parses that line, as checked in go-amt and onprem-k8s.
    • Each UpdateParent and each abandon is logged with its slot.
    • No frame, group or subscriber-visible behavior changes.
  • The feed doc says partition 2 (simple votes) is now empty, because Alpenglow votes are BLS messages, not transactions.

Tests

  • TestParseMarker covers:
    • header, UpdateParent, footer (no certificates) and abandon round-trips;
    • the 54-byte header prefix 00×8 01 00 01 29 00 01 that agave v4.3.2 writes;
    • an entry batch is not a marker;
    • an unknown variant, a wrong outer version and a wrong body version are errors;
    • every truncation past the entry count is an error, never a panic.
  • FuzzParseEntries now also runs ParseMarker on every input. A 20 s local fuzz passed (231k execs).
  • go vet and go test ./txfeed/ ./cmd/shred-txfeed/ pass.

Verified on live bytes

TestAlpenglowLiveSets decodes the header, footer and final-tick sets of two mainnet blocks. They come from a passive capture of the v3 union (232.0.0.2:5001, 2026-10-22 21:41Z), and Reed-Solomon recovers the shreds the capture missed. The two blocks:

  • slot 460191482, an Agave 4.3.2 (Jito) leader;
  • slot 460191528, a Firedancer 26.10.4 leader.

For both blocks the test confirms:

  • the header's parent is slot − 1;
  • the footer carries a bank hash, a producer clock inside the capture minute, and its client string: agave/4.3.2 …, or empty for Firedancer;
  • the final tick is a zero-transaction entry batch.

Firedancer zero-fills every data shred to full size. Its header set is the 54-byte marker followed by zeros. ParseMarker ignores bytes past the marker's length prefix. It also treats a zero entry count followed only by zeros as an abandon, because a zero-filled empty batch would otherwise count as a marker error. No marker is all zeros, since its version is 1. The fixture is 327 frames, 34 KB gzipped.

Deploy

  • Merging deploys nothing. shred-txfeed runs as a CT 140 host unit, and rolling it there is Omar's go.

…g the ones that void sent transactions (BLO-48422)

Mainnet runs Alpenglow since slot 459,653,000 (feature A1pengvu... active at
epoch 1064, 2026-10-21). A block is now a BlockHeader marker, entry batches,
a BlockFooter marker and a final one-tick batch (SIMD-0307/0337/0384; Agave
v4.3.2 entry/src/block_component.rs), each its own DATA_COMPLETE run. A
marker opens with a zero entry count, so ParseEntries returned no
transactions for it and the feed ignored it silently. That stays safe, but it
hid two things:

- whether the feed sees whole Alpenglow blocks (a header and a footer each);
- the two components that void a slot's earlier transactions after this feed
  has sent them: an UpdateParent marker (SIMD-0337 fast leader handover;
  "state changes from transactions before the UpdateParent marker are
  discarded"; its gate is not active on mainnet yet) and the empty entry
  batch a leader sends to abandon its block.

txfeed.ParseMarker decodes a marker: u64 0, u16 version 1, u8 variant
(footer 0, header 1, UpdateParent 2, genesis certificate 3), u16 length, and
the body. It returns the parent slot and block id of a header or
UpdateParent, and the bank hash, producer clock and client of a footer. It
reports an empty entry batch as MarkerAbandon. shred-txfeed counts headers,
footers, UpdateParents, abandons and marker errors in its stats line, and
logs each UpdateParent and abandon with its slot. No frame, group or
subscriber-visible behavior changes.

The feed doc now says partition 2 (simple votes) is empty: Alpenglow votes
are BLS messages between validators, not transactions.
@allyblockcast

allyblockcast Bot commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

🔗 Paperclip issue: BLO-48422

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 82559df

Looks good. I checked the wire layout against Agave v4.3.2 entry/src/block_component.rs and it matches:

  • a u64 zero entry count;
  • a u16 VersionedBlockMarker tag = 1;
  • a u8 BlockMarkerV1 variant (footer 0, header 1, UpdateParent 2, genesis cert 3);
  • a u16 LengthPrefixed body;
  • an inner u8 version for every variant except GenesisCertificate;
  • a footer of bank_hash, then u64 time, then a u8-length user agent.

The abandon case also matches: Agave treats exactly 8 zero bytes as an aborted block.

Verified locally at this head:

  • go vet ./txfeed ./cmd/shred-txfeed: clean.
  • go test ./txfeed: ok.
  • FuzzParseEntries for 20 s, which now also exercises ParseMarker: PASS.

Critical Issues (0)

Important Issues (0)

Suggestions (2)

  • [pr-review-toolkit/code] cmd/shred-txfeed/main.go:397: markers[MarkerGenesisCert] is counted but never printed in the stats line, so a genesis-cert count is collected and then dropped. Either add genesis_certs= or stop counting it.
  • [native-codex] cmd/shred-txfeed/main.go:426: marker detection relies on each marker being its own DATA_COMPLETE run, as the PR body states. If a run ever carried a marker followed by entry batches, ParseEntries would still stop at the zero count, and those transactions would be lost without any signal. This behavior predates the PR and is not a blocker. Keep it in mind if headers= and footers= ever come out lower than slots=.

Strengths

  • Bounds-safe decoding throughout. Truncation at every byte offset is tested and returns an error rather than panicking, and markers[m.Variant] is reachable only for variants 0–3.
  • The fixture pins the exact 54-byte Agave header prefix, so a drift in the layout fails loudly.
  • Subscriber-visible framing is unchanged. The new counters and logs are purely observational.

Recommended Action

  1. No blocking changes requested.
  2. Merge once the remaining required CI checks finish green.

…s on live Agave and Firedancer sets (BLO-48422)

A Firedancer leader (26.10.x, about 14% of stake) zero-fills every data
shred to full size: its header set arrives as the 54-byte marker and then
zeros. ParseMarker already ignored bytes past a marker's length prefix, but
it called only the exact 8-byte empty batch an abandon, so a zero-filled
one would have counted as a marker error. A zero entry count followed only
by zeros is now an abandon; no marker is all zeros, since its version is 1.

TestAlpenglowLiveSets decodes the header, footer and final-tick sets of
two mainnet blocks from a passive capture of the v3 union (232.0.0.2:5001,
2026-10-22 21:41Z): slot 460191482 (Agave 4.3.2, Jito) and slot 460191528
(Firedancer 26.10.4). Reed-Solomon recovers the shreds the capture missed.
It checks parent = slot - 1, the bank hash, a producer clock inside the
capture minute, the footer client (agave/4.3.2..., and empty for
Firedancer), that the final tick is a zero-transaction entry batch, and
that only the Firedancer sets are zero-filled. The fixture is the 327
frames, 34 KB gzipped.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: c6f98a1

Looks good. This is a re-review: there is one new commit since my approval of 82559df, c6f98a1 ("a zero-filled empty batch is an abandon too; test markers on live Alpenglow sets"). The earlier review carried no Critical or Important findings, so there are no prior findings to disposition.

What changed:

  • ParseMarker now reads a zero entry count followed by an all-zero tail as MarkerAbandon, so a padded abandon from a Firedancer leader is recognised.
  • New tests:
    • TestAlpenglowLiveSets decodes the header, footer and final-tick sets of two live mainnet blocks, one from an Agave 4.3.2 leader and one from Firedancer, from a gzipped capture.
    • TestParseMarker gains a 963-byte zero-filled abandon case.

Verified locally at this head:

  • go vet ./txfeed ./cmd/shred-txfeed: clean.
  • go test ./txfeed: ok. TestAlpenglowLiveSets and TestParseMarker both PASS.
  • FuzzParseEntries for 15 s: PASS.

Critical Issues (0)

Important Issues (0)

Suggestions (2)

  • [native-codex] txfeed/solana.go:357: the padded-abandon rule is inferred, not observed. The live fixture contains no abandoned block, and Agave's own rule is exactly eight bytes. The fixture does prove that a Firedancer header marker tolerates a zero tail, so the inference is reasonable, and since abandons only drive a counter and a log line, a misread costs nothing. If a live Firedancer abandon is ever captured, add it to the fixture so this branch is pinned by data rather than by reasoning.
  • [pr-review-toolkit/code] cmd/shred-txfeed/main.go:397: carried from the last review and still present. markers[MarkerGenesisCert] is counted but never printed in the stats line. Either add genesis_certs= or stop counting it.

Strengths

  • The abandon check is exact and cannot swallow a real marker: every marker carries version 1 in byte 8, so no real marker is all zeros, as the new comment says.
  • Skipping the truncation loop for abandons is correct, since every zero prefix is itself an abandon.
  • The live-capture fixture pins header parent slots, footer user agents, the producer clock window, and whether each leader zero-fills. That catches both a layout drift and a client-behaviour drift.

Recommended Action

  1. No blocking changes requested.
  2. Merge once the remaining required CI checks finish green.

@kkroo
kkroo merged commit 1b01b4d into main Oct 22, 2026
22 checks passed
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.

1 participant