Reject importing Gloas block until parent's payload is imported#9382
Conversation
Ports the consensus lessons from the gloas-lookup-sync work to unstable: when deciding whether a block's parent is "imported", a post-Gloas FULL child (whose bid commits to the parent's execution payload) must also wait for the parent's payload to be imported, not just the parent block. - proto_array: add `Block::is_child_full(child_bid)` to test whether a post-Gloas child commits to this block's execution payload. - fork_choice: add `ParentImportedStatus` and `is_parent_imported`/`is_parent_imported_status`, which return `UnimportedPayload` for a FULL child whose parent payload has not been received yet. - block_verification: use these in `verify_parent_block_is_known`, the `ExecutionPendingBlock` parent-invalid/parent-unknown gate, and `load_parent` (replacing the plain `get_block`/`contains_block` checks), and drop the now-obsolete gloas TODO comment.
| /// A child block can only be imported after its parent has been fully imported. For a post-Gloas | ||
| /// FULL child (one whose bid commits to the parent's execution payload), "fully imported" also | ||
| /// requires the parent's payload to have been received by fork choice. | ||
| pub fn is_parent_imported_status(&self, block: &SignedBeaconBlock<E>) -> ParentImportedStatus { |
There was a problem hiding this comment.
This returns an enum as some consumers want the parent's ProtoBlock
There was a problem hiding this comment.
Maybe the name of this function should be get_parent_imported_status, or get_parent_import_status? If we go with the latter, the type could be ParentImportStatus. I think both are more grammatical than "is_parent_imported_status"
Remove redundant explanation about FULL child requirement.
| } else if let Some(execution_block_hash) = self.execution_status.block_hash() { | ||
| // Parent is before Gloas, and child is gloas | ||
| execution_block_hash == child_bid.message.parent_block_hash |
There was a problem hiding this comment.
This handles the Gloas fork block. We may need additional handling for Gloas genesis once Gloas lookup sync lands
There was a problem hiding this comment.
I deleted this function in 048b958. I think we still handle the fork boundary by treating all pre-Gloas parents as already imported.
|
How does this relate to: Replacement? Complement? |
Complement, #9039 does not fix this issue |
|
Some required checks have failed. Could you please take a look @dapplion? 🙏 |
Production fix: - fork_choice: `is_parent_imported_status` no longer gates a Gloas child on its parent's payload when the parent is pre-Gloas. A pre-Gloas parent's execution payload is embedded in the block and always present, so the first Gloas block at the fork-transition boundary must not be rejected with `ParentUnknown`. Test harness fixes (all under FORK_NAME=gloas): - block_verification: import each block's payload envelope into fork choice before importing its child (process_chain_segment imports blocks only); for the invalid-signature tests, pre-import ancestors and assert on the segment suffix to avoid re-processing finalization-pruned blocks. - store_tests: mark the weak-subjectivity checkpoint anchor's payload as received so the first forward-sync block (a FULL child of the anchor) is not rejected.
- Rename `ProtoBlock::is_child_full` -> `is_parent_full` (per review); the method is read from the parent's perspective. - Reword `ParentImportedStatus` variant docs and the `is_parent_imported_status` doc comment to describe the condition in terms of the child's bid. No behaviour change.
| @@ -1869,12 +1865,18 @@ fn verify_parent_block_is_known<T: BeaconChainTypes>( | |||
There was a problem hiding this comment.
Might be good to rename this function. Glancing at the gossip verification logic it wasn't immediately clear to me that we'd added the parent envelope check (it looked like we just deleted some stuff).
Maybe verify_parent_block_and_envelope_are_known? Or just verify_parent_is_known?
|
Pushed what I feel is a simplification and clarification in this commit: It removes the |
| // Reject any block where the parent has an invalid payload. It's impossible for a valid | ||
| // block to descend from an invalid parent. | ||
| if parent.execution_status.is_invalid() { | ||
| return Err(BlockError::ParentExecutionPayloadInvalid { | ||
| parent_root: block.parent_root(), | ||
| }); | ||
| } |
There was a problem hiding this comment.
This is kind of a separate issue to this PR, but maybe this check should be part of is_parent_imported_status so that we're forced to handle it at all places we check the parent.
Then again, it is only relevant pre-Gloas, so it's going away soon.
There was a problem hiding this comment.
Then should is_parent_imported return false if the parent is invalid?
There was a problem hiding this comment.
Then should is_parent_imported return false if the parent is invalid?
Maybe an error?
There was a problem hiding this comment.
Would prefer to defer this change to another PR... Doing this now will definitely complicate sync
| // Mark the checkpoint block's payload as received in fork choice. The anchor block is a | ||
| // trusted, finalized block whose payload we already have, but it is loaded via | ||
| // `from_anchor` and so is never marked payload-received. Without this, the first forward | ||
| // block (a FULL child of the anchor) would be rejected with `ParentUnknown`. | ||
| beacon_chain | ||
| .canonical_head | ||
| .fork_choice_write_lock() | ||
| .on_valid_payload_envelope_received(wss_block_root) | ||
| .unwrap(); |
There was a problem hiding this comment.
If we need this in the tests, is it possible we need it on the prod codepath for checkpoint sync as well?
There was a problem hiding this comment.
No, this WSS tests re-implement range sync manually. In production we start without the payload and then either lookup or range sync with populate the payload.
| /// imported into fork choice. `process_chain_segment` imports blocks only (in production, payload | ||
| /// envelopes are fetched and imported separately), so a multi-block segment of consecutive FULL |
There was a problem hiding this comment.
Is this true? Are we planning to keep process_chain_segment only importing blocks rather than blocks and envelopes?
michaelsproul
left a comment
There was a problem hiding this comment.
Few changes I'm not sure about
- Rename `verify_parent_block_is_known` -> `verify_parent_block_and_envelope_are_known` so the added parent-envelope check is visible from the call site (michaelsproul). - Rename `is_parent_imported_status` -> `get_parent_import_status` and the `ParentImportedStatus` enum -> `ParentImportStatus` (michaelsproul). - Drop the contested "in production payload envelopes are fetched separately" claim from the test helper doc; describe current behaviour only (eserilev).
Range sync does not import payload envelopes yet (coming in sigp#9362), so a multi-block segment of consecutive post-Gloas FULL blocks cannot be imported by `process_chain_segment`. Skip the affected `block_verification` tests under `FORK_NAME=gloas` until then, and drop the `import_chain_segment_with_envelopes` placeholder helper and the per-test workarounds it required.
Reduce the weak-subjectivity forward-sync loop to the minimum needed to import post-Gloas blocks: persist the payload envelope and mark it received in fork choice. The data-column reconstruction was unnecessary (the WSS test chain has no blobs). Placeholder until range sync imports payload envelopes (sigp#9362).
- Remove the parent-not-known explanation in the gate match (duplicated in load_parent) and the invalid-parent comment (error name is self-documenting). - Remove the stale spec-paraphrase in verify_parent_block_and_envelope_are_known. - Shorten the get_parent_import_status / verify_parent docs and the WSS anchor and TODO(gloas) comments.
|
Some required checks have failed. Could you please take a look @dapplion? 🙏 |
Merge Queue Status
This pull request spent 29 minutes 22 seconds in the queue, including 27 minutes 40 seconds running CI. Required conditions to merge
ReasonPull request #9382 has been dequeued The pull request could not be merged. This could be related to an activated branch protection or ruleset rule that prevents us from merging. 1 review requesting changes and 1 approving review by reviewers with write access. HintYou should look at the reason for the failure and decide if the pull request needs to be fixed or if you want to requeue it. |
|
@Mergifyio queue |
Merge Queue Status
This pull request spent 30 minutes 44 seconds in the queue, including 28 minutes 52 seconds running CI. Required conditions to merge
|
Adopt sigp#9382's canonical ParentImportStatus / get_parent_import_status (drops the duplicate is_parent_imported_status from this branch), keeping ParentUnknown's parent_block_hash field which the lookup-sync peer donation depends on.
Range sync now imports payload envelopes, so the chain_segment and invalid_signature tests no longer need to early-return under Gloas. Drop the now-unused fork_name_from_env import.
Gloas spec states:
In other words: if the block is FULL child, the payload+data must have been imported before hand. We need to gate Gossip / REST API and sync import flows on that condition. To de-duplicate logic I added
is_parent_imported_statusin fork-choice.ParentUnknown, handle it and fetch the payload. This part is implemented in Gloas lookup sync #9155