Skip to content

feat: unknown payload sync for incoming blocks#9102

Closed
ensi321 wants to merge 14 commits intounstablefrom
nc/unknown-payload-sync
Closed

feat: unknown payload sync for incoming blocks#9102
ensi321 wants to merge 14 commits intounstablefrom
nc/unknown-payload-sync

Conversation

@ensi321
Copy link
Copy Markdown
Contributor

@ensi321 ensi321 commented Mar 24, 2026

Depends on #9050
In parallel to #9025

This PR mainly handle case where an incoming block that contains a bid that contains an unknown parent payload hash. See case 2 in the table below.

This PR does not fully handle other one-off cases like unknown payload from attestation data.index === 1, PTC payloadPresent === true. Only to provide the barebones. Will fully handle these cases after #9025 is merged

Case Incoming Unknown Description
1 Block Parent block Handled by pre-gloas flow already
2 Block Parent payload gossip handler emits ChainEvent.unknownParentPayload → BlockInputSync adds child to pendingBlocks + parent to pendingPayloads, fetches payload
3 Block Parent block and parent payload Turns into case 1 first to find parent block, after parent block is imported, this block will be reevaluated, and turn into case 2
4 Payload Beacon block extractBlockSlotRootFns returns {slot, root} → queued in awaitingMessagesByBlockRoot. Already live in unstable
5 Payload Parent payload No need to handle. Beacon block known means parent payload hash must be known. Or else this will be case 2
6 Payload Beacon block and parent payload Turns into case 4 first to find beacon block, then turn into case 2

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the beacon node's ability to synchronize blocks by introducing a dedicated mechanism for handling unknown execution payload envelopes. It addresses scenarios where a block's parent payload is missing, allowing the node to proactively request and integrate these payloads from the network. This improves the robustness of block propagation and ensures the chain can continue to build even when execution layer data is temporarily unavailable.

Highlights

  • New Chain Event Handling: Introduced new ChainEvent types, unknownPayloadEnvelope and unknownParentPayload, to signal when execution payload envelopes are missing for known blocks or parent blocks, respectively.
  • Execution Payload Envelope Sync: Implemented new request-response methods (ExecutionPayloadEnvelopesByRange and ExecutionPayloadEnvelopesByRoot) in the network layer to fetch missing execution payload envelopes from peers.
  • Block Input Synchronization Logic: Enhanced the BlockInputSync module to manage a new pendingPayloads queue, actively search for and fetch missing parent payload envelopes, and re-evaluate blocks once their parent payloads become available.
  • Error Handling for Missing Parent Payloads: Added a new BlockErrorCode.PARENT_PAYLOAD_UNKNOWN to specifically identify and handle cases where a parent block is known but its corresponding full payload variant is missing, triggering a payload fetch instead of a block fetch.
  • New Metrics: Integrated new metrics to track the size of pending payloads, and the success/failure rates of payload envelope fetches, providing better observability into the new sync mechanism.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

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 introduces a new feature for handling unknown execution payloads for incoming blocks, specifically for the upcoming "gloas" fork. The changes are extensive, touching upon chain events, error handling, networking with new ReqResp methods, metrics, and the core synchronization logic in BlockInputSync. The implementation correctly parallels the existing logic for unknown block syncing. My review provides suggestions to improve the correctness of new metrics, enhance code clarity in the peer selection logic, and increase the robustness of the payload fetching mechanism by adding better error handling and peer management.

Comment on lines +627 to +665
private async fetchPayloadEnvelope(pending: PendingPayloadEnvelope): Promise<void> {
pending.status = "fetching";
pending.attempts++;
try {
const peer = this.peerBalancer.bestPeerForPendingColumns(new Set(), new Set())?.peerId;
if (!peer) {
pending.status = "pending";
return;
}

const envelopes = await this.network.sendExecutionPayloadEnvelopesByRoot(peer, [fromHex(pending.blockRootHex)]);
if (envelopes.length === 0) {
pending.status = "pending";
return;
}

const envelope = envelopes[0];
const payloadInput = this.chain.seenPayloadEnvelopeInputCache.get(pending.blockRootHex);
if (!payloadInput) {
throw Error(`PayloadEnvelopeInput missing for known block root ${pending.blockRootHex}`);
}

payloadInput.addPayloadEnvelope({
envelope,
source: PayloadEnvelopeInputSource.byRoot,
seenTimestampSec: Date.now() / 1000,
peerIdStr: peer,
});

if (payloadInput.isComplete()) {
await this.chain.processExecutionPayload(payloadInput);
}
this.metrics?.blockInputSync.payloadFetchSuccess.inc();
} catch (e) {
this.logger.debug("Error fetching payload envelope", {root: pending.blockRootHex}, e as Error);
pending.status = "pending";
this.metrics?.blockInputSync.payloadFetchError.inc();
}
}
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

The fetchPayloadEnvelope function currently tries to fetch from a single peer. If the fetch fails, it resets the status to "pending" for a later retry, but it doesn't penalize the failing peer or prevent it from being picked again for the next attempt. This could lead to inefficient retries with a faulty or slow peer.

Consider enhancing this function to be more robust, similar to fetchBlockInput. This could involve:

  • Adding a retry loop within fetchPayloadEnvelope to try multiple peers.
  • Tracking excluded peers for the duration of the fetch attempts for a given payload.
  • Applying peer scoring penalties for certain types of RequestError.

Comment on lines +687 to +690
payloadFetchSuccess: register.gauge({
name: "lodestar_sync_block_input_payload_fetch_success_total",
help: "Total successful payload envelope fetches",
}),
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 metric lodestar_sync_block_input_payload_fetch_success_total is defined as a gauge, but it's used as a counter since its value only ever increases. For metrics that represent a cumulative count, it's more idiomatic and correct to use a counter. This provides clearer intent and allows monitoring systems like Prometheus to apply counter-specific functions (e.g., rate()).

This feedback also applies to other new metrics in this file that are used as counters but defined as gauges, such as payloadFetchError, payloadRequests, and the metrics within awaitingEnvelopeGossipMessages.

Suggested change
payloadFetchSuccess: register.gauge({
name: "lodestar_sync_block_input_payload_fetch_success_total",
help: "Total successful payload envelope fetches",
}),
payloadFetchSuccess: register.counter({
name: "lodestar_sync_block_input_payload_fetch_success_total",
help: "Total successful payload envelope fetches",
}),

pending.status = "fetching";
pending.attempts++;
try {
const peer = this.peerBalancer.bestPeerForPendingColumns(new Set(), new Set())?.peerId;
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 method bestPeerForPendingColumns is being used here to find a peer for fetching a payload envelope, not columns. While it works because it's called with empty sets, the name is misleading and could cause confusion for future maintenance. Consider renaming bestPeerForPendingColumns to something more generic, like findBestPeer, and making the pendingColumns argument optional to better reflect its dual use.

@ensi321 ensi321 changed the base branch from unstable to nc/epbs-reqresp March 24, 2026 02:47
@github-actions
Copy link
Copy Markdown
Contributor

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: ebbabb7 Previous: 4118b5b Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 1.0777 ms/op 859.34 us/op 1.25
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 36.625 us/op 35.564 us/op 1.03
BLS verify - blst 1.2956 ms/op 869.14 us/op 1.49
BLS verifyMultipleSignatures 3 - blst 1.1847 ms/op 1.5364 ms/op 0.77
BLS verifyMultipleSignatures 8 - blst 2.4969 ms/op 2.1397 ms/op 1.17
BLS verifyMultipleSignatures 32 - blst 7.3568 ms/op 4.6331 ms/op 1.59
BLS verifyMultipleSignatures 64 - blst 10.580 ms/op 8.6927 ms/op 1.22
BLS verifyMultipleSignatures 128 - blst 17.031 ms/op 16.734 ms/op 1.02
BLS deserializing 10000 signatures 665.81 ms/op 636.22 ms/op 1.05
BLS deserializing 100000 signatures 6.6509 s/op 6.4072 s/op 1.04
BLS verifyMultipleSignatures - same message - 3 - blst 983.69 us/op 1.1151 ms/op 0.88
BLS verifyMultipleSignatures - same message - 8 - blst 999.12 us/op 1.0913 ms/op 0.92
BLS verifyMultipleSignatures - same message - 32 - blst 1.6646 ms/op 1.7005 ms/op 0.98
BLS verifyMultipleSignatures - same message - 64 - blst 2.5431 ms/op 2.6250 ms/op 0.97
BLS verifyMultipleSignatures - same message - 128 - blst 4.2958 ms/op 4.2664 ms/op 1.01
BLS aggregatePubkeys 32 - blst 19.529 us/op 17.963 us/op 1.09
BLS aggregatePubkeys 128 - blst 69.586 us/op 63.875 us/op 1.09
getSlashingsAndExits - default max 66.449 us/op 50.076 us/op 1.33
getSlashingsAndExits - 2k 319.81 us/op 346.03 us/op 0.92
isKnown best case - 1 super set check 198.00 ns/op 426.00 ns/op 0.46
isKnown normal case - 2 super set checks 187.00 ns/op 398.00 ns/op 0.47
isKnown worse case - 16 super set checks 193.00 ns/op 399.00 ns/op 0.48
validate api signedAggregateAndProof - struct 1.4058 ms/op 1.7047 ms/op 0.82
validate gossip signedAggregateAndProof - struct 2.1328 ms/op 1.7460 ms/op 1.22
batch validate gossip attestation - vc 640000 - chunk 32 120.83 us/op 112.08 us/op 1.08
batch validate gossip attestation - vc 640000 - chunk 64 104.89 us/op 94.327 us/op 1.11
batch validate gossip attestation - vc 640000 - chunk 128 95.097 us/op 86.384 us/op 1.10
batch validate gossip attestation - vc 640000 - chunk 256 89.961 us/op 85.376 us/op 1.05
bytes32 toHexString 337.00 ns/op 498.00 ns/op 0.68
bytes32 Buffer.toString(hex) 296.00 ns/op 400.00 ns/op 0.74
bytes32 Buffer.toString(hex) from Uint8Array 322.00 ns/op 513.00 ns/op 0.63
bytes32 Buffer.toString(hex) + 0x 235.00 ns/op 413.00 ns/op 0.57
Return object 10000 times 0.21890 ns/op 0.22740 ns/op 0.96
Throw Error 10000 times 4.0999 us/op 3.2060 us/op 1.28
toHex 136.77 ns/op 94.045 ns/op 1.45
Buffer.from 114.88 ns/op 84.485 ns/op 1.36
shared Buffer 73.919 ns/op 57.428 ns/op 1.29
fastMsgIdFn sha256 / 200 bytes 1.7910 us/op 1.7340 us/op 1.03
fastMsgIdFn h32 xxhash / 200 bytes 240.00 ns/op 377.00 ns/op 0.64
fastMsgIdFn h64 xxhash / 200 bytes 295.00 ns/op 431.00 ns/op 0.68
fastMsgIdFn sha256 / 1000 bytes 5.6580 us/op 4.9290 us/op 1.15
fastMsgIdFn h32 xxhash / 1000 bytes 272.00 ns/op 465.00 ns/op 0.58
fastMsgIdFn h64 xxhash / 1000 bytes 350.00 ns/op 479.00 ns/op 0.73
fastMsgIdFn sha256 / 10000 bytes 49.959 us/op 41.203 us/op 1.21
fastMsgIdFn h32 xxhash / 10000 bytes 1.3240 us/op 1.4490 us/op 0.91
fastMsgIdFn h64 xxhash / 10000 bytes 873.00 ns/op 1.0580 us/op 0.83
send data - 1000 256B messages 4.5624 ms/op 4.0639 ms/op 1.12
send data - 1000 512B messages 4.7030 ms/op 4.3025 ms/op 1.09
send data - 1000 1024B messages 5.1399 ms/op 4.6560 ms/op 1.10
send data - 1000 1200B messages 5.5371 ms/op 4.5497 ms/op 1.22
send data - 1000 2048B messages 5.6849 ms/op 4.9959 ms/op 1.14
send data - 1000 4096B messages 8.0506 ms/op 6.3659 ms/op 1.26
send data - 1000 16384B messages 42.232 ms/op 25.983 ms/op 1.63
send data - 1000 65536B messages 122.32 ms/op 84.257 ms/op 1.45
enrSubnets - fastDeserialize 64 bits 867.00 ns/op 977.00 ns/op 0.89
enrSubnets - ssz BitVector 64 bits 425.00 ns/op 483.00 ns/op 0.88
enrSubnets - fastDeserialize 4 bits 156.00 ns/op 320.00 ns/op 0.49
enrSubnets - ssz BitVector 4 bits 336.00 ns/op 508.00 ns/op 0.66
prioritizePeers score -10:0 att 32-0.1 sync 2-0 223.37 us/op 254.92 us/op 0.88
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 312.55 us/op 289.25 us/op 1.08
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 489.24 us/op 359.44 us/op 1.36
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 902.73 us/op 614.47 us/op 1.47
prioritizePeers score 0:0 att 64-1 sync 4-1 894.03 us/op 772.58 us/op 1.16
array of 16000 items push then shift 1.5302 us/op 1.3088 us/op 1.17
LinkedList of 16000 items push then shift 7.1220 ns/op 7.7320 ns/op 0.92
array of 16000 items push then pop 72.156 ns/op 67.628 ns/op 1.07
LinkedList of 16000 items push then pop 6.7490 ns/op 6.3600 ns/op 1.06
array of 24000 items push then shift 2.2764 us/op 1.9240 us/op 1.18
LinkedList of 24000 items push then shift 7.1200 ns/op 7.2050 ns/op 0.99
array of 24000 items push then pop 101.83 ns/op 95.412 ns/op 1.07
LinkedList of 24000 items push then pop 6.8080 ns/op 6.5680 ns/op 1.04
intersect bitArray bitLen 8 5.4680 ns/op 4.9440 ns/op 1.11
intersect array and set length 8 33.209 ns/op 29.998 ns/op 1.11
intersect bitArray bitLen 128 27.395 ns/op 26.482 ns/op 1.03
intersect array and set length 128 525.32 ns/op 501.14 ns/op 1.05
bitArray.getTrueBitIndexes() bitLen 128 1.2020 us/op 1.2400 us/op 0.97
bitArray.getTrueBitIndexes() bitLen 248 1.8030 us/op 1.9700 us/op 0.92
bitArray.getTrueBitIndexes() bitLen 512 3.7370 us/op 3.8620 us/op 0.97
Full columns - reconstruct all 6 blobs 234.57 us/op 268.67 us/op 0.87
Full columns - reconstruct half of the blobs out of 6 103.98 us/op 86.092 us/op 1.21
Full columns - reconstruct single blob out of 6 30.866 us/op 27.025 us/op 1.14
Half columns - reconstruct all 6 blobs 261.40 ms/op 247.90 ms/op 1.05
Half columns - reconstruct half of the blobs out of 6 134.73 ms/op 125.10 ms/op 1.08
Half columns - reconstruct single blob out of 6 49.104 ms/op 45.856 ms/op 1.07
Full columns - reconstruct all 10 blobs 338.29 us/op 344.64 us/op 0.98
Full columns - reconstruct half of the blobs out of 10 201.45 us/op 197.86 us/op 1.02
Full columns - reconstruct single blob out of 10 30.660 us/op 39.336 us/op 0.78
Half columns - reconstruct all 10 blobs 434.34 ms/op 402.45 ms/op 1.08
Half columns - reconstruct half of the blobs out of 10 220.00 ms/op 209.67 ms/op 1.05
Half columns - reconstruct single blob out of 10 48.174 ms/op 46.470 ms/op 1.04
Full columns - reconstruct all 20 blobs 628.31 us/op 802.60 us/op 0.78
Full columns - reconstruct half of the blobs out of 20 305.45 us/op 268.68 us/op 1.14
Full columns - reconstruct single blob out of 20 31.280 us/op 44.279 us/op 0.71
Half columns - reconstruct all 20 blobs 869.19 ms/op 803.15 ms/op 1.08
Half columns - reconstruct half of the blobs out of 20 428.52 ms/op 407.86 ms/op 1.05
Half columns - reconstruct single blob out of 20 47.926 ms/op 47.469 ms/op 1.01
Set add up to 64 items then delete first 2.0635 us/op 1.7192 us/op 1.20
OrderedSet add up to 64 items then delete first 2.9216 us/op 2.5299 us/op 1.15
Set add up to 64 items then delete last 2.2662 us/op 1.9174 us/op 1.18
OrderedSet add up to 64 items then delete last 3.4220 us/op 2.8570 us/op 1.20
Set add up to 64 items then delete middle 2.2982 us/op 1.9117 us/op 1.20
OrderedSet add up to 64 items then delete middle 4.9384 us/op 4.2632 us/op 1.16
Set add up to 128 items then delete first 4.6346 us/op 4.0069 us/op 1.16
OrderedSet add up to 128 items then delete first 6.6714 us/op 5.7642 us/op 1.16
Set add up to 128 items then delete last 4.6522 us/op 3.6401 us/op 1.28
OrderedSet add up to 128 items then delete last 6.8406 us/op 5.4230 us/op 1.26
Set add up to 128 items then delete middle 4.5141 us/op 3.6295 us/op 1.24
OrderedSet add up to 128 items then delete middle 13.115 us/op 11.777 us/op 1.11
Set add up to 256 items then delete first 9.5981 us/op 7.4716 us/op 1.28
OrderedSet add up to 256 items then delete first 13.898 us/op 12.169 us/op 1.14
Set add up to 256 items then delete last 9.2236 us/op 7.2308 us/op 1.28
OrderedSet add up to 256 items then delete last 14.185 us/op 11.314 us/op 1.25
Set add up to 256 items then delete middle 9.0254 us/op 7.1746 us/op 1.26
OrderedSet add up to 256 items then delete middle 39.425 us/op 35.915 us/op 1.10
pass gossip attestations to forkchoice per slot 575.46 us/op 409.35 us/op 1.41
computeDeltas 1400000 validators 0% inactive 13.843 ms/op 12.525 ms/op 1.11
computeDeltas 1400000 validators 10% inactive 12.863 ms/op 12.866 ms/op 1.00
computeDeltas 1400000 validators 20% inactive 11.956 ms/op 11.100 ms/op 1.08
computeDeltas 1400000 validators 50% inactive 9.3102 ms/op 8.3558 ms/op 1.11
computeDeltas 2100000 validators 0% inactive 20.657 ms/op 18.651 ms/op 1.11
computeDeltas 2100000 validators 10% inactive 19.347 ms/op 17.919 ms/op 1.08
computeDeltas 2100000 validators 20% inactive 17.937 ms/op 16.759 ms/op 1.07
computeDeltas 2100000 validators 50% inactive 13.954 ms/op 9.4176 ms/op 1.48
altair processAttestation - setStatus - 1/6 committees join 537.00 ns/op 746.00 ns/op 0.72
altair processAttestation - setStatus - 1/3 committees join 915.00 ns/op 1.0200 us/op 0.90
altair processAttestation - setStatus - 1/2 committees join 1.2530 us/op 1.3160 us/op 0.95
altair processAttestation - setStatus - 2/3 committees join 1.4690 us/op 1.4870 us/op 0.99
altair processAttestation - setStatus - 4/5 committees join 1.6910 us/op 1.6230 us/op 1.04
altair processAttestation - setStatus - 100% committees join 1.9120 us/op 1.8600 us/op 1.03
phase0 processBlock - 250000 vs - 7PWei normalcase 1.8907 ms/op 1.4499 ms/op 1.30
phase0 processBlock - 250000 vs - 7PWei worstcase 21.203 ms/op 20.346 ms/op 1.04
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 9.0130 us/op 3.3280 us/op 2.71
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 52.386 us/op 37.166 us/op 1.41
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 16.219 us/op 9.8410 us/op 1.65
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 10.596 us/op 6.0880 us/op 1.74
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 132.16 us/op 145.94 us/op 0.91
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.7444 ms/op 1.2948 ms/op 1.35
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 2.0392 ms/op 1.8016 ms/op 1.13
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 2.0469 ms/op 1.8311 ms/op 1.12
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 4.2360 ms/op 3.7953 ms/op 1.12
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.5191 ms/op 2.0600 ms/op 1.22
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 4.7132 ms/op 4.0055 ms/op 1.18
Tree 40 250000 create 392.96 ms/op 307.38 ms/op 1.28
Tree 40 250000 get(125000) 127.69 ns/op 97.529 ns/op 1.31
Tree 40 250000 set(125000) 1.2179 us/op 979.37 ns/op 1.24
Tree 40 250000 toArray() 13.837 ms/op 10.531 ms/op 1.31
Tree 40 250000 iterate all - toArray() + loop 13.449 ms/op 9.1889 ms/op 1.46
Tree 40 250000 iterate all - get(i) 43.260 ms/op 35.395 ms/op 1.22
Array 250000 create 2.4343 ms/op 2.0790 ms/op 1.17
Array 250000 clone - spread 802.35 us/op 640.53 us/op 1.25
Array 250000 get(125000) 0.45300 ns/op 0.51300 ns/op 0.88
Array 250000 set(125000) 0.35300 ns/op 0.51400 ns/op 0.69
Array 250000 iterate all - loop 60.704 us/op 57.574 us/op 1.05
phase0 afterProcessEpoch - 250000 vs - 7PWei 41.854 ms/op 38.636 ms/op 1.08
Array.fill - length 1000000 2.9645 ms/op 2.0487 ms/op 1.45
Array push - length 1000000 10.706 ms/op 7.7681 ms/op 1.38
Array.get 0.21437 ns/op 0.20129 ns/op 1.06
Uint8Array.get 0.21777 ns/op 0.19996 ns/op 1.09
phase0 beforeProcessEpoch - 250000 vs - 7PWei 18.494 ms/op 15.470 ms/op 1.20
altair processEpoch - mainnet_e81889 338.83 ms/op 271.17 ms/op 1.25
mainnet_e81889 - altair beforeProcessEpoch 33.999 ms/op 18.152 ms/op 1.87
mainnet_e81889 - altair processJustificationAndFinalization 6.7070 us/op 7.8630 us/op 0.85
mainnet_e81889 - altair processInactivityUpdates 3.8542 ms/op 4.3948 ms/op 0.88
mainnet_e81889 - altair processRewardsAndPenalties 19.150 ms/op 16.300 ms/op 1.17
mainnet_e81889 - altair processRegistryUpdates 632.00 ns/op 820.00 ns/op 0.77
mainnet_e81889 - altair processSlashings 169.00 ns/op 376.00 ns/op 0.45
mainnet_e81889 - altair processEth1DataReset 167.00 ns/op 360.00 ns/op 0.46
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.5312 ms/op 1.8726 ms/op 0.82
mainnet_e81889 - altair processSlashingsReset 1.0600 us/op 958.00 ns/op 1.11
mainnet_e81889 - altair processRandaoMixesReset 1.1520 us/op 1.5980 us/op 0.72
mainnet_e81889 - altair processHistoricalRootsUpdate 196.00 ns/op 385.00 ns/op 0.51
mainnet_e81889 - altair processParticipationFlagUpdates 503.00 ns/op 799.00 ns/op 0.63
mainnet_e81889 - altair processSyncCommitteeUpdates 137.00 ns/op 328.00 ns/op 0.42
mainnet_e81889 - altair afterProcessEpoch 42.964 ms/op 40.485 ms/op 1.06
capella processEpoch - mainnet_e217614 813.46 ms/op 786.43 ms/op 1.03
mainnet_e217614 - capella beforeProcessEpoch 53.798 ms/op 58.152 ms/op 0.93
mainnet_e217614 - capella processJustificationAndFinalization 5.9840 us/op 4.6800 us/op 1.28
mainnet_e217614 - capella processInactivityUpdates 17.106 ms/op 12.716 ms/op 1.35
mainnet_e217614 - capella processRewardsAndPenalties 102.99 ms/op 105.41 ms/op 0.98
mainnet_e217614 - capella processRegistryUpdates 5.5420 us/op 4.7020 us/op 1.18
mainnet_e217614 - capella processSlashings 223.00 ns/op 454.00 ns/op 0.49
mainnet_e217614 - capella processEth1DataReset 165.00 ns/op 371.00 ns/op 0.44
mainnet_e217614 - capella processEffectiveBalanceUpdates 11.230 ms/op 5.4837 ms/op 2.05
mainnet_e217614 - capella processSlashingsReset 995.00 ns/op 1.0090 us/op 0.99
mainnet_e217614 - capella processRandaoMixesReset 1.3610 us/op 1.2620 us/op 1.08
mainnet_e217614 - capella processHistoricalRootsUpdate 197.00 ns/op 377.00 ns/op 0.52
mainnet_e217614 - capella processParticipationFlagUpdates 529.00 ns/op 732.00 ns/op 0.72
mainnet_e217614 - capella afterProcessEpoch 110.38 ms/op 111.66 ms/op 0.99
phase0 processEpoch - mainnet_e58758 261.00 ms/op 242.94 ms/op 1.07
mainnet_e58758 - phase0 beforeProcessEpoch 52.962 ms/op 55.217 ms/op 0.96
mainnet_e58758 - phase0 processJustificationAndFinalization 6.5190 us/op 6.1270 us/op 1.06
mainnet_e58758 - phase0 processRewardsAndPenalties 18.342 ms/op 18.552 ms/op 0.99
mainnet_e58758 - phase0 processRegistryUpdates 2.7970 us/op 2.4770 us/op 1.13
mainnet_e58758 - phase0 processSlashings 202.00 ns/op 370.00 ns/op 0.55
mainnet_e58758 - phase0 processEth1DataReset 172.00 ns/op 362.00 ns/op 0.48
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 1.0891 ms/op 986.80 us/op 1.10
mainnet_e58758 - phase0 processSlashingsReset 904.00 ns/op 1.1850 us/op 0.76
mainnet_e58758 - phase0 processRandaoMixesReset 1.0740 us/op 1.1730 us/op 0.92
mainnet_e58758 - phase0 processHistoricalRootsUpdate 210.00 ns/op 373.00 ns/op 0.56
mainnet_e58758 - phase0 processParticipationRecordUpdates 858.00 ns/op 1.2420 us/op 0.69
mainnet_e58758 - phase0 afterProcessEpoch 34.639 ms/op 35.064 ms/op 0.99
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.5793 ms/op 897.05 us/op 1.76
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.5752 ms/op 1.5577 ms/op 1.01
altair processInactivityUpdates - 250000 normalcase 92.730 us/op 63.080 us/op 1.47
altair processInactivityUpdates - 250000 worstcase 91.415 us/op 55.881 us/op 1.64
phase0 processRegistryUpdates - 250000 normalcase 8.6010 us/op 6.3110 us/op 1.36
phase0 processRegistryUpdates - 250000 badcase_full_deposits 372.83 us/op 257.21 us/op 1.45
phase0 processRegistryUpdates - 250000 worstcase 0.5 97.079 ms/op 69.568 ms/op 1.40
altair processRewardsAndPenalties - 250000 normalcase 95.918 us/op 84.724 us/op 1.13
altair processRewardsAndPenalties - 250000 worstcase 74.218 us/op 88.586 us/op 0.84
phase0 getAttestationDeltas - 250000 normalcase 6.4855 ms/op 4.8291 ms/op 1.34
phase0 getAttestationDeltas - 250000 worstcase 6.6671 ms/op 4.9434 ms/op 1.35
phase0 processSlashings - 250000 worstcase 79.415 us/op 90.225 us/op 0.88
altair processSyncCommitteeUpdates - 250000 8.4120 ms/op 7.4774 ms/op 1.12
BeaconState.hashTreeRoot - No change 265.00 ns/op 402.00 ns/op 0.66
BeaconState.hashTreeRoot - 1 full validator 94.608 us/op 77.748 us/op 1.22
BeaconState.hashTreeRoot - 32 full validator 1.0453 ms/op 928.84 us/op 1.13
BeaconState.hashTreeRoot - 512 full validator 10.953 ms/op 6.8249 ms/op 1.60
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 111.50 us/op 98.580 us/op 1.13
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.4581 ms/op 1.4768 ms/op 0.99
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 15.241 ms/op 17.689 ms/op 0.86
BeaconState.hashTreeRoot - 1 balances 89.597 us/op 75.327 us/op 1.19
BeaconState.hashTreeRoot - 32 balances 769.14 us/op 722.02 us/op 1.07
BeaconState.hashTreeRoot - 512 balances 7.4301 ms/op 6.0405 ms/op 1.23
BeaconState.hashTreeRoot - 250000 balances 162.38 ms/op 146.85 ms/op 1.11
aggregationBits - 2048 els - zipIndexesInBitList 20.369 us/op 19.452 us/op 1.05
regular array get 100000 times 24.219 us/op 22.726 us/op 1.07
wrappedArray get 100000 times 24.175 us/op 22.730 us/op 1.06
arrayWithProxy get 100000 times 14.706 ms/op 12.844 ms/op 1.15
ssz.Root.equals 23.420 ns/op 22.096 ns/op 1.06
byteArrayEquals 22.979 ns/op 21.855 ns/op 1.05
Buffer.compare 9.7360 ns/op 9.1900 ns/op 1.06
processSlot - 1 slots 10.577 us/op 10.277 us/op 1.03
processSlot - 32 slots 2.7699 ms/op 2.0634 ms/op 1.34
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 5.2080 ms/op 6.5515 ms/op 0.79
getCommitteeAssignments - req 1 vs - 250000 vc 1.8248 ms/op 1.6474 ms/op 1.11
getCommitteeAssignments - req 100 vs - 250000 vc 3.5773 ms/op 3.3637 ms/op 1.06
getCommitteeAssignments - req 1000 vs - 250000 vc 3.8470 ms/op 3.6219 ms/op 1.06
findModifiedValidators - 10000 modified validators 596.31 ms/op 461.81 ms/op 1.29
findModifiedValidators - 1000 modified validators 291.89 ms/op 317.22 ms/op 0.92
findModifiedValidators - 100 modified validators 272.46 ms/op 234.79 ms/op 1.16
findModifiedValidators - 10 modified validators 158.28 ms/op 116.93 ms/op 1.35
findModifiedValidators - 1 modified validators 172.25 ms/op 151.02 ms/op 1.14
findModifiedValidators - no difference 157.11 ms/op 131.98 ms/op 1.19
migrate state 1500000 validators, 3400 modified, 2000 new 353.37 ms/op 306.90 ms/op 1.15
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 4.1200 ns/op 5.7900 ns/op 0.71
state getBlockRootAtSlot - 250000 vs - 7PWei 375.30 ns/op 441.88 ns/op 0.85
computeProposerIndex 100000 validators 1.5348 ms/op 1.3709 ms/op 1.12
getNextSyncCommitteeIndices 1000 validators 3.2958 ms/op 2.8783 ms/op 1.15
getNextSyncCommitteeIndices 10000 validators 3.2928 ms/op 2.8753 ms/op 1.15
getNextSyncCommitteeIndices 100000 validators 3.2908 ms/op 2.9102 ms/op 1.13
computeProposers - vc 250000 599.86 us/op 554.79 us/op 1.08
computeEpochShuffling - vc 250000 40.318 ms/op 38.714 ms/op 1.04
getNextSyncCommittee - vc 250000 10.447 ms/op 9.6262 ms/op 1.09
nodejs block root to RootHex using toHex 136.79 ns/op 113.85 ns/op 1.20
nodejs block root to RootHex using toRootHex 85.057 ns/op 72.780 ns/op 1.17
nodejs fromHex(blob) 325.69 us/op 242.64 us/op 1.34
nodejs fromHexInto(blob) 681.99 us/op 633.33 us/op 1.08
nodejs block root to RootHex using the deprecated toHexString 548.89 ns/op 550.49 ns/op 1.00
nodejs byteArrayEquals 32 bytes (block root) 27.664 ns/op 29.427 ns/op 0.94
nodejs byteArrayEquals 48 bytes (pubkey) 39.572 ns/op 42.629 ns/op 0.93
nodejs byteArrayEquals 96 bytes (signature) 39.156 ns/op 35.458 ns/op 1.10
nodejs byteArrayEquals 1024 bytes 45.434 ns/op 41.658 ns/op 1.09
nodejs byteArrayEquals 131072 bytes (blob) 1.8185 us/op 1.7686 us/op 1.03
browser block root to RootHex using toHex 159.28 ns/op 152.14 ns/op 1.05
browser block root to RootHex using toRootHex 149.25 ns/op 139.50 ns/op 1.07
browser fromHex(blob) 1.0756 ms/op 974.12 us/op 1.10
browser fromHexInto(blob) 681.28 us/op 646.67 us/op 1.05
browser block root to RootHex using the deprecated toHexString 400.21 ns/op 371.99 ns/op 1.08
browser byteArrayEquals 32 bytes (block root) 30.191 ns/op 30.420 ns/op 0.99
browser byteArrayEquals 48 bytes (pubkey) 45.081 ns/op 40.954 ns/op 1.10
browser byteArrayEquals 96 bytes (signature) 82.896 ns/op 76.668 ns/op 1.08
browser byteArrayEquals 1024 bytes 775.08 ns/op 760.22 ns/op 1.02
browser byteArrayEquals 131072 bytes (blob) 98.423 us/op 95.707 us/op 1.03

by benchmarkbot/action

Base automatically changed from nc/epbs-reqresp to unstable March 25, 2026 14:25
private onUnknownPayloadEnvelope = (data: ChainEventData[ChainEvent.unknownPayloadEnvelope]): void => {
try {
const {blockRootHex, peer} = data;
const block = this.chain.forkChoice.getBlockHexDefaultStatus(blockRootHex);
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.

if there is no block we should search for it via addByRootHex()

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 ChainEvent.unknownPayloadEnvelope should have a PayloadEnvelopeInput instead of a SignedExecutionPayloadEnvelope
BlockInputSync should then enhance downloadByRoot to download envelope + DataColumnSidecars and track it in PayloadEnvelopeInput to know when data is available

but there is already a TODO here https://github.com/ChainSafe/lodestar/pull/9025/changes#diff-c702dde1ffa90a6d2720c7b4930bc5c08cc1f302cfd09ed9e74d80755b92da8fR850

we cannot create aPayloadEnvelopeInput without a bid/proposerIndex. Maybe make these fields optional? also maybe we store more than needed

that is to also handle the (rare) case where payload/envelope comes even before the block

ensi321 added a commit that referenced this pull request Apr 15, 2026
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>
@ensi321 ensi321 mentioned this pull request Apr 15, 2026
5 tasks
ensi321 added a commit that referenced this pull request Apr 16, 2026
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>
ensi321 added a commit that referenced this pull request Apr 20, 2026
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>
@ensi321
Copy link
Copy Markdown
Contributor Author

ensi321 commented Apr 22, 2026

superseded by #9241

@ensi321 ensi321 closed this Apr 22, 2026
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 22, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.32%. Comparing base (4118b5b) to head (968e53f).
⚠️ Report is 71 commits behind head on unstable.

Additional details and impacted files
@@            Coverage Diff            @@
##           unstable    #9102   +/-   ##
=========================================
  Coverage     52.32%   52.32%           
=========================================
  Files           848      848           
  Lines         62020    62016    -4     
  Branches       4538     4538           
=========================================
- Hits          32449    32447    -2     
+ Misses        29506    29504    -2     
  Partials         65       65           
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

ensi321 added a commit that referenced this pull request Apr 22, 2026
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants