fix: compute Gloas envelope stateRoot from postState#9255
fix: compute Gloas envelope stateRoot from postState#9255lodekeeper wants to merge 2 commits intoChainSafe:unstablefrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request implements the calculation and caching of the envelopeStateRoot for self-produced blocks in the Gloas fork. Key changes include updating the ProduceFullGloas type, modifying the block production flow in BeaconChain to compute the state root after processing the execution payload envelope, and exposing this root via the validator API. Several tests were added to verify the correct handling of self-build envelopes. A review comment identified critical issues in the implementation of the state root calculation, including incorrect function invocation, missing imports, and type mismatches, providing a comprehensive suggestion to fix these errors.
| if (isForkPostGloas(fork)) { | ||
| const signedEnvelope = ssz.gloas.SignedExecutionPayloadEnvelope.defaultValue(); | ||
| signedEnvelope.message = { | ||
| payload: (produceResult as ProduceFullGloas).executionPayload, | ||
| executionRequests: (produceResult as ProduceFullGloas).executionRequests, | ||
| builderIndex: BUILDER_INDEX_SELF_BUILD, | ||
| beaconBlockRoot: blockRoot, | ||
| slot, | ||
| stateRoot: ZERO_HASH, | ||
| }; | ||
| const postEnvelopeState = postState.processExecutionPayloadEnvelope(signedEnvelope, { | ||
| verifySignature: false, | ||
| verifyStateRoot: false, | ||
| dontTransferCache: true, | ||
| }); | ||
| (produceResult as ProduceFullGloas).envelopeStateRoot = postEnvelopeState.hashTreeRoot(); | ||
| } |
There was a problem hiding this comment.
There are several issues in this block that will likely cause compilation or runtime errors:
- Incorrect Call Syntax and Missing Import:
processExecutionPayloadEnvelopeis called as a method onpostState, but it is defined as a standalone function inpackages/state-transition/src/block/processExecutionPayloadEnvelope.ts. It must be imported and called as a function. - Type Mismatch: The
processExecutionPayloadEnvelopefunction expects aCachedBeaconStateGloasas its first argument, butpostStateis typed asIBeaconStateView. A cast is required. - Repetitive Casting:
produceResultis cast toProduceFullGloasmultiple times. It is cleaner to cast it once to a local variable. - Object Creation: Using a plain object for
signedEnvelopeis more efficient and idiomatic here than usingssz.gloas.SignedExecutionPayloadEnvelope.defaultValue()and then mutating it.
Note: You must add the following imports from @lodestar/state-transition (which are currently missing in this file):
import {processExecutionPayloadEnvelope, type CachedBeaconStateGloas} from "@lodestar/state-transition";| if (isForkPostGloas(fork)) { | |
| const signedEnvelope = ssz.gloas.SignedExecutionPayloadEnvelope.defaultValue(); | |
| signedEnvelope.message = { | |
| payload: (produceResult as ProduceFullGloas).executionPayload, | |
| executionRequests: (produceResult as ProduceFullGloas).executionRequests, | |
| builderIndex: BUILDER_INDEX_SELF_BUILD, | |
| beaconBlockRoot: blockRoot, | |
| slot, | |
| stateRoot: ZERO_HASH, | |
| }; | |
| const postEnvelopeState = postState.processExecutionPayloadEnvelope(signedEnvelope, { | |
| verifySignature: false, | |
| verifyStateRoot: false, | |
| dontTransferCache: true, | |
| }); | |
| (produceResult as ProduceFullGloas).envelopeStateRoot = postEnvelopeState.hashTreeRoot(); | |
| } | |
| if (isForkPostGloas(fork)) { | |
| const fullGloasResult = produceResult as ProduceFullGloas; | |
| const signedEnvelope: gloas.SignedExecutionPayloadEnvelope = { | |
| message: { | |
| payload: fullGloasResult.executionPayload, | |
| executionRequests: fullGloasResult.executionRequests, | |
| builderIndex: BUILDER_INDEX_SELF_BUILD, | |
| beaconBlockRoot: blockRoot, | |
| slot, | |
| stateRoot: ZERO_HASH, | |
| }, | |
| signature: new Uint8Array(96), | |
| }; | |
| const postEnvelopeState = processExecutionPayloadEnvelope(postState as CachedBeaconStateGloas, signedEnvelope, { | |
| verifySignature: false, | |
| verifyStateRoot: false, | |
| dontTransferCache: true, | |
| }); | |
| fullGloasResult.envelopeStateRoot = postEnvelopeState.hashTreeRoot(); | |
| } |
|
Closing — this PR was opened on a false premise by an autonomous investigation. The The fix here computes an envelope The real v1.42.0 "Withdrawal mismatch at index=0" regression is addressed by #9246 (loadState cache aliasing). Apologies for the noise. |
PR draft — Gloas envelope stateRoot fix
Proposed title
fix: compute Gloas envelope stateRoot from postState
Proposed body
Summary
Fix Gloas self-build execution payload envelopes to use the correct envelope-specific
stateRoot.Previously
getExecutionPayloadEnvelope()could return an invalidstateRootfor self-build Gloas envelopes:ZERO_HASHNeither matches what envelope import validates against.
This patch caches an envelope-specific state root derived from the real
postStatereturned by block production and returns that root fromgetExecutionPayloadEnvelope().Root cause
The validator API constructs self-build Gloas envelopes after block production, but the original cached produce result did not retain the correct envelope-level post-state root.
That led to a mismatch between:
stateRootpublished by the validator API, andprocessExecutionPayloadEnvelope(...)Fix
envelopeStateRootenvelopeStateRootfrom the realpostStatereturned bycomputeNewStateRoot()processExecutionPayloadEnvelope(postState, signedEnvelope, {verifySignature: false, verifyStateRoot: false})envelopeStateRootfromgetExecutionPayloadEnvelope()Tests
Added:
packages/beacon-node/test/unit/api/impl/validator/getExecutionPayloadEnvelope.test.tspackages/beacon-node/test/e2e/chain/gloasEnvelopeSelfBuildImport.test.tschain.processExecutionPayload(..., {validSignature: true})Validation run
pnpm test:unit packages/beacon-node/test/unit/api/impl/validator/getExecutionPayloadEnvelope.test.tspnpm exec vitest run packages/beacon-node/test/e2e/chain/gloasEnvelopeSelfBuildImport.test.tsNotes
This PR intentionally carries only the minimal fix and focused coverage, not the broader root-cause investigation / runtime repro history.