Skip to content

fix(gloas): read blob KZG commitments from the bid post-EIP-7732#702

Merged
barnabasbusa merged 5 commits into
masterfrom
bbusa/gloas-blob-commitments-from-bid
May 19, 2026
Merged

fix(gloas): read blob KZG commitments from the bid post-EIP-7732#702
barnabasbusa merged 5 commits into
masterfrom
bbusa/gloas-blob-commitments-from-bid

Conversation

@barnabasbusa
Copy link
Copy Markdown
Collaborator

@barnabasbusa barnabasbusa commented May 19, 2026

Summary

EIP-7732 (Gloas) moved blob_kzg_commitments off the BeaconBlockBody onto the execution payload bid (signed_execution_payload_bid.message.blob_kzg_commitments). Every read site in dora still pulled from the body, so post-Gloas blocks showed empty blob data everywhere.

Symptoms (observed on a Gloas devnet)

  • Slot list / epoch totals: BlobCount=0 on every blob-bearing block
  • Slot detail page: no Blobs tab (BlobsCount=0 hides it)
  • /slot/<root>/blob/<index>: returned Blob index out of range
  • Transaction pages: versioned-hash → KZG-commitment matching produced empty commitments

Fix

Single helper utils.BlockBodyBlobCommitments(body) returns the right commitment list regardless of fork — body field pre-Gloas, bid field post-Gloas. All six call sites now use it.

Sites touched:

  • indexer/beacon/block.goBlockBodyIndex.BlobCount
  • indexer/beacon/writedb.go (×2) — buildDbBlock + buildDbEpoch
  • handlers/slot.go (×2) — single-blob download + slot detail tab data
  • handlers/transaction.go — tx page blob-commitment match (with warn log when commitments unavailable on a blob-bearing block)
  • utils/blobs.go — new helper

Test plan

  • Restart dora against a Gloas devnet with blob-producing spamoor
  • Slot list shows non-zero Txs / Blobs (e.g. 54 / 15)
  • Slot detail page shows a Blobs N tab and lists the commitments
  • /slot/<root>/blob/0 returns blob payload
  • Transaction detail page shows commitments next to each versioned hash

Gloas (EIP-7732) moved blob_kzg_commitments off the BeaconBlockBody onto
the execution payload bid (the proposer commits to the blob set via
signed_execution_payload_bid). Every read site that still pulled from
body.BlobKZGCommitments saw an empty list on Gloas+ blocks:

- slot list / epoch totals recorded BlobCount=0 on every blob-bearing
  block post-fork
- the slot detail page didn't render a Blobs tab (BlobsCount=0 →
  template gate hides it)
- /slot/<root>/blob/<index> returned "Blob index out of range"
- transaction pages couldn't match versioned-hash to commitment

Gate each read site on Version >= DataVersionGloas. Post-gloas reads
go through SignedExecutionPayloadBid.Message.BlobKZGCommitments;
pre-gloas behavior is unchanged.

Sites touched:
- indexer/beacon/block.go        (BlockBodyIndex.BlobCount)
- indexer/beacon/writedb.go (×2) (buildDbBlock + buildDbEpoch)
- handlers/slot.go         (×2)  (single-blob download + slot detail tab)
- handlers/transaction.go        (versioned-hash → commitment match)
@qu0b-reviewer
Copy link
Copy Markdown

qu0b-reviewer Bot commented May 19, 2026

🤖 qu0b-reviewer

Summary

The fix correctly extracts blob KZG commitments from SignedExecutionPayloadBid.Message for Gloas+ (post-EIP-7732) blocks via a new helper BlockBodyBlobCommitments, replacing the previous hardcoded access to body.BlobKZGCommitments which is empty on Gloas+ bodies. The approach is consistent with the existing pattern used in handlers/slot.go:777 for the same field.

Issues

  • 🟡 concern utils/blobs.go:17BlockBodyBlobCommitments gets body.Version from body.Version (a field on the BeaconBlockBody struct), but for SignedExecutionPayloadBid.Message the version semantics are domain-separated — SignedExecutionPayloadBid is an independent gymap message whose Message.Version is the same integer value but logically independent. The check body.Version >= spec.DataVersionGloas controls whether to read SignedExecutionPayloadBid, but it's reading the body's version, which correctly gates the branch. This is safe because: (a) body version and bid presence are coupled by the protocol (bid only exists on Gloas+ bodies), and (b) even if body.Version were somehow mislabeled but the bid existed, reading bid.Message.BlobKZGCommitments is still semantically correct. However, nothing guards the invariant that if SignedExecutionPayloadBid is nil for a Gloas+ body, the callers handle the returned nil gracefully. We should verify: do all callers tolerate a nil/empty return?

  • 🟡 concern handlers/transaction.go:1362blockData.Block.Message.Body is dereferenced here without checking blockData.Block.Version first. For pre-Gloas blocks, BlockBodyBlobCommitments correctly returns body.BlobKZGCommitments. For Gloas+ blocks where SignedExecutionPayloadBid is nil (which can happen during gossip if the bid and envelope arrive out of order), BlockBodyBlobCommitments returns nil, MatchBlobCommitments returns all-zero commitments (since no hash match will occur), and the warning on line 1364 fires. This path is plausible: blockData.Payload (the separate envelope) may be live (as seen two lines above at 1306), but SignedExecutionPayloadBid in the block body might not have been populated because it's a cached block where the gossip bid event arrived after the block event. Worth confirming whether blockInfo.GetBlock(ctx) for a cached block always has SignedExecutionPayloadBid set.

Suggestions

  • 🟢 nit handlers/transaction.go:1362–1364 — The nil-check guard if blockData != nil && blockData.Block != nil && blockData.Block.Message != nil && blockData.Block.Message.Body != nil is repeated verbatim in at least two other places (slot.go:163, transaction.go:1359). Consider extracting to utils.BlockMessageIsNil or similar to consolidate null-checks and make the intent clearer.

Reviewed @ 5879e01f
"LGTM."

- block.go + writedb.go: switch from version.DataVersionGloas to
  spec.DataVersionGloas (re-exported alias, matches the 10+ existing
  references across the repo); drop the gratuitous `spec/version`
  import in block.go.
- transaction.go: log a warn when a Gloas+ block carrying blob txs
  has a nil SignedExecutionPayloadBid, so the silent
  empty-commitments case is observable instead of rendering a
  transaction page with missing KZGs.
Reverts the var-typed declaration that needed an explicit
[]deneb.KZGCommitment. Reads body.BlobKZGCommitments unconditionally,
then in the Gloas+ branch reassigns to the bid's commitments (or nil
when the bid is missing). Same behaviour, no new import, smaller diff.
The Gloas-vs-pre-Gloas blob commitment lookup was open-coded at 6
sites (indexer/block, indexer/writedb ×2, handlers/slot ×2,
handlers/transaction). Pull it into utils.BlockBodyBlobCommitments
so each caller is a single line and the fork gate lives in one place.
Per pk: the bid container is a required field on the Gloas
BeaconBlockBody schema; a structurally-valid post-fork block always
carries it. No need for the nil-fallback path or the warn log that
guarded an unreachable case.
@barnabasbusa barnabasbusa enabled auto-merge May 19, 2026 12:25
@barnabasbusa barnabasbusa merged commit c623074 into master May 19, 2026
4 checks passed
@barnabasbusa barnabasbusa deleted the bbusa/gloas-blob-commitments-from-bid branch May 19, 2026 12:26
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.

2 participants