Skip to content

Backport #17222 to glamsterdam-devnet-7: fix PTC blob_data_available to use data column availability - #17316

Open
parithosh wants to merge 29 commits into
OffchainLabs:glamsterdam-devnet-7from
parithosh:fix/ptc-blob-da-devnet7
Open

Backport #17222 to glamsterdam-devnet-7: fix PTC blob_data_available to use data column availability#17316
parithosh wants to merge 29 commits into
OffchainLabs:glamsterdam-devnet-7from
parithosh:fix/ptc-blob-da-devnet7

Conversation

@parithosh

Copy link
Copy Markdown

What

Backport of #17222 (21b1d046b0) to glamsterdam-devnet-7. Clean cherry-pick, no conflicts, original authorship preserved.

develop got this fix on 2026-07-22. glamsterdam-devnet-7 branched from develop at 5420c085 (2026-07-15) and never received it, so the branch the devnet is actually running still has the old behaviour.

Why it matters on this branch

On devnet-7, buildPayloadAttestationData populates the PTC vote as:

BlobDataAvailable: s.ForkchoiceFetcher.HasFullNode(root),

HasFullNode only reports whether a full payload node exists in fork choice — it is a payload-import check and never looks at a blob commitment or a column sidecar. Per specs/gloas/validator.md, the field is:

Set data.blob_data_available to is_data_available(data.beacon_block_root)

is_data_available is independent of envelope import, and with zero blob commitments it is vacuously true (all([])).

Observed on the live devnet

Slot 165502 (2026-08-06 11:40:24 UTC). The payload was orphaned and the bid declared blob_kzg_commitments: [] — zero blobs. All 500 PTC votes agreed payload_present=false, then split on blob_data_available:

vote count clients
blob_data_available=true 350 nimbus, teku, lodestar, grandine
blob_data_available=false 150 prysm, lighthouse

With zero blobs there is no data that can be unavailable, so true is correct and Prysm's false is wrong.

This is systematic, and the correlation is exact — the (payload_absent, blob_data_available=true) aggregate appears on only the zero-blob slots and never when the orphaned payload carried blobs:

voted slot blobs in bid aggregates (present, available) → signers
165484 0 (F,T)→331 · (F,F)→162
165488 14 (T,T)→340 · (T,F)→92 · (F,F)→13
165494 0 (F,T)→313 · (F,F)→163
165497 0 (F,T)→291 · (F,F)→184
165501 3 (T,T)→431 · (T,F)→30 · (F,F)→14
165502 0 (F,T)→350 · (F,F)→150
165508 0 (F,T)→337 · (F,F)→126

That is precisely the behaviour DataAvailable restores, and the cherry-picked TestDataAvailable/no_blob_commitments covers exactly this case.

Fork-choice impact at 165502 was nil — payload_present=false was unanimous, so ptcVotedLate decided it and the availability split was never consulted. The reason to backport is that on this branch a Prysm PTC vote carries no independent blob-availability signal at all: both fields derive from having the payload, so the vote cannot express "payload arrived, blob data did not".

For reference, Lighthouse had the same class of bug on their glamsterdam-devnet-7 branch (fork_choice.is_payload_received) and have already replaced it with a custody-column check on unstable.

Behaviour change reviewers should know

buildPayloadAttestationData is now fallible. A DataAvailable error returns an Internal RpcError, so a getBlock miss (root not in cache or DB) suppresses the PTC vote, where the old HasFullNode(root) could never fail and always produced one. This is upstream's own design from #17222, carried over unchanged — noting it because this branch runs on a live devnet.

Verification

Run in a clean worktree at the devnet-7 tip:

  • go build of all four touched packages — clean
  • go test ./beacon-chain/rpc/core/... -tags develop — ok
  • go test ./beacon-chain/rpc/eth/validator/... -tags develop — ok
  • TestDataAvailable (new, from the cherry-pick) — all three subtests pass, including no_blob_commitments

Pre-existing CI failures on this branch, not caused by this PR

Two test packages already fail to build on the devnet-7 base. Both verified at HEAD~1 (3cf9687dd9) and neither file is touched here:

  • beacon-chain/blockchainhead_test.go:169,269,300: notifyNewHeadEvent takes [32]byte, tests pass []byte
  • beacon-chain/rpc/prysm/v1alpha1/validatorproposer_payload_envelope_test.go:271,282: undefined ethpb.GenericSignedExecutionPayloadEnvelope_SignedEnvelope, undefined proto

To run TestDataAvailable I temporarily patched head_test.go locally; that patch is not part of this PR.

potuz and others added 29 commits July 1, 2026 13:27
Adds a feature flag --reorg-late-payloads that currently defaults to
false and it is hidden.
postPayloadTasks no longer takes the head root and the envelope root as
parameters since they can be obtained from the node.

Also, it splits up a helper to check if the incoming payload will be
head under a head lock and thus avoids any race.
When the payload is for the current slot, will become head and we are
proposing in the next slot, do not send FCU if it arrived late.
The following are the main branches:

1. If the head has not changed at 10 seconds, we may still need to send
   a new FCU if the PTC is telling us to reorg the payload (there was
consensus that it was late)
2. If the head has changed but the head root hasn't, we are dealing with
   a payload content reorg, we need to understand it: if it went from
full -> empty, this can only happen because of attestations and this can
only happen for heads from the previous blocks, we do not reorg such
blocks so we need to send FCU
  If it went empty->full, it may have been that we avoided sending FCU
when the payload was late, we need to evaluate if the PTC already
achieved consensus, if it hasn't then we can continue our bet of
reorging.
3. If the headroot has changed, continue with the previous paths to
   reorg late blocks.
Update shouldReorgPayload expectations for the negated PTC-consensus
logic, only emit head_v2 on the empty->full transition, and convert
counting loops to range to satisfy the rangeint modernize analyzer.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Kasey Kirkham <kasey@users.noreply.github.com>
…nnot be guaranteed (OffchainLabs#17174)

**What type of PR is this?**

> Bug fix

**What does this PR do? Why is it needed?**

Reported in Discord ([Message
link](https://discord.com/channels/595666850260713488/892088344438255616/1526470317315457165)).


https://github.com/OffchainLabs/prysm/blob/73439ebe08e6cee85e5e9dbc24ac60dd71d4294d/beacon-chain/execution/engine_client.go#L182-L196

Today, Prysm reconstructs the series of execution payload envelopes from
multiple sources:
1. DB: Saves (and read) *blinded* payload envelope which deliberately
omits large-sized transactions, withdrawals, and **BAL**.
2. Call Engine API (`engine_getPayloadBodiesByHashV2`) for getting
payloads. Note that this can return `null` `blockAccessList` when BAL
has been pruned from storage. (ref:
https://github.com/ethereum/execution-apis/blob/main/src/engine/amsterdam.md#engine_getpayloadbodiesbyhashv2)
3. Call Engine API (`eth_getBlockByHash`) for getting execution blocks.

So it is possible to construct a payload with empty BAL, when the
envelope originally had non-empty BAL at that slot. The problem is that
we uses the signature from the DB, which can be deviated as the message
becomes different. (non-empty BAL -> empty BAL)

This PR returns an error when reconstruction cannot guarantee the
correctness, which means:
- if EL cannot provide execution payloads or
- length of BAL is zero - BAL shouldn't be zero-byted in terms of RLP.

**Which issue(s) does this PR fix?**

N/A but should solve the issue in `glamsterdam-devnet-6`. ([Discord
link](https://discord.com/channels/595666850260713488/892088344438255616/1526470317315457165)))

**Other notes for review**

~~`TestReconstructExecutionPayloadEnvelope_BlindedStorage` covers
possible scenarios that I can think of.~~

**Acknowledgements**

- [x] I have read
[CONTRIBUTING.md](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md).
- [x] I have included a uniquely named [changelog fragment
file](https://github.com/prysmaticlabs/prysm/blob/develop/CONTRIBUTING.md#maintaining-changelogmd).
- [x] I have added a description with sufficient context for reviewers
to understand this PR.
- [x] I have tested that my changes work as expected and I added a
testing plan to the PR description (if applicable).
The lookahead solved the shuffling stability problem on Fulu
…Labs#17222)

- `BlobDataAvailable` in payload attestation data was populated from
`HasFullNode`, which only flips after the envelope is fully imported
- Per spec, `blob_data_available` is
`is_data_available(beacon_block_root)`, independent of envelope import
- New `DataAvailable` getter checks the non-blocking column store status
first, and only reads the block's bid when no columns are stored, since
an empty column summary can't distinguish a blobless payload from
missing data and this order avoids a DB block read in the common case

(cherry picked from commit 21b1d04)
@terencechain
terencechain force-pushed the glamsterdam-devnet-7 branch from 3cf9687 to 821840a Compare August 6, 2026 13:43
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.

6 participants