Conversation
There was a problem hiding this comment.
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; |
There was a problem hiding this comment.
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.
| 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(); |
There was a problem hiding this comment.
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).
| 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); | ||
| }); | ||
| } | ||
| }; |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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]; |
|
| 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
twoeths
left a comment
There was a problem hiding this comment.
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
| // Verify execution_requests_root matches bid commitment (consensus-specs#5094) | ||
| // Can be skipped if already verified during gossip validation | ||
| if (verifyExecutionRequestsRoot) { |
There was a problem hiding this comment.
not sure we need this optimization, but curious what @twoeths thinks
There was a problem hiding this comment.
yes I think this is needed for this lodestar
we can either do this way, or add cachePermanentRootStruct: true to
| * | ||
| * Spec: apply_parent_execution_payload | ||
| */ | ||
| function applyParentExecutionPayload( |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
Added IBeaconStateViewGloas.getExpectedWithdrawalsForFullParent to handle apply_parent_execution_payload + get_expected_withdrawals
can't we just get the head state dialed forward via we need to make sure we don't mutate the state, but I think we already handle this case by doing a |
we never do a |
18f1694 to
fecfcb3
Compare
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>
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>
42de636 to
6f9ad5b
Compare
3b7e592 to
89e60ae
Compare
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):
parentExecutionRequestsadded toBeaconBlockBody— parent slot's execution requests are now embedded in the blockexecutionRequestsRootadded toExecutionPayloadBid— builder commits to execution requestsstateRootremoved fromExecutionPayloadEnvelope— envelope no longer produces a post-stateprocessParentExecutionPayloadruns as first step inprocessBlock, beforeprocessBlockHeaderprocessExecutionPayloadEnvelopetransformed to pure verification (no state mutation)processWithdrawalsearly return removed — parent effects handled byprocessParentExecutionPayloadonExecutionPayloadno longer takesstateRoot— FULL variant shares PENDING's stateRootparentExecutionRequestsandexecutionRequestsRootexecutionRequestsRootcheck with skip optimization for importRange sync (supersedes #9155):
payloadEnvelopesacross all state transitionsdownloadByRangefetches envelopes viasendExecutionPayloadEnvelopesByRangevalidateEnvelopesByRangeResponseverifiesbeaconBlockRootmatches blocksprocessChainSegmentaccepts and processes envelopes after block importDataColumnSidecarvalidation (dual Fulu/Gloas column shapes)Unknown payload sync (supersedes #9102):
PARENT_PAYLOAD_UNKNOWNblock error for gossip blocks with missing parent envelopeBlockInputSynctrackspendingPayloads, fetches viasendExecutionPayloadEnvelopesByRootunknownEnvelopeBlockRootandexecutionPayloadAvailableeventsDependencies
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 transition —
parentExecutionRequestsis in the block body, soprocessBlockdoesn'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-typespassesprocessParentExecutionPayloadandverifyExecutionPayloadEnvelope🤖 Generated with Claude Code
AI Assistance Disclosure: Used Claude Code to assist with implementation and review.