Skip to content

feat: gloas alpha.5 speed run#9219

Draft
ensi321 wants to merge 24 commits intounstablefrom
nc/alpha.5-vibe
Draft

feat: gloas alpha.5 speed run#9219
ensi321 wants to merge 24 commits intounstablefrom
nc/alpha.5-vibe

Conversation

@ensi321
Copy link
Copy Markdown
Contributor

@ensi321 ensi321 commented Apr 15, 2026

Summary

Speed-run implementation of consensus-specs#5094 (deferred execution payload processing) plus range sync and unknown payload sync for Gloas. Pinned to spec commit 26ed32e.

This branch is for testing and demonstration only — not production ready. It will be broken into smaller PRs for proper review.

What changed

Deferred payload processing (specs#5094):

  • parentExecutionRequests added to BeaconBlockBody — parent slot's execution requests are now embedded in the block
  • executionRequestsRoot added to ExecutionPayloadBid — builder commits to execution requests
  • stateRoot removed from ExecutionPayloadEnvelope — envelope no longer produces a post-state
  • New processParentExecutionPayload runs as first step in processBlock, before processBlockHeader
  • processExecutionPayloadEnvelope transformed to pure verification (no state mutation)
  • processWithdrawals early return removed — parent effects handled by processParentExecutionPayload
  • Fork choice onExecutionPayload no longer takes stateRoot — FULL variant shares PENDING's stateRoot
  • Envelope import pipeline simplified: no state clone, no post-payload state caching, no state root check
  • Block production includes parentExecutionRequests and executionRequestsRoot
  • Gossip validation adds executionRequestsRoot check with skip optimization for import

Range sync (supersedes #9155):

  • Batch carries payloadEnvelopes across all state transitions
  • downloadByRange fetches envelopes via sendExecutionPayloadEnvelopesByRange
  • validateEnvelopesByRangeResponse verifies beaconBlockRoot matches blocks
  • processChainSegment accepts and processes envelopes after block import
  • Gloas DataColumnSidecar validation (dual Fulu/Gloas column shapes)

Unknown payload sync (supersedes #9102):

  • PARENT_PAYLOAD_UNKNOWN block error for gossip blocks with missing parent envelope
  • BlockInputSync tracks pendingPayloads, fetches via sendExecutionPayloadEnvelopesByRoot
  • Subscribes to unknownEnvelopeBlockRoot and executionPayloadAvailable events

Dependencies

This branch is based on the dual-state revert PRs and needs alpha.4 PRs merged:

Key architectural insight

With deferred processing, blocks are self-contained for state transitionparentExecutionRequests is in the block body, so processBlock doesn't depend on the parent's envelope. Envelopes are only needed for fork-choice FULL/EMPTY variant determination and data availability. This means blocks can sync optimistically without envelopes.

Test plan

  • pnpm check-types passes
  • Unit tests pass for state-transition, fork-choice, beacon-node
  • Spec tests for processParentExecutionPayload and verifyExecutionPayloadEnvelope
  • E2E finalized sync test with Gloas fork
  • Devnet testing with multiple clients

🤖 Generated with Claude Code

AI Assistance Disclosure: Used Claude Code to assist with implementation and review.

@ensi321 ensi321 changed the title feat: gloas alpha.5 — deferred payload processing + sync feat: gloas alpha.5 speed run Apr 15, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request implements deferred execution payload processing for the Gloas fork in accordance with consensus-specs#5094. Key changes include removing the state root from execution payload envelopes, adding execution request roots to bids, and introducing a mechanism to apply parent payload effects during child block processing. Feedback identifies a critical bug in range sync where Gloas data columns are inadvertently dropped, and potential issues in block production when parent envelopes are missing from the cache. Further recommendations include batching network requests for efficiency, refining peer selection strategies for envelope fetching, and ensuring fetched envelopes are verified against requested roots.

);
}
// Gloas columns are added to PayloadEnvelopeInput by the caller, not to IBlockInput
if (isGloasDataColumnSidecar(firstColumn)) continue;
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.

high

Gloas data columns are being skipped in cacheByRangeResponses, but they are not being handled by the caller either. In RangeSync.downloadByRange, the responses.validatedColumnSidecars are passed to this function but the returned blocks and payloadEnvelopes do not include them. This means Gloas columns downloaded during range sync are effectively dropped, which will prevent PayloadEnvelopeInput from becoming complete and stall sync.

Comment thread packages/beacon-node/src/chain/chain.ts Outdated
Comment on lines +881 to +886
const payloadInput = this.seenPayloadEnvelopeInputCache.get(parentBlockRootHex);
if (payloadInput?.hasPayloadEnvelope()) {
return payloadInput.getPayloadEnvelope().message.executionRequests;
}
// Parent was EMPTY or we don't have the envelope — return empty requests
return ssz.electra.ExecutionRequests.defaultValue();
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.

medium

If seenPayloadEnvelopeInputCache.get(parentBlockRootHex) results in a cache miss for a parent block that was actually FULL, this method returns empty execution requests. During block production, this will lead to an invalid block because the parentExecutionRequests will not match the parent's bid commitment stored in the state. We should check if the parent was FULL (e.g., by checking fork choice or the state's latestExecutionPayloadBid) and handle the missing envelope case more robustly (e.g., by throwing or attempting to retrieve it from the DB).

Comment on lines +316 to +338
for (const [rootHex, pending] of this.pendingPayloads) {
if (pending.slot <= finalizedSlot) {
this.pendingPayloads.delete(rootHex);
continue;
}
if (
this.chain.seenPayloadEnvelopeInputCache.get(rootHex)?.hasPayloadEnvelope() ||
this.chain.forkChoice.getBlockHex(rootHex, PayloadStatus.FULL)
) {
this.pendingPayloads.delete(rootHex);
continue;
}
if (!this.chain.forkChoice.hasBlockHexUnsafe(rootHex)) continue;
if (pending.status !== "pending") continue;
if (pending.attempts >= MAX_ATTEMPTS_PER_PAYLOAD) {
this.pendingPayloads.delete(rootHex);
continue;
}
this.fetchPayloadEnvelope(pending).catch((e) => {
this.logger.debug("Unexpected error - fetchPayloadEnvelope", {root: pending.blockRootHex}, e);
});
}
};
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.

medium

triggerPayloadSearch iterates through all pending payloads and triggers individual network requests for each. This is inefficient and could lead to a large number of concurrent RPCs. Since sendExecutionPayloadEnvelopesByRoot supports multiple roots, consider batching these requests into a single RPC call per peer.

pending.status = "fetching";
pending.attempts++;
try {
const peerMeta = this.peerBalancer.bestPeerForPendingColumns(new Set(), new Set());
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.

medium

Using bestPeerForPendingColumns with empty sets is inappropriate for selecting a peer to fetch execution payload envelopes. This method is specifically designed for Data Availability sampling and might not return an optimal peer for consensus/execution data. Consider using a more general peer selection strategy or prioritizing peers that have already provided related blocks.

return;
}

const envelope = envelopes[0];
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.

medium

The fetched envelope should be verified against the requested blockRootHex. A malicious or buggy peer could return an envelope for a different block, which should be caught before adding it to the cache.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 15, 2026

⚠️ Performance Alert ⚠️

Possible performance regression was detected for some benchmarks.
Benchmark result of this commit is worse than the previous benchmark result exceeding threshold.

Benchmark suite Current: 9e5dcdf Previous: 0bc48d3 Ratio
Full columns - reconstruct all 20 blobs 1.6618 ms/op 503.58 us/op 3.30
Full benchmark results
Benchmark suite Current: 9e5dcdf Previous: 0bc48d3 Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 840.94 us/op 1.0771 ms/op 0.78
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 39.272 us/op 44.000 us/op 0.89
BLS verify - blst 760.73 us/op 718.93 us/op 1.06
BLS verifyMultipleSignatures 3 - blst 1.3671 ms/op 1.3713 ms/op 1.00
BLS verifyMultipleSignatures 8 - blst 2.1756 ms/op 2.1905 ms/op 0.99
BLS verifyMultipleSignatures 32 - blst 6.7678 ms/op 6.9608 ms/op 0.97
BLS verifyMultipleSignatures 64 - blst 13.292 ms/op 14.038 ms/op 0.95
BLS verifyMultipleSignatures 128 - blst 25.608 ms/op 26.228 ms/op 0.98
BLS deserializing 10000 signatures 627.01 ms/op 644.45 ms/op 0.97
BLS deserializing 100000 signatures 6.3878 s/op 6.3799 s/op 1.00
BLS verifyMultipleSignatures - same message - 3 - blst 805.99 us/op 811.07 us/op 0.99
BLS verifyMultipleSignatures - same message - 8 - blst 956.48 us/op 894.63 us/op 1.07
BLS verifyMultipleSignatures - same message - 32 - blst 1.5440 ms/op 1.4551 ms/op 1.06
BLS verifyMultipleSignatures - same message - 64 - blst 2.3700 ms/op 2.3774 ms/op 1.00
BLS verifyMultipleSignatures - same message - 128 - blst 4.0199 ms/op 4.0744 ms/op 0.99
BLS aggregatePubkeys 32 - blst 17.672 us/op 17.855 us/op 0.99
BLS aggregatePubkeys 128 - blst 63.170 us/op 63.645 us/op 0.99
getSlashingsAndExits - default max 46.639 us/op 49.555 us/op 0.94
getSlashingsAndExits - 2k 324.12 us/op 360.73 us/op 0.90
proposeBlockBody type=full, size=empty 883.80 us/op 779.03 us/op 1.13
isKnown best case - 1 super set check 170.00 ns/op 163.00 ns/op 1.04
isKnown normal case - 2 super set checks 165.00 ns/op 167.00 ns/op 0.99
isKnown worse case - 16 super set checks 165.00 ns/op 163.00 ns/op 1.01
validate api signedAggregateAndProof - struct 1.5285 ms/op 1.5149 ms/op 1.01
validate gossip signedAggregateAndProof - struct 1.5265 ms/op 1.5095 ms/op 1.01
batch validate gossip attestation - vc 640000 - chunk 32 105.05 us/op 105.35 us/op 1.00
batch validate gossip attestation - vc 640000 - chunk 64 91.894 us/op 91.766 us/op 1.00
batch validate gossip attestation - vc 640000 - chunk 128 85.690 us/op 85.032 us/op 1.01
batch validate gossip attestation - vc 640000 - chunk 256 82.222 us/op 81.175 us/op 1.01
bytes32 toHexString 292.00 ns/op 279.00 ns/op 1.05
bytes32 Buffer.toString(hex) 173.00 ns/op 177.00 ns/op 0.98
bytes32 Buffer.toString(hex) from Uint8Array 246.00 ns/op 241.00 ns/op 1.02
bytes32 Buffer.toString(hex) + 0x 175.00 ns/op 183.00 ns/op 0.96
Return object 10000 times 0.21320 ns/op 0.21070 ns/op 1.01
Throw Error 10000 times 3.3243 us/op 3.3055 us/op 1.01
toHex 100.58 ns/op 106.55 ns/op 0.94
Buffer.from 91.594 ns/op 89.826 ns/op 1.02
shared Buffer 60.445 ns/op 63.870 ns/op 0.95
fastMsgIdFn sha256 / 200 bytes 1.4990 us/op 1.4720 us/op 1.02
fastMsgIdFn h32 xxhash / 200 bytes 153.00 ns/op 155.00 ns/op 0.99
fastMsgIdFn h64 xxhash / 200 bytes 204.00 ns/op 209.00 ns/op 0.98
fastMsgIdFn sha256 / 1000 bytes 4.7980 us/op 4.7490 us/op 1.01
fastMsgIdFn h32 xxhash / 1000 bytes 241.00 ns/op 248.00 ns/op 0.97
fastMsgIdFn h64 xxhash / 1000 bytes 248.00 ns/op 263.00 ns/op 0.94
fastMsgIdFn sha256 / 10000 bytes 42.073 us/op 41.952 us/op 1.00
fastMsgIdFn h32 xxhash / 10000 bytes 1.2830 us/op 1.2590 us/op 1.02
fastMsgIdFn h64 xxhash / 10000 bytes 826.00 ns/op 823.00 ns/op 1.00
send data - 1000 256B messages 4.0171 ms/op 4.0913 ms/op 0.98
send data - 1000 512B messages 4.0882 ms/op 4.0938 ms/op 1.00
send data - 1000 1024B messages 4.1829 ms/op 4.2278 ms/op 0.99
send data - 1000 1200B messages 4.3790 ms/op 4.4535 ms/op 0.98
send data - 1000 2048B messages 4.6377 ms/op 4.5511 ms/op 1.02
send data - 1000 4096B messages 5.2675 ms/op 5.2444 ms/op 1.00
send data - 1000 16384B messages 15.336 ms/op 25.387 ms/op 0.60
send data - 1000 65536B messages 258.67 ms/op 211.75 ms/op 1.22
enrSubnets - fastDeserialize 64 bits 717.00 ns/op 705.00 ns/op 1.02
enrSubnets - ssz BitVector 64 bits 276.00 ns/op 266.00 ns/op 1.04
enrSubnets - fastDeserialize 4 bits 97.000 ns/op 100.00 ns/op 0.97
enrSubnets - ssz BitVector 4 bits 272.00 ns/op 270.00 ns/op 1.01
prioritizePeers score -10:0 att 32-0.1 sync 2-0 212.36 us/op 202.26 us/op 1.05
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 243.49 us/op 244.46 us/op 1.00
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 359.50 us/op 341.48 us/op 1.05
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 640.23 us/op 592.00 us/op 1.08
prioritizePeers score 0:0 att 64-1 sync 4-1 771.80 us/op 691.30 us/op 1.12
array of 16000 items push then shift 1.3206 us/op 1.2915 us/op 1.02
LinkedList of 16000 items push then shift 7.3220 ns/op 6.8460 ns/op 1.07
array of 16000 items push then pop 67.473 ns/op 64.056 ns/op 1.05
LinkedList of 16000 items push then pop 6.0910 ns/op 5.9110 ns/op 1.03
array of 24000 items push then shift 1.9403 us/op 1.9101 us/op 1.02
LinkedList of 24000 items push then shift 6.8270 ns/op 6.6030 ns/op 1.03
array of 24000 items push then pop 94.168 ns/op 90.671 ns/op 1.04
LinkedList of 24000 items push then pop 6.0450 ns/op 5.9330 ns/op 1.02
intersect bitArray bitLen 8 4.7940 ns/op 4.7510 ns/op 1.01
intersect array and set length 8 30.080 ns/op 29.360 ns/op 1.02
intersect bitArray bitLen 128 26.775 ns/op 24.086 ns/op 1.11
intersect array and set length 128 504.92 ns/op 493.69 ns/op 1.02
bitArray.getTrueBitIndexes() bitLen 128 1.0640 us/op 1.0590 us/op 1.00
bitArray.getTrueBitIndexes() bitLen 248 1.8260 us/op 1.8650 us/op 0.98
bitArray.getTrueBitIndexes() bitLen 512 3.6490 us/op 3.7920 us/op 0.96
Full columns - reconstruct all 6 blobs 114.54 us/op 134.77 us/op 0.85
Full columns - reconstruct half of the blobs out of 6 70.370 us/op 63.211 us/op 1.11
Full columns - reconstruct single blob out of 6 35.223 us/op 32.516 us/op 1.08
Half columns - reconstruct all 6 blobs 386.34 ms/op 378.59 ms/op 1.02
Half columns - reconstruct half of the blobs out of 6 194.58 ms/op 195.49 ms/op 1.00
Half columns - reconstruct single blob out of 6 70.367 ms/op 66.948 ms/op 1.05
Full columns - reconstruct all 10 blobs 273.65 us/op 299.91 us/op 0.91
Full columns - reconstruct half of the blobs out of 10 94.097 us/op 161.49 us/op 0.58
Full columns - reconstruct single blob out of 10 36.612 us/op 32.245 us/op 1.14
Half columns - reconstruct all 10 blobs 652.89 ms/op 623.03 ms/op 1.05
Half columns - reconstruct half of the blobs out of 10 332.79 ms/op 314.00 ms/op 1.06
Half columns - reconstruct single blob out of 10 70.675 ms/op 67.183 ms/op 1.05
Full columns - reconstruct all 20 blobs 1.6618 ms/op 503.58 us/op 3.30
Full columns - reconstruct half of the blobs out of 20 158.46 us/op 266.42 us/op 0.59
Full columns - reconstruct single blob out of 20 33.502 us/op 33.220 us/op 1.01
Half columns - reconstruct all 20 blobs 1.2840 s/op 1.3137 s/op 0.98
Half columns - reconstruct half of the blobs out of 20 634.07 ms/op 661.62 ms/op 0.96
Half columns - reconstruct single blob out of 20 68.376 ms/op 66.805 ms/op 1.02
Set add up to 64 items then delete first 2.1656 us/op 2.1782 us/op 0.99
OrderedSet add up to 64 items then delete first 3.3951 us/op 3.4391 us/op 0.99
Set add up to 64 items then delete last 2.1803 us/op 2.1583 us/op 1.01
OrderedSet add up to 64 items then delete last 3.3328 us/op 3.3140 us/op 1.01
Set add up to 64 items then delete middle 2.1912 us/op 2.1624 us/op 1.01
OrderedSet add up to 64 items then delete middle 4.7805 us/op 4.8188 us/op 0.99
Set add up to 128 items then delete first 4.3759 us/op 4.3483 us/op 1.01
OrderedSet add up to 128 items then delete first 6.6527 us/op 6.6009 us/op 1.01
Set add up to 128 items then delete last 3.9738 us/op 3.9451 us/op 1.01
OrderedSet add up to 128 items then delete last 5.8740 us/op 5.8609 us/op 1.00
Set add up to 128 items then delete middle 4.0102 us/op 6.3560 us/op 0.63
OrderedSet add up to 128 items then delete middle 11.761 us/op 11.767 us/op 1.00
Set add up to 256 items then delete first 8.1150 us/op 7.9291 us/op 1.02
OrderedSet add up to 256 items then delete first 12.345 us/op 12.204 us/op 1.01
Set add up to 256 items then delete last 8.1696 us/op 7.6968 us/op 1.06
OrderedSet add up to 256 items then delete last 11.594 us/op 11.506 us/op 1.01
Set add up to 256 items then delete middle 7.8221 us/op 7.6832 us/op 1.02
OrderedSet add up to 256 items then delete middle 34.992 us/op 35.105 us/op 1.00
pass gossip attestations to forkchoice per slot 2.5377 ms/op 2.5335 ms/op 1.00
forkChoice updateHead vc 100000 bc 64 eq 0 468.36 us/op 423.00 us/op 1.11
forkChoice updateHead vc 600000 bc 64 eq 0 2.7282 ms/op 2.5337 ms/op 1.08
forkChoice updateHead vc 1000000 bc 64 eq 0 4.6437 ms/op 4.1710 ms/op 1.11
forkChoice updateHead vc 600000 bc 320 eq 0 2.7529 ms/op 2.5029 ms/op 1.10
forkChoice updateHead vc 600000 bc 1200 eq 0 2.7810 ms/op 2.5478 ms/op 1.09
forkChoice updateHead vc 600000 bc 7200 eq 0 3.0763 ms/op 2.8322 ms/op 1.09
forkChoice updateHead vc 600000 bc 64 eq 1000 3.4170 ms/op 3.0421 ms/op 1.12
forkChoice updateHead vc 600000 bc 64 eq 10000 3.5110 ms/op 3.1611 ms/op 1.11
forkChoice updateHead vc 600000 bc 64 eq 300000 7.2393 ms/op 7.0942 ms/op 1.02
computeDeltas 1400000 validators 0% inactive 14.320 ms/op 12.941 ms/op 1.11
computeDeltas 1400000 validators 10% inactive 12.887 ms/op 12.154 ms/op 1.06
computeDeltas 1400000 validators 20% inactive 12.029 ms/op 11.238 ms/op 1.07
computeDeltas 1400000 validators 50% inactive 9.2215 ms/op 8.4405 ms/op 1.09
computeDeltas 2100000 validators 0% inactive 21.024 ms/op 19.168 ms/op 1.10
computeDeltas 2100000 validators 10% inactive 19.516 ms/op 18.123 ms/op 1.08
computeDeltas 2100000 validators 20% inactive 18.066 ms/op 16.565 ms/op 1.09
computeDeltas 2100000 validators 50% inactive 14.026 ms/op 9.9284 ms/op 1.41
altair processAttestation - 250000 vs - 7PWei normalcase 1.7066 ms/op 1.7124 ms/op 1.00
altair processAttestation - 250000 vs - 7PWei worstcase 2.4658 ms/op 2.4435 ms/op 1.01
altair processAttestation - setStatus - 1/6 committees join 103.41 us/op 103.36 us/op 1.00
altair processAttestation - setStatus - 1/3 committees join 206.22 us/op 199.99 us/op 1.03
altair processAttestation - setStatus - 1/2 committees join 292.38 us/op 289.19 us/op 1.01
altair processAttestation - setStatus - 2/3 committees join 373.84 us/op 379.96 us/op 0.98
altair processAttestation - setStatus - 4/5 committees join 515.59 us/op 515.81 us/op 1.00
altair processAttestation - setStatus - 100% committees join 644.61 us/op 627.79 us/op 1.03
altair processBlock - 250000 vs - 7PWei normalcase 4.5996 ms/op 3.1328 ms/op 1.47
altair processBlock - 250000 vs - 7PWei normalcase hashState 17.081 ms/op 12.861 ms/op 1.33
altair processBlock - 250000 vs - 7PWei worstcase 26.061 ms/op 21.178 ms/op 1.23
altair processBlock - 250000 vs - 7PWei worstcase hashState 51.487 ms/op 44.948 ms/op 1.15
phase0 processBlock - 250000 vs - 7PWei normalcase 1.4543 ms/op 1.3588 ms/op 1.07
phase0 processBlock - 250000 vs - 7PWei worstcase 18.617 ms/op 18.956 ms/op 0.98
altair processEth1Data - 250000 vs - 7PWei normalcase 304.09 us/op 309.69 us/op 0.98
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 3.2730 us/op 8.2730 us/op 0.40
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 20.852 us/op 20.900 us/op 1.00
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 5.9270 us/op 6.2420 us/op 0.95
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 3.5700 us/op 3.5680 us/op 1.00
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 96.373 us/op 89.801 us/op 1.07
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.4112 ms/op 1.4286 ms/op 0.99
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.8656 ms/op 1.8785 ms/op 0.99
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.8676 ms/op 1.8339 ms/op 1.02
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.9817 ms/op 3.9329 ms/op 1.01
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.1135 ms/op 2.1560 ms/op 0.98
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 4.2404 ms/op 4.3359 ms/op 0.98
Tree 40 250000 create 322.27 ms/op 334.65 ms/op 0.96
Tree 40 250000 get(125000) 99.440 ns/op 94.938 ns/op 1.05
Tree 40 250000 set(125000) 1.0465 us/op 1.0187 us/op 1.03
Tree 40 250000 toArray() 13.291 ms/op 9.4175 ms/op 1.41
Tree 40 250000 iterate all - toArray() + loop 10.487 ms/op 10.217 ms/op 1.03
Tree 40 250000 iterate all - get(i) 41.850 ms/op 41.225 ms/op 1.02
Array 250000 create 2.1576 ms/op 2.2246 ms/op 0.97
Array 250000 clone - spread 687.13 us/op 691.46 us/op 0.99
Array 250000 get(125000) 0.30200 ns/op 0.30600 ns/op 0.99
Array 250000 set(125000) 0.30500 ns/op 0.30800 ns/op 0.99
Array 250000 iterate all - loop 58.217 us/op 58.227 us/op 1.00
phase0 afterProcessEpoch - 250000 vs - 7PWei 52.764 ms/op 41.921 ms/op 1.26
Array.fill - length 1000000 2.2577 ms/op 2.4638 ms/op 0.92
Array push - length 1000000 9.2414 ms/op 7.9322 ms/op 1.17
Array.get 0.20836 ns/op 0.20979 ns/op 0.99
Uint8Array.get 0.26335 ns/op 0.23963 ns/op 1.10
phase0 beforeProcessEpoch - 250000 vs - 7PWei 20.434 ms/op 14.483 ms/op 1.41
altair processEpoch - mainnet_e81889 270.29 ms/op 277.70 ms/op 0.97
mainnet_e81889 - altair beforeProcessEpoch 23.372 ms/op 21.070 ms/op 1.11
mainnet_e81889 - altair processJustificationAndFinalization 5.7620 us/op 6.5520 us/op 0.88
mainnet_e81889 - altair processInactivityUpdates 4.3802 ms/op 4.3262 ms/op 1.01
mainnet_e81889 - altair processRewardsAndPenalties 18.553 ms/op 21.873 ms/op 0.85
mainnet_e81889 - altair processRegistryUpdates 532.00 ns/op 544.00 ns/op 0.98
mainnet_e81889 - altair processSlashings 137.00 ns/op 134.00 ns/op 1.02
mainnet_e81889 - altair processEth1DataReset 140.00 ns/op 133.00 ns/op 1.05
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.7283 ms/op 1.3065 ms/op 1.32
mainnet_e81889 - altair processSlashingsReset 709.00 ns/op 677.00 ns/op 1.05
mainnet_e81889 - altair processRandaoMixesReset 1.1570 us/op 1.0860 us/op 1.07
mainnet_e81889 - altair processHistoricalRootsUpdate 132.00 ns/op 137.00 ns/op 0.96
mainnet_e81889 - altair processParticipationFlagUpdates 419.00 ns/op 418.00 ns/op 1.00
mainnet_e81889 - altair processSyncCommitteeUpdates 105.00 ns/op 107.00 ns/op 0.98
mainnet_e81889 - altair afterProcessEpoch 42.580 ms/op 42.829 ms/op 0.99
capella processEpoch - mainnet_e217614 847.21 ms/op 828.47 ms/op 1.02
mainnet_e217614 - capella beforeProcessEpoch 63.252 ms/op 60.496 ms/op 1.05
mainnet_e217614 - capella processJustificationAndFinalization 5.9770 us/op 5.7560 us/op 1.04
mainnet_e217614 - capella processInactivityUpdates 17.599 ms/op 17.508 ms/op 1.01
mainnet_e217614 - capella processRewardsAndPenalties 98.467 ms/op 91.198 ms/op 1.08
mainnet_e217614 - capella processRegistryUpdates 4.5240 us/op 4.4830 us/op 1.01
mainnet_e217614 - capella processSlashings 134.00 ns/op 135.00 ns/op 0.99
mainnet_e217614 - capella processEth1DataReset 130.00 ns/op 133.00 ns/op 0.98
mainnet_e217614 - capella processEffectiveBalanceUpdates 12.623 ms/op 5.8147 ms/op 2.17
mainnet_e217614 - capella processSlashingsReset 678.00 ns/op 685.00 ns/op 0.99
mainnet_e217614 - capella processRandaoMixesReset 1.1110 us/op 1.2630 us/op 0.88
mainnet_e217614 - capella processHistoricalRootsUpdate 134.00 ns/op 133.00 ns/op 1.01
mainnet_e217614 - capella processParticipationFlagUpdates 417.00 ns/op 443.00 ns/op 0.94
mainnet_e217614 - capella afterProcessEpoch 109.33 ms/op 109.56 ms/op 1.00
phase0 processEpoch - mainnet_e58758 289.47 ms/op 320.93 ms/op 0.90
mainnet_e58758 - phase0 beforeProcessEpoch 59.622 ms/op 67.266 ms/op 0.89
mainnet_e58758 - phase0 processJustificationAndFinalization 5.3790 us/op 6.4800 us/op 0.83
mainnet_e58758 - phase0 processRewardsAndPenalties 16.113 ms/op 17.181 ms/op 0.94
mainnet_e58758 - phase0 processRegistryUpdates 2.2880 us/op 2.2950 us/op 1.00
mainnet_e58758 - phase0 processSlashings 134.00 ns/op 135.00 ns/op 0.99
mainnet_e58758 - phase0 processEth1DataReset 128.00 ns/op 131.00 ns/op 0.98
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 878.35 us/op 833.13 us/op 1.05
mainnet_e58758 - phase0 processSlashingsReset 869.00 ns/op 920.00 ns/op 0.94
mainnet_e58758 - phase0 processRandaoMixesReset 1.8890 us/op 1.2330 us/op 1.53
mainnet_e58758 - phase0 processHistoricalRootsUpdate 137.00 ns/op 135.00 ns/op 1.01
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.0390 us/op 1.1510 us/op 0.90
mainnet_e58758 - phase0 afterProcessEpoch 34.359 ms/op 34.800 ms/op 0.99
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.0188 ms/op 1.0152 ms/op 1.00
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.2579 ms/op 1.5992 ms/op 0.79
altair processInactivityUpdates - 250000 normalcase 13.579 ms/op 10.895 ms/op 1.25
altair processInactivityUpdates - 250000 worstcase 13.022 ms/op 10.931 ms/op 1.19
phase0 processRegistryUpdates - 250000 normalcase 2.2590 us/op 2.1030 us/op 1.07
phase0 processRegistryUpdates - 250000 badcase_full_deposits 155.72 us/op 147.99 us/op 1.05
phase0 processRegistryUpdates - 250000 worstcase 0.5 64.980 ms/op 60.906 ms/op 1.07
altair processRewardsAndPenalties - 250000 normalcase 16.438 ms/op 16.557 ms/op 0.99
altair processRewardsAndPenalties - 250000 worstcase 16.108 ms/op 15.809 ms/op 1.02
phase0 getAttestationDeltas - 250000 normalcase 5.4450 ms/op 7.9741 ms/op 0.68
phase0 getAttestationDeltas - 250000 worstcase 5.5046 ms/op 5.4585 ms/op 1.01
phase0 processSlashings - 250000 worstcase 60.820 us/op 59.758 us/op 1.02
altair processSyncCommitteeUpdates - 250000 10.495 ms/op 10.356 ms/op 1.01
BeaconState.hashTreeRoot - No change 198.00 ns/op 188.00 ns/op 1.05
BeaconState.hashTreeRoot - 1 full validator 76.904 us/op 82.722 us/op 0.93
BeaconState.hashTreeRoot - 32 full validator 986.30 us/op 954.75 us/op 1.03
BeaconState.hashTreeRoot - 512 full validator 7.6813 ms/op 6.7337 ms/op 1.14
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 103.40 us/op 95.768 us/op 1.08
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.6017 ms/op 1.6148 ms/op 0.99
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 17.260 ms/op 13.703 ms/op 1.26
BeaconState.hashTreeRoot - 1 balances 79.768 us/op 59.693 us/op 1.34
BeaconState.hashTreeRoot - 32 balances 656.72 us/op 726.24 us/op 0.90
BeaconState.hashTreeRoot - 512 balances 4.8558 ms/op 5.1354 ms/op 0.95
BeaconState.hashTreeRoot - 250000 balances 109.48 ms/op 105.31 ms/op 1.04
aggregationBits - 2048 els - zipIndexesInBitList 19.923 us/op 19.691 us/op 1.01
regular array get 100000 times 23.367 us/op 23.085 us/op 1.01
wrappedArray get 100000 times 23.206 us/op 23.008 us/op 1.01
arrayWithProxy get 100000 times 10.465 ms/op 19.444 ms/op 0.54
ssz.Root.equals 21.662 ns/op 21.625 ns/op 1.00
byteArrayEquals 21.382 ns/op 21.266 ns/op 1.01
Buffer.compare 9.2550 ns/op 8.8480 ns/op 1.05
processSlot - 1 slots 8.4370 us/op 8.3710 us/op 1.01
processSlot - 32 slots 1.7274 ms/op 2.0871 ms/op 0.83
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 3.8856 ms/op 3.4684 ms/op 1.12
getCommitteeAssignments - req 1 vs - 250000 vc 1.6621 ms/op 1.7011 ms/op 0.98
getCommitteeAssignments - req 100 vs - 250000 vc 3.4366 ms/op 3.4465 ms/op 1.00
getCommitteeAssignments - req 1000 vs - 250000 vc 3.6829 ms/op 3.7038 ms/op 0.99
findModifiedValidators - 10000 modified validators 905.40 ms/op 754.59 ms/op 1.20
findModifiedValidators - 1000 modified validators 590.79 ms/op 411.14 ms/op 1.44
findModifiedValidators - 100 modified validators 299.42 ms/op 294.29 ms/op 1.02
findModifiedValidators - 10 modified validators 171.41 ms/op 205.77 ms/op 0.83
findModifiedValidators - 1 modified validators 171.76 ms/op 190.28 ms/op 0.90
findModifiedValidators - no difference 246.81 ms/op 163.70 ms/op 1.51
migrate state 1500000 validators, 3400 modified, 2000 new 2.9092 s/op 3.7061 s/op 0.78
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 3.7400 ns/op 3.8900 ns/op 0.96
state getBlockRootAtSlot - 250000 vs - 7PWei 285.43 ns/op 388.75 ns/op 0.73
computeProposerIndex 100000 validators 1.3646 ms/op 1.3746 ms/op 0.99
getNextSyncCommitteeIndices 1000 validators 2.9260 ms/op 3.0006 ms/op 0.98
getNextSyncCommitteeIndices 10000 validators 25.946 ms/op 25.931 ms/op 1.00
getNextSyncCommitteeIndices 100000 validators 89.546 ms/op 90.820 ms/op 0.99
computeProposers - vc 250000 555.38 us/op 570.87 us/op 0.97
computeEpochShuffling - vc 250000 40.375 ms/op 41.794 ms/op 0.97
getNextSyncCommittee - vc 250000 9.4830 ms/op 9.8676 ms/op 0.96
nodejs block root to RootHex using toHex 102.20 ns/op 98.815 ns/op 1.03
nodejs block root to RootHex using toRootHex 63.077 ns/op 60.141 ns/op 1.05
nodejs fromHex(blob) 821.89 us/op 811.44 us/op 1.01
nodejs fromHexInto(blob) 664.91 us/op 636.41 us/op 1.04
nodejs block root to RootHex using the deprecated toHexString 497.96 ns/op 527.86 ns/op 0.94
nodejs byteArrayEquals 32 bytes (block root) 26.043 ns/op 26.430 ns/op 0.99
nodejs byteArrayEquals 48 bytes (pubkey) 37.605 ns/op 38.153 ns/op 0.99
nodejs byteArrayEquals 96 bytes (signature) 39.960 ns/op 36.003 ns/op 1.11
nodejs byteArrayEquals 1024 bytes 44.665 ns/op 42.451 ns/op 1.05
nodejs byteArrayEquals 131072 bytes (blob) 1.7654 us/op 1.7924 us/op 0.98
browser block root to RootHex using toHex 146.50 ns/op 146.06 ns/op 1.00
browser block root to RootHex using toRootHex 132.70 ns/op 131.57 ns/op 1.01
browser fromHex(blob) 1.6173 ms/op 1.5640 ms/op 1.03
browser fromHexInto(blob) 666.12 us/op 637.32 us/op 1.05
browser block root to RootHex using the deprecated toHexString 349.28 ns/op 350.47 ns/op 1.00
browser byteArrayEquals 32 bytes (block root) 27.732 ns/op 28.294 ns/op 0.98
browser byteArrayEquals 48 bytes (pubkey) 39.100 ns/op 39.810 ns/op 0.98
browser byteArrayEquals 96 bytes (signature) 73.151 ns/op 74.535 ns/op 0.98
browser byteArrayEquals 1024 bytes 743.05 ns/op 763.51 ns/op 0.97
browser byteArrayEquals 131072 bytes (blob) 93.982 us/op 96.026 us/op 0.98

by benchmarkbot/action

Copy link
Copy Markdown
Contributor

@twoeths twoeths left a comment

Choose a reason for hiding this comment

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

the prepare_execution_payload function is tricky with PR #5094 because we need to apply payload request in order to get a correct withdrawals to send to EL, need to add that part

also in that function, builder_pending_withdrawals is mutated after epoch boundary, which lodestar implemented correctly but it's implicit, I think it's worth to note a comment around the place for maintenance purpose

Comment on lines +104 to +106
// Verify execution_requests_root matches bid commitment (consensus-specs#5094)
// Can be skipped if already verified during gossip validation
if (verifyExecutionRequestsRoot) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

not sure we need this optimization, but curious what @twoeths thinks

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.

yes I think this is needed for this lodestar
we can either do this way, or add cachePermanentRootStruct: true to

{typeName: "ExecutionRequests", jsonCase: "eth2"}

Comment thread packages/state-transition/src/block/processExecutionPayloadEnvelope.ts Outdated
*
* Spec: apply_parent_execution_payload
*/
function applyParentExecutionPayload(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

just pointed out that apply_parent_execution_payload will be needed during block production, which is still missing from this PR, already partially updated in #9209, but we need to handle that in prepareNextSlot, specifically the spec updates to prepare_execution_payload in 5094

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Added IBeaconStateViewGloas.getExpectedWithdrawalsForFullParent to handle apply_parent_execution_payload + get_expected_withdrawals

@nflaig
Copy link
Copy Markdown
Member

nflaig commented Apr 15, 2026

the prepare_execution_payload function is tricky with PR #5094 because we need to apply payload request in order to get a correct withdrawals to send to EL, need to add that part

also in that function, builder_pending_withdrawals is mutated after epoch boundary, which lodestar implemented correctly but it's implicit, I think it's worth to note a comment around the place for maintenance purpose

can't we just get the head state dialed forward via process_slots (to the proposal slot), we already do that in prepareNextSlot and we really just need to run apply_parent_execution_payload on the prepareState before calling prepareState.getExpectedWithdrawals().expectedWithdrawals if we build on top of the parent block roots payload

we need to make sure we don't mutate the state, but I think we already handle this case by doing a clone()

@twoeths
Copy link
Copy Markdown
Contributor

twoeths commented Apr 16, 2026

we need to make sure we don't mutate the state, but I think we already handle this case by doing a clone()

we never do a clone() in beacon-node now after we migrate to BeaconStateView, see #8728
the convention is never do state mutation there
we'll likely need to enhance to a new BeaconStateView method for that, this should work better when we migrate to the native state-transition

@ensi321 ensi321 force-pushed the nc/alpha.5-vibe branch 2 times, most recently from 18f1694 to fecfcb3 Compare April 20, 2026 01:34
@wemeetagain wemeetagain added the spec-gloas Issues targeting the Glamsterdam spec version label Apr 20, 2026
ensi321 and others added 17 commits April 21, 2026 17:45
Implement the `should_apply_proposer_boost` logic from consensus-specs
commit 71d1151 (PR #4807). This addresses the builder reveal safety
concern where a colluding next-slot proposer could use proposer boost
to override a legitimately revealed block.

Changes:
- Add `ptcTimeliness` and `proposerIndex` fields to ProtoBlock
- Add `isBlockPtcTimely` to track PTC deadline timeliness
- Add `shouldApplyProposerBoost` which withholds boost when the parent
  is a weak, equivocating block from the previous slot
- Add `findEquivocatingBlocks` in ProtoArray to detect proposer
  equivocations by scanning for PTC-timely blocks at the same slot
  from the same proposer
- Gate proposer boost in `getWeight` on `shouldApplyProposerBoost()`
- Pre-gloas blocks retain unconditional boost (backward compatible)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…eturn

New function implements deferred parent execution payload processing
(consensus-specs#5094). Moves execution requests, builder payment, and
availability updates from envelope processing to block processing.

Removes the isParentBlockFull early return in processWithdrawals since
processParentExecutionPayload now runs before withdrawals.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…lock

New ordering for Gloas blocks:
1. processParentExecutionPayload (NEW - before header)
2. processBlockHeader
3. processWithdrawals (no longer has empty-parent early return)
4. processExecutionPayloadBid
5. ... rest unchanged

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Envelope verification no longer mutates state. All state effects (execution
requests, builder payment, availability, latestBlockHash) have moved to
processParentExecutionPayload in the next block.

Changes:
- Remove state cloning, execution request processing, builder payment,
  availability/latestBlockHash updates, and state root verification
- Add executionRequestsRoot check against bid commitment
- Return void instead of post-state
- Remove verifyStateRoot and dontTransferCache options
- Rename stateView method to verifyExecutionPayloadEnvelope

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…onPayload

With deferred payload processing, the envelope no longer produces a
separate post-state. The FULL variant node shares the same stateRoot
as the PENDING node.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- importExecutionPayload now does pure verification only — no state
  cloning, no post-payload state computation, no state root check,
  no regen.processState(), no checkpoint caching
- Remove stateRoot from executionPayload and executionPayloadGossip events
- Remove stateRoot from envelope construction in validator API
- Remove stateRoot from fork choice onExecutionPayload call
- Envelope verification runs via verifyExecutionPayloadEnvelope (void)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rocessing

Block production:
- Add executionRequestsRoot to ExecutionPayloadBid construction
- Add parentExecutionRequests to BeaconBlockBody via getParentExecutionRequests()
- Add getParentExecutionRequests() to BeaconChain (returns parent's
  execution requests from cached envelope, or empty if EMPTY parent)

Gossip validation:
- Add executionRequestsRoot check in envelope validation
- Add EXECUTION_REQUESTS_ROOT_MISMATCH error code

Cleanup:
- Remove stateRoot from validator envelope logging
- Fix spec test for void-returning processExecutionPayloadEnvelope
- Update stale TODO comment in writePayloadEnvelopeInputToDb

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Use electra.ExecutionRequests type instead of unknown[]
- Fix parentBlockRootHex -> parentBlock.blockRoot variable name

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Range sync now fetches and validates execution payload envelopes alongside
blocks for Gloas forks. With deferred processing, blocks can sync
optimistically without envelopes, but envelopes are fetched in parallel
for fork-choice FULL/EMPTY variant accuracy.

Changes:
- Batch carries payloadEnvelopes across all state transitions
- downloadByRange fetches envelopes via sendExecutionPayloadEnvelopesByRange
- validateEnvelopesByRangeResponse verifies beaconBlockRoot matches blocks
- processChainSegment accepts payloadEnvelopes parameter
- SyncChainFns types updated for envelope data flow
- Add INVALID_ENVELOPE_BEACON_BLOCK_ROOT error code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
When a gossip block claims its parent was FULL but the parent's envelope
hasn't been seen, the block is queued and the parent envelope is fetched
via sendExecutionPayloadEnvelopesByRoot.

Changes:
- Add PARENT_PAYLOAD_UNKNOWN block error code and gossip validation
- Add PendingPayloadEnvelope type for tracking missing envelopes
- BlockInputSync: pendingPayloads map, triggerPayloadSearch,
  fetchPayloadEnvelope, onUnknownPayloadEnvelope handler
- Subscribe to unknownEnvelopeBlockRoot and executionPayloadAvailable events
- Add metrics for payload fetch tracking
- Handle PARENT_PAYLOAD_UNKNOWN in processBlock error recovery

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Handle both Fulu and Gloas column shapes in validateColumnsByRangeResponse.
Fulu columns use signedBlockHeader.message.slot, Gloas columns use slot
directly. Dispatch to validateGloasBlockDataColumnSidecars vs
validateFuluBlockDataColumnSidecars based on fork.

Gloas columns in cacheByRangeResponses are skipped (routed to
PayloadEnvelopeInput by the caller, not to IBlockInput).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Only check payload-seen when block is known; unknown blocks are
  handled later by verifyHeadBlockAndTargetRoot which enables the
  API retry path for unknown roots
- Use chain.seenPayloadEnvelope which checks both the gossip cache
  (seenPayloadEnvelopeInputCache) and fork choice FULL variant

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
ensi321 and others added 6 commits April 21, 2026 17:49
Move slot from ExecutionPayloadEnvelope to ExecutionPayload as
slotNumber (uint64) and add slotNumber to PayloadAttributes per
consensus-specs#4840. Updates all verification, gossip validation,
signing, serialization, and SSZ byte extraction accordingly.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Fix ssz calculation

fix
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

spec-gloas Issues targeting the Glamsterdam spec version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants