Skip to content

Conversation

@ensi321
Copy link
Contributor

@ensi321 ensi321 commented Nov 17, 2025

  • Add new gossip topics execution_payload, payload_attestation_message and execution_payload_bid
  • Add gossip validation for each topic
  • Add gossip handling for payload_attestation_message and execution_payload_bid
  • Add PayloadAttestationPool, ExecutionPayloadBidPool
  • Update gossip validation for beacon_aggregate_and_proof and beacon_block
  • Add seen cache for PTC and execution payload
  • Extends ProtoBlock to store bid info

TODO in next PRs:

  • Add gossip handling for execution_payload
  • Update req/resp
  • Update data column

@ensi321 ensi321 requested a review from a team as a code owner November 17, 2025 19:33
@ensi321 ensi321 marked this pull request as draft November 17, 2025 19:34
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ensi321, 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 lays the groundwork for implementing the ePBS (enshrined Proposer-Builder Separation) networking specification, likely for an upcoming Ethereum fork referred to as "Gloas." It introduces new data structures and validation rules for execution payload bids, envelopes, and payload attestations, and integrates these new message types into the beacon node's gossip network for propagation and processing. This is a Work In Progress, building upon a previous PR.

Highlights

  • New Error Definitions for ePBS: Introduced specific error codes and types for ExecutionPayloadBid, ExecutionPayloadEnvelope, and PayloadAttestation to support the new ePBS networking specification.
  • Attestation Validation Updates: Modified existing attestation validation logic in aggregateAndProof.ts and attestation.ts to incorporate "Gloas" fork-specific rules, particularly concerning attestation.data.index values.
  • Block Validation Adjustments: Updated block validation in block.ts to conditionally apply existing checks (e.g., blobKzgCommitments length, execution payload timestamp) based on the "Gloas" fork, indicating a new phase in block processing.
  • New ePBS Validation Functions: Added dedicated validation functions for ExecutionPayloadBid, ExecutionPayloadEnvelope, and PayloadAttestationMessage to ensure compliance with the ePBS specification.
  • Gossip Network Integration: Expanded the gossip network interface and handlers to support new message types (execution_payload, payload_attestation_message, execution_payload_bid), enabling the beacon node to process ePBS-related gossip.
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.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

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
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 networking specifications for ePBS, including new gossip message types, error codes, and validation logic. The changes are extensive and touch upon core validation and networking components. My review has identified a critical logic error in attestation validation, several typos in new error codes that should be corrected for consistency, and some areas for code improvement regarding duplication and import consistency. Overall, the changes are a good step towards implementing the ePBS spec, but the identified issues, especially the critical one, should be addressed.

@gemini-code-assist
Copy link
Contributor

Warning

Gemini encountered an error creating the review. You can try again by commenting /gemini review.

@ensi321 ensi321 changed the title [WIP] feat: implement ePBS networking spec feat: implement ePBS networking spec Dec 9, 2025
@ensi321 ensi321 changed the title feat: implement ePBS networking spec feat: implement ePBS gossip topics Dec 9, 2025
@ensi321
Copy link
Contributor Author

ensi321 commented Dec 9, 2025

/gemini review

Copy link
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 new gossip topics for ePBS, including their validation logic and a new PayloadAttestationPool. The changes are extensive and align with the feature's goals. However, I've identified a critical bug in the PayloadAttestationPool implementation where it incorrectly selects attestations, which could lead to including the least-voted attestations in a block instead of the most-voted ones. Please see the detailed comment for the fix.

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@github-actions
Copy link
Contributor

github-actions bot commented Dec 9, 2025

⚠️ 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: 5e05546 Previous: b255111 Ratio
400 bytes - uncompress - snappyjs 19.810 us/op 1.5489 us/op 12.79
1000 bytes - uncompress - snappy-wasm 23.325 us/op 1.3217 us/op 17.65
Full benchmark results
Benchmark suite Current: 5e05546 Previous: b255111 Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 1.1531 ms/op 1.1953 ms/op 0.96
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 37.855 us/op 39.675 us/op 0.95
BLS verify - blst 1.3948 ms/op 988.67 us/op 1.41
BLS verifyMultipleSignatures 3 - blst 2.2479 ms/op 1.3100 ms/op 1.72
BLS verifyMultipleSignatures 8 - blst 2.4925 ms/op 1.9223 ms/op 1.30
BLS verifyMultipleSignatures 32 - blst 7.2805 ms/op 5.8637 ms/op 1.24
BLS verifyMultipleSignatures 64 - blst 10.498 ms/op 11.040 ms/op 0.95
BLS verifyMultipleSignatures 128 - blst 17.195 ms/op 18.263 ms/op 0.94
BLS deserializing 10000 signatures 666.82 ms/op 701.93 ms/op 0.95
BLS deserializing 100000 signatures 6.9133 s/op 7.0895 s/op 0.98
BLS verifyMultipleSignatures - same message - 3 - blst 1.5646 ms/op 1.0116 ms/op 1.55
BLS verifyMultipleSignatures - same message - 8 - blst 1.6172 ms/op 1.1463 ms/op 1.41
BLS verifyMultipleSignatures - same message - 32 - blst 1.7893 ms/op 1.7717 ms/op 1.01
BLS verifyMultipleSignatures - same message - 64 - blst 2.6982 ms/op 2.6999 ms/op 1.00
BLS verifyMultipleSignatures - same message - 128 - blst 4.3788 ms/op 4.7673 ms/op 0.92
BLS aggregatePubkeys 32 - blst 18.880 us/op 19.926 us/op 0.95
BLS aggregatePubkeys 128 - blst 68.696 us/op 71.401 us/op 0.96
getSlashingsAndExits - default max 66.486 us/op 71.738 us/op 0.93
getSlashingsAndExits - 2k 313.91 us/op 331.96 us/op 0.95
isKnown best case - 1 super set check 197.00 ns/op 209.00 ns/op 0.94
isKnown normal case - 2 super set checks 205.00 ns/op 215.00 ns/op 0.95
isKnown worse case - 16 super set checks 202.00 ns/op 203.00 ns/op 1.00
InMemoryCheckpointStateCache - add get delete 3.4640 us/op 2.4740 us/op 1.40
validate api signedAggregateAndProof - struct 2.5890 ms/op 1.4506 ms/op 1.78
validate gossip signedAggregateAndProof - struct 2.5878 ms/op 1.4720 ms/op 1.76
batch validate gossip attestation - vc 640000 - chunk 32 116.22 us/op 131.45 us/op 0.88
batch validate gossip attestation - vc 640000 - chunk 64 103.05 us/op 107.03 us/op 0.96
batch validate gossip attestation - vc 640000 - chunk 128 104.05 us/op 108.66 us/op 0.96
batch validate gossip attestation - vc 640000 - chunk 256 89.376 us/op 94.555 us/op 0.95
bytes32 toHexString 357.00 ns/op 420.00 ns/op 0.85
bytes32 Buffer.toString(hex) 311.00 ns/op 257.00 ns/op 1.21
bytes32 Buffer.toString(hex) from Uint8Array 638.00 ns/op 344.00 ns/op 1.85
bytes32 Buffer.toString(hex) + 0x 288.00 ns/op 264.00 ns/op 1.09
Object access 1 prop 0.14600 ns/op 0.13500 ns/op 1.08
Map access 1 prop 0.16300 ns/op 0.13300 ns/op 1.23
Object get x1000 5.4420 ns/op 5.4850 ns/op 0.99
Map get x1000 0.37200 ns/op 0.38000 ns/op 0.98
Object set x1000 29.625 ns/op 30.119 ns/op 0.98
Map set x1000 20.334 ns/op 21.062 ns/op 0.97
Return object 10000 times 0.22840 ns/op 0.23540 ns/op 0.97
Throw Error 10000 times 4.1460 us/op 4.1240 us/op 1.01
toHex 160.01 ns/op 144.21 ns/op 1.11
Buffer.from 147.40 ns/op 128.35 ns/op 1.15
shared Buffer 89.820 ns/op 82.871 ns/op 1.08
fastMsgIdFn sha256 / 200 bytes 1.8480 us/op 1.8670 us/op 0.99
fastMsgIdFn h32 xxhash / 200 bytes 205.00 ns/op 190.00 ns/op 1.08
fastMsgIdFn h64 xxhash / 200 bytes 269.00 ns/op 261.00 ns/op 1.03
fastMsgIdFn sha256 / 1000 bytes 6.0080 us/op 6.0170 us/op 1.00
fastMsgIdFn h32 xxhash / 1000 bytes 362.00 ns/op 285.00 ns/op 1.27
fastMsgIdFn h64 xxhash / 1000 bytes 397.00 ns/op 355.00 ns/op 1.12
fastMsgIdFn sha256 / 10000 bytes 52.420 us/op 52.977 us/op 0.99
fastMsgIdFn h32 xxhash / 10000 bytes 1.4100 us/op 1.3930 us/op 1.01
fastMsgIdFn h64 xxhash / 10000 bytes 943.00 ns/op 1.0480 us/op 0.90
100 bytes - compress - snappyjs 1.1710 us/op 1.3321 us/op 0.88
100 bytes - compress - snappy 1.0753 us/op 1.1157 us/op 0.96
100 bytes - compress - snappy-wasm 639.57 ns/op 635.00 ns/op 1.01
100 bytes - compress - snappy-wasm - prealloc 1.7840 us/op 1.1270 us/op 1.58
200 bytes - compress - snappyjs 1.5717 us/op 1.7517 us/op 0.90
200 bytes - compress - snappy 1.2134 us/op 1.3654 us/op 0.89
200 bytes - compress - snappy-wasm 782.01 ns/op 726.22 ns/op 1.08
200 bytes - compress - snappy-wasm - prealloc 1.4408 us/op 1.8413 us/op 0.78
300 bytes - compress - snappyjs 2.0572 us/op 2.3622 us/op 0.87
300 bytes - compress - snappy 1.6654 us/op 2.4523 us/op 0.68
300 bytes - compress - snappy-wasm 876.27 ns/op 1.0585 us/op 0.83
300 bytes - compress - snappy-wasm - prealloc 1.6930 us/op 2.0140 us/op 0.84
400 bytes - compress - snappyjs 2.0814 us/op 2.1820 us/op 0.95
400 bytes - compress - snappy 1.4450 us/op 1.9656 us/op 0.74
400 bytes - compress - snappy-wasm 1.0338 us/op 1.1756 us/op 0.88
400 bytes - compress - snappy-wasm - prealloc 1.9902 us/op 1.9096 us/op 1.04
500 bytes - compress - snappyjs 2.6203 us/op 2.4346 us/op 1.08
500 bytes - compress - snappy 1.5219 us/op 1.4805 us/op 1.03
500 bytes - compress - snappy-wasm 1.2204 us/op 1.0033 us/op 1.22
500 bytes - compress - snappy-wasm - prealloc 1.6244 us/op 1.3324 us/op 1.22
1000 bytes - compress - snappyjs 5.3413 us/op 4.4733 us/op 1.19
1000 bytes - compress - snappy 1.6893 us/op 1.5751 us/op 1.07
1000 bytes - compress - snappy-wasm 1.7586 us/op 1.9342 us/op 0.91
1000 bytes - compress - snappy-wasm - prealloc 1.7158 us/op 1.7710 us/op 0.97
10000 bytes - compress - snappyjs 39.688 us/op 24.701 us/op 1.61
10000 bytes - compress - snappy 27.970 us/op 24.558 us/op 1.14
10000 bytes - compress - snappy-wasm 21.042 us/op 30.597 us/op 0.69
10000 bytes - compress - snappy-wasm - prealloc 29.421 us/op 26.325 us/op 1.12
100 bytes - uncompress - snappyjs 676.12 ns/op 749.52 ns/op 0.90
100 bytes - uncompress - snappy 1.0747 us/op 1.1171 us/op 0.96
100 bytes - uncompress - snappy-wasm 571.47 ns/op 654.07 ns/op 0.87
100 bytes - uncompress - snappy-wasm - prealloc 1.1349 us/op 1.0964 us/op 1.04
200 bytes - uncompress - snappyjs 792.04 ns/op 945.87 ns/op 0.84
200 bytes - uncompress - snappy 1.3363 us/op 1.2842 us/op 1.04
200 bytes - uncompress - snappy-wasm 1.0629 us/op 876.15 ns/op 1.21
200 bytes - uncompress - snappy-wasm - prealloc 1.5644 us/op 995.79 ns/op 1.57
300 bytes - uncompress - snappyjs 1.3756 us/op 1.0431 us/op 1.32
300 bytes - uncompress - snappy 1.6697 us/op 1.5321 us/op 1.09
300 bytes - uncompress - snappy-wasm 879.87 ns/op 1.0311 us/op 0.85
300 bytes - uncompress - snappy-wasm - prealloc 1.1917 us/op 1.3159 us/op 0.91
400 bytes - uncompress - snappyjs 19.810 us/op 1.5489 us/op 12.79
400 bytes - uncompress - snappy 1.1804 us/op 3.0416 us/op 0.39
400 bytes - uncompress - snappy-wasm 834.05 ns/op 858.05 ns/op 0.97
400 bytes - uncompress - snappy-wasm - prealloc 1.1436 us/op 1.0731 us/op 1.07
500 bytes - uncompress - snappyjs 1.1608 us/op 1.8794 us/op 0.62
500 bytes - uncompress - snappy 1.2972 us/op 1.3211 us/op 0.98
500 bytes - uncompress - snappy-wasm 1.1177 us/op 977.64 ns/op 1.14
500 bytes - uncompress - snappy-wasm - prealloc 1.0730 us/op 1.4029 us/op 0.76
1000 bytes - uncompress - snappyjs 2.0238 us/op 2.4616 us/op 0.82
1000 bytes - uncompress - snappy 1.5819 us/op 1.5818 us/op 1.00
1000 bytes - uncompress - snappy-wasm 23.325 us/op 1.3217 us/op 17.65
1000 bytes - uncompress - snappy-wasm - prealloc 1.0845 us/op 1.2055 us/op 0.90
10000 bytes - uncompress - snappyjs 19.378 us/op 35.218 us/op 0.55
10000 bytes - uncompress - snappy 30.924 us/op 31.715 us/op 0.98
10000 bytes - uncompress - snappy-wasm 24.333 us/op 16.747 us/op 1.45
10000 bytes - uncompress - snappy-wasm - prealloc 29.024 us/op 17.191 us/op 1.69
send data - 1000 256B messages 17.098 ms/op 15.037 ms/op 1.14
send data - 1000 512B messages 20.761 ms/op 19.321 ms/op 1.07
send data - 1000 1024B messages 25.269 ms/op 23.642 ms/op 1.07
send data - 1000 1200B messages 28.826 ms/op 29.635 ms/op 0.97
send data - 1000 2048B messages 33.231 ms/op 34.772 ms/op 0.96
send data - 1000 4096B messages 33.471 ms/op 63.751 ms/op 0.53
send data - 1000 16384B messages 144.36 ms/op 144.54 ms/op 1.00
send data - 1000 65536B messages 315.04 ms/op 305.07 ms/op 1.03
enrSubnets - fastDeserialize 64 bits 901.00 ns/op 923.00 ns/op 0.98
enrSubnets - ssz BitVector 64 bits 366.00 ns/op 349.00 ns/op 1.05
enrSubnets - fastDeserialize 4 bits 142.00 ns/op 137.00 ns/op 1.04
enrSubnets - ssz BitVector 4 bits 359.00 ns/op 383.00 ns/op 0.94
prioritizePeers score -10:0 att 32-0.1 sync 2-0 260.72 us/op 235.39 us/op 1.11
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 291.58 us/op 263.41 us/op 1.11
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 452.99 us/op 407.93 us/op 1.11
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 782.57 us/op 765.87 us/op 1.02
prioritizePeers score 0:0 att 64-1 sync 4-1 849.43 us/op 949.09 us/op 0.89
array of 16000 items push then shift 1.5992 us/op 1.6514 us/op 0.97
LinkedList of 16000 items push then shift 7.1840 ns/op 7.7390 ns/op 0.93
array of 16000 items push then pop 73.022 ns/op 79.314 ns/op 0.92
LinkedList of 16000 items push then pop 6.8980 ns/op 7.4030 ns/op 0.93
array of 24000 items push then shift 2.3327 us/op 2.4584 us/op 0.95
LinkedList of 24000 items push then shift 7.2650 ns/op 7.9500 ns/op 0.91
array of 24000 items push then pop 104.39 ns/op 107.68 ns/op 0.97
LinkedList of 24000 items push then pop 7.0360 ns/op 7.4350 ns/op 0.95
intersect bitArray bitLen 8 5.6340 ns/op 5.7880 ns/op 0.97
intersect array and set length 8 33.278 ns/op 34.079 ns/op 0.98
intersect bitArray bitLen 128 27.705 ns/op 29.100 ns/op 0.95
intersect array and set length 128 552.08 ns/op 563.87 ns/op 0.98
bitArray.getTrueBitIndexes() bitLen 128 1.0610 us/op 1.0950 us/op 0.97
bitArray.getTrueBitIndexes() bitLen 248 1.7860 us/op 2.0800 us/op 0.86
bitArray.getTrueBitIndexes() bitLen 512 4.6700 us/op 4.3320 us/op 1.08
Full columns - reconstruct all 6 blobs 323.86 us/op 320.97 us/op 1.01
Full columns - reconstruct half of the blobs out of 6 141.98 us/op 103.75 us/op 1.37
Full columns - reconstruct single blob out of 6 39.298 us/op 31.952 us/op 1.23
Half columns - reconstruct all 6 blobs 278.97 ms/op 268.12 ms/op 1.04
Half columns - reconstruct half of the blobs out of 6 137.35 ms/op 133.52 ms/op 1.03
Half columns - reconstruct single blob out of 6 52.444 ms/op 50.049 ms/op 1.05
Full columns - reconstruct all 10 blobs 331.69 us/op 405.82 us/op 0.82
Full columns - reconstruct half of the blobs out of 10 216.95 us/op 153.72 us/op 1.41
Full columns - reconstruct single blob out of 10 31.783 us/op 44.695 us/op 0.71
Half columns - reconstruct all 10 blobs 456.58 ms/op 435.73 ms/op 1.05
Half columns - reconstruct half of the blobs out of 10 229.94 ms/op 222.71 ms/op 1.03
Half columns - reconstruct single blob out of 10 50.827 ms/op 48.148 ms/op 1.06
Full columns - reconstruct all 20 blobs 1.2555 ms/op 785.12 us/op 1.60
Full columns - reconstruct half of the blobs out of 20 310.07 us/op 420.26 us/op 0.74
Full columns - reconstruct single blob out of 20 31.642 us/op 40.368 us/op 0.78
Half columns - reconstruct all 20 blobs 916.38 ms/op 862.40 ms/op 1.06
Half columns - reconstruct half of the blobs out of 20 465.84 ms/op 435.08 ms/op 1.07
Half columns - reconstruct single blob out of 20 51.493 ms/op 53.114 ms/op 0.97
Set add up to 64 items then delete first 2.1548 us/op 2.0700 us/op 1.04
OrderedSet add up to 64 items then delete first 3.1954 us/op 3.0994 us/op 1.03
Set add up to 64 items then delete last 2.4040 us/op 2.3646 us/op 1.02
OrderedSet add up to 64 items then delete last 3.3356 us/op 3.3977 us/op 0.98
Set add up to 64 items then delete middle 2.3157 us/op 2.3461 us/op 0.99
OrderedSet add up to 64 items then delete middle 4.9403 us/op 4.9110 us/op 1.01
Set add up to 128 items then delete first 4.9464 us/op 4.8253 us/op 1.03
OrderedSet add up to 128 items then delete first 7.3121 us/op 7.2404 us/op 1.01
Set add up to 128 items then delete last 4.6460 us/op 4.5460 us/op 1.02
OrderedSet add up to 128 items then delete last 6.9532 us/op 7.0090 us/op 0.99
Set add up to 128 items then delete middle 4.6046 us/op 4.5795 us/op 1.01
OrderedSet add up to 128 items then delete middle 13.334 us/op 12.946 us/op 1.03
Set add up to 256 items then delete first 10.466 us/op 10.411 us/op 1.01
OrderedSet add up to 256 items then delete first 15.469 us/op 14.881 us/op 1.04
Set add up to 256 items then delete last 9.6735 us/op 9.2470 us/op 1.05
OrderedSet add up to 256 items then delete last 14.264 us/op 13.862 us/op 1.03
Set add up to 256 items then delete middle 9.4389 us/op 9.3006 us/op 1.01
OrderedSet add up to 256 items then delete middle 41.380 us/op 40.584 us/op 1.02
pass gossip attestations to forkchoice per slot 2.5546 ms/op 3.0347 ms/op 0.84
forkChoice updateHead vc 100000 bc 64 eq 0 504.39 us/op 485.79 us/op 1.04
forkChoice updateHead vc 600000 bc 64 eq 0 3.0198 ms/op 2.9468 ms/op 1.02
forkChoice updateHead vc 1000000 bc 64 eq 0 5.0537 ms/op 5.0174 ms/op 1.01
forkChoice updateHead vc 600000 bc 320 eq 0 3.0307 ms/op 3.0945 ms/op 0.98
forkChoice updateHead vc 600000 bc 1200 eq 0 2.9813 ms/op 2.9440 ms/op 1.01
forkChoice updateHead vc 600000 bc 7200 eq 0 3.3972 ms/op 3.2419 ms/op 1.05
forkChoice updateHead vc 600000 bc 64 eq 1000 3.4248 ms/op 3.3187 ms/op 1.03
forkChoice updateHead vc 600000 bc 64 eq 10000 3.4320 ms/op 3.4273 ms/op 1.00
forkChoice updateHead vc 600000 bc 64 eq 300000 8.8914 ms/op 9.1789 ms/op 0.97
computeDeltas 1400000 validators 0% inactive 14.285 ms/op 14.640 ms/op 0.98
computeDeltas 1400000 validators 10% inactive 14.023 ms/op 13.707 ms/op 1.02
computeDeltas 1400000 validators 20% inactive 12.528 ms/op 12.772 ms/op 0.98
computeDeltas 1400000 validators 50% inactive 10.101 ms/op 10.017 ms/op 1.01
computeDeltas 2100000 validators 0% inactive 22.266 ms/op 22.032 ms/op 1.01
computeDeltas 2100000 validators 10% inactive 21.023 ms/op 20.487 ms/op 1.03
computeDeltas 2100000 validators 20% inactive 19.481 ms/op 19.169 ms/op 1.02
computeDeltas 2100000 validators 50% inactive 15.217 ms/op 14.784 ms/op 1.03
altair processAttestation - 250000 vs - 7PWei normalcase 2.2308 ms/op 1.9810 ms/op 1.13
altair processAttestation - 250000 vs - 7PWei worstcase 3.1591 ms/op 2.9092 ms/op 1.09
altair processAttestation - setStatus - 1/6 committees join 123.95 us/op 116.90 us/op 1.06
altair processAttestation - setStatus - 1/3 committees join 241.19 us/op 224.61 us/op 1.07
altair processAttestation - setStatus - 1/2 committees join 341.22 us/op 320.71 us/op 1.06
altair processAttestation - setStatus - 2/3 committees join 423.81 us/op 414.65 us/op 1.02
altair processAttestation - setStatus - 4/5 committees join 595.72 us/op 605.10 us/op 0.98
altair processAttestation - setStatus - 100% committees join 708.89 us/op 696.91 us/op 1.02
altair processBlock - 250000 vs - 7PWei normalcase 3.7177 ms/op 4.5936 ms/op 0.81
altair processBlock - 250000 vs - 7PWei normalcase hashState 17.262 ms/op 17.907 ms/op 0.96
altair processBlock - 250000 vs - 7PWei worstcase 33.041 ms/op 29.107 ms/op 1.14
altair processBlock - 250000 vs - 7PWei worstcase hashState 68.574 ms/op 57.874 ms/op 1.18
phase0 processBlock - 250000 vs - 7PWei normalcase 1.4559 ms/op 1.5579 ms/op 0.93
phase0 processBlock - 250000 vs - 7PWei worstcase 21.816 ms/op 22.445 ms/op 0.97
altair processEth1Data - 250000 vs - 7PWei normalcase 365.78 us/op 363.63 us/op 1.01
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:15 10.783 us/op 9.7880 us/op 1.10
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:219 43.098 us/op 58.014 us/op 0.74
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:42 15.932 us/op 16.652 us/op 0.96
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:18 10.005 us/op 10.289 us/op 0.97
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1020 246.83 us/op 154.87 us/op 1.59
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11777 1.7976 ms/op 1.9600 ms/op 0.92
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 2.2791 ms/op 2.4983 ms/op 0.91
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 2.2653 ms/op 2.4529 ms/op 0.92
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 4.7863 ms/op 5.2601 ms/op 0.91
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.3565 ms/op 2.5451 ms/op 0.93
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 4.7551 ms/op 4.9358 ms/op 0.96
Tree 40 250000 create 399.00 ms/op 377.24 ms/op 1.06
Tree 40 250000 get(125000) 122.21 ns/op 122.26 ns/op 1.00
Tree 40 250000 set(125000) 1.1966 us/op 1.2070 us/op 0.99
Tree 40 250000 toArray() 12.987 ms/op 15.277 ms/op 0.85
Tree 40 250000 iterate all - toArray() + loop 12.729 ms/op 16.711 ms/op 0.76
Tree 40 250000 iterate all - get(i) 44.022 ms/op 47.078 ms/op 0.94
Array 250000 create 2.4890 ms/op 2.5413 ms/op 0.98
Array 250000 clone - spread 815.19 us/op 859.14 us/op 0.95
Array 250000 get(125000) 0.34500 ns/op 0.34900 ns/op 0.99
Array 250000 set(125000) 0.35700 ns/op 0.35400 ns/op 1.01
Array 250000 iterate all - loop 62.039 us/op 62.457 us/op 0.99
phase0 afterProcessEpoch - 250000 vs - 7PWei 41.986 ms/op 42.699 ms/op 0.98
Array.fill - length 1000000 2.9241 ms/op 2.9051 ms/op 1.01
Array push - length 1000000 10.843 ms/op 9.9115 ms/op 1.09
Array.get 0.21381 ns/op 0.21971 ns/op 0.97
Uint8Array.get 0.22102 ns/op 0.22077 ns/op 1.00
phase0 beforeProcessEpoch - 250000 vs - 7PWei 13.283 ms/op 13.849 ms/op 0.96
altair processEpoch - mainnet_e81889 260.54 ms/op 224.49 ms/op 1.16
mainnet_e81889 - altair beforeProcessEpoch 16.279 ms/op 17.453 ms/op 0.93
mainnet_e81889 - altair processJustificationAndFinalization 5.1740 us/op 5.1830 us/op 1.00
mainnet_e81889 - altair processInactivityUpdates 3.7200 ms/op 3.9274 ms/op 0.95
mainnet_e81889 - altair processRewardsAndPenalties 18.433 ms/op 18.368 ms/op 1.00
mainnet_e81889 - altair processRegistryUpdates 643.00 ns/op 633.00 ns/op 1.02
mainnet_e81889 - altair processSlashings 220.00 ns/op 209.00 ns/op 1.05
mainnet_e81889 - altair processEth1DataReset 161.00 ns/op 166.00 ns/op 0.97
mainnet_e81889 - altair processEffectiveBalanceUpdates 2.6155 ms/op 2.3233 ms/op 1.13
mainnet_e81889 - altair processSlashingsReset 848.00 ns/op 910.00 ns/op 0.93
mainnet_e81889 - altair processRandaoMixesReset 1.1360 us/op 1.4670 us/op 0.77
mainnet_e81889 - altair processHistoricalRootsUpdate 162.00 ns/op 198.00 ns/op 0.82
mainnet_e81889 - altair processParticipationFlagUpdates 506.00 ns/op 685.00 ns/op 0.74
mainnet_e81889 - altair processSyncCommitteeUpdates 124.00 ns/op 134.00 ns/op 0.93
mainnet_e81889 - altair afterProcessEpoch 42.745 ms/op 44.485 ms/op 0.96
capella processEpoch - mainnet_e217614 846.38 ms/op 909.48 ms/op 0.93
mainnet_e217614 - capella beforeProcessEpoch 63.739 ms/op 107.92 ms/op 0.59
mainnet_e217614 - capella processJustificationAndFinalization 5.3610 us/op 6.4620 us/op 0.83
mainnet_e217614 - capella processInactivityUpdates 15.724 ms/op 18.938 ms/op 0.83
mainnet_e217614 - capella processRewardsAndPenalties 107.42 ms/op 130.53 ms/op 0.82
mainnet_e217614 - capella processRegistryUpdates 5.8670 us/op 5.8380 us/op 1.00
mainnet_e217614 - capella processSlashings 163.00 ns/op 183.00 ns/op 0.89
mainnet_e217614 - capella processEth1DataReset 188.00 ns/op 193.00 ns/op 0.97
mainnet_e217614 - capella processEffectiveBalanceUpdates 14.051 ms/op 36.358 ms/op 0.39
mainnet_e217614 - capella processSlashingsReset 1.0690 us/op 929.00 ns/op 1.15
mainnet_e217614 - capella processRandaoMixesReset 1.0840 us/op 1.3670 us/op 0.79
mainnet_e217614 - capella processHistoricalRootsUpdate 277.00 ns/op 192.00 ns/op 1.44
mainnet_e217614 - capella processParticipationFlagUpdates 499.00 ns/op 577.00 ns/op 0.86
mainnet_e217614 - capella afterProcessEpoch 114.70 ms/op 119.91 ms/op 0.96
phase0 processEpoch - mainnet_e58758 251.99 ms/op 285.49 ms/op 0.88
mainnet_e58758 - phase0 beforeProcessEpoch 52.197 ms/op 69.280 ms/op 0.75
mainnet_e58758 - phase0 processJustificationAndFinalization 5.6490 us/op 7.2090 us/op 0.78
mainnet_e58758 - phase0 processRewardsAndPenalties 21.373 ms/op 22.271 ms/op 0.96
mainnet_e58758 - phase0 processRegistryUpdates 2.8580 us/op 3.1890 us/op 0.90
mainnet_e58758 - phase0 processSlashings 160.00 ns/op 189.00 ns/op 0.85
mainnet_e58758 - phase0 processEth1DataReset 299.00 ns/op 184.00 ns/op 1.63
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 1.1768 ms/op 4.6918 ms/op 0.25
mainnet_e58758 - phase0 processSlashingsReset 824.00 ns/op 1.2210 us/op 0.67
mainnet_e58758 - phase0 processRandaoMixesReset 1.1160 us/op 1.1610 us/op 0.96
mainnet_e58758 - phase0 processHistoricalRootsUpdate 190.00 ns/op 195.00 ns/op 0.97
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.0060 us/op 958.00 ns/op 1.05
mainnet_e58758 - phase0 afterProcessEpoch 35.212 ms/op 36.756 ms/op 0.96
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.2497 ms/op 2.2112 ms/op 0.57
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 2.6479 ms/op 2.2057 ms/op 1.20
altair processInactivityUpdates - 250000 normalcase 12.130 ms/op 17.230 ms/op 0.70
altair processInactivityUpdates - 250000 worstcase 12.291 ms/op 19.347 ms/op 0.64
phase0 processRegistryUpdates - 250000 normalcase 7.3350 us/op 5.4590 us/op 1.34
phase0 processRegistryUpdates - 250000 badcase_full_deposits 367.36 us/op 310.16 us/op 1.18
phase0 processRegistryUpdates - 250000 worstcase 0.5 84.638 ms/op 81.520 ms/op 1.04
altair processRewardsAndPenalties - 250000 normalcase 18.976 ms/op 22.649 ms/op 0.84
altair processRewardsAndPenalties - 250000 worstcase 16.758 ms/op 21.267 ms/op 0.79
phase0 getAttestationDeltas - 250000 normalcase 5.7380 ms/op 7.4156 ms/op 0.77
phase0 getAttestationDeltas - 250000 worstcase 5.6891 ms/op 7.5116 ms/op 0.76
phase0 processSlashings - 250000 worstcase 122.09 us/op 132.84 us/op 0.92
altair processSyncCommitteeUpdates - 250000 10.907 ms/op 11.529 ms/op 0.95
BeaconState.hashTreeRoot - No change 219.00 ns/op 223.00 ns/op 0.98
BeaconState.hashTreeRoot - 1 full validator 90.273 us/op 95.057 us/op 0.95
BeaconState.hashTreeRoot - 32 full validator 1.0433 ms/op 1.5862 ms/op 0.66
BeaconState.hashTreeRoot - 512 full validator 8.2021 ms/op 12.424 ms/op 0.66
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 123.19 us/op 125.71 us/op 0.98
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 2.3801 ms/op 2.6130 ms/op 0.91
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 24.096 ms/op 19.721 ms/op 1.22
BeaconState.hashTreeRoot - 1 balances 90.362 us/op 97.525 us/op 0.93
BeaconState.hashTreeRoot - 32 balances 833.51 us/op 946.84 us/op 0.88
BeaconState.hashTreeRoot - 512 balances 7.4854 ms/op 6.4740 ms/op 1.16
BeaconState.hashTreeRoot - 250000 balances 179.71 ms/op 187.60 ms/op 0.96
aggregationBits - 2048 els - zipIndexesInBitList 20.953 us/op 24.715 us/op 0.85
regular array get 100000 times 24.777 us/op 25.485 us/op 0.97
wrappedArray get 100000 times 24.873 us/op 25.700 us/op 0.97
arrayWithProxy get 100000 times 15.219 ms/op 15.380 ms/op 0.99
ssz.Root.equals 23.979 ns/op 24.159 ns/op 0.99
byteArrayEquals 23.223 ns/op 23.599 ns/op 0.98
Buffer.compare 10.090 ns/op 10.020 ns/op 1.01
processSlot - 1 slots 12.001 us/op 13.079 us/op 0.92
processSlot - 32 slots 2.5533 ms/op 2.6185 ms/op 0.98
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 4.7323 ms/op 6.6992 ms/op 0.71
getCommitteeAssignments - req 1 vs - 250000 vc 1.9255 ms/op 1.9110 ms/op 1.01
getCommitteeAssignments - req 100 vs - 250000 vc 3.7511 ms/op 3.7814 ms/op 0.99
getCommitteeAssignments - req 1000 vs - 250000 vc 3.9803 ms/op 4.0340 ms/op 0.99
findModifiedValidators - 10000 modified validators 775.46 ms/op 855.10 ms/op 0.91
findModifiedValidators - 1000 modified validators 422.05 ms/op 483.29 ms/op 0.87
findModifiedValidators - 100 modified validators 265.02 ms/op 403.46 ms/op 0.66
findModifiedValidators - 10 modified validators 171.82 ms/op 250.17 ms/op 0.69
findModifiedValidators - 1 modified validators 177.13 ms/op 218.61 ms/op 0.81
findModifiedValidators - no difference 164.64 ms/op 191.93 ms/op 0.86
migrate state 1500000 validators, 3400 modified, 2000 new 1.2084 s/op 1.2261 s/op 0.99
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 4.2100 ns/op 4.3200 ns/op 0.97
state getBlockRootAtSlot - 250000 vs - 7PWei 845.09 ns/op 550.13 ns/op 1.54
computeProposerIndex 100000 validators 1.5469 ms/op 1.6801 ms/op 0.92
getNextSyncCommitteeIndices 1000 validators 119.40 ms/op 147.88 ms/op 0.81
getNextSyncCommitteeIndices 10000 validators 125.06 ms/op 148.33 ms/op 0.84
getNextSyncCommitteeIndices 100000 validators 126.49 ms/op 143.44 ms/op 0.88
computeProposers - vc 250000 677.47 us/op 696.84 us/op 0.97
computeEpochShuffling - vc 250000 43.187 ms/op 44.294 ms/op 0.98
getNextSyncCommittee - vc 250000 11.206 ms/op 13.145 ms/op 0.85
nodejs block root to RootHex using toHex 157.12 ns/op 152.54 ns/op 1.03
nodejs block root to RootHex using toRootHex 86.995 ns/op 99.033 ns/op 0.88
nodejs fromHex(blob) 195.43 us/op 558.04 us/op 0.35
nodejs fromHexInto(blob) 717.30 us/op 702.89 us/op 1.02
nodejs block root to RootHex using the deprecated toHexString 566.94 ns/op 578.00 ns/op 0.98
browser block root to RootHex using toHex 308.94 ns/op 282.10 ns/op 1.10
browser block root to RootHex using toRootHex 157.79 ns/op 158.01 ns/op 1.00
browser fromHex(blob) 1.1056 ms/op 1.3124 ms/op 0.84
browser fromHexInto(blob) 754.07 us/op 743.53 us/op 1.01
browser block root to RootHex using the deprecated toHexString 390.84 ns/op 394.32 ns/op 0.99

by benchmarkbot/action

@ensi321
Copy link
Contributor Author

ensi321 commented Dec 13, 2025

/gemini review

Copy link
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 support for ePBS gossip topics, including execution_payload, payload_attestation_message, and execution_payload_bid. It adds the necessary gossip validation, handling logic, operation pools, and seen caches for these new topics. The changes also update existing validation for beacon_aggregate_and_proof and beacon_block to align with the GLOAS fork specifications. Overall, the implementation is comprehensive and well-structured. I've identified two critical issues in the gossip topic SSZ type definitions that would prevent the new gossip messages from being deserialized correctly. Please see the detailed comments for fixes.

@ensi321 ensi321 marked this pull request as ready for review December 13, 2025 05:33
@nflaig
Copy link
Member

nflaig commented Dec 19, 2025

@ensi321 there are merge conflicts, will look into reviewing this tomorrow or next week

Copy link
Member

@nflaig nflaig left a comment

Choose a reason for hiding this comment

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

looks good overall, fixed a few things along the way here #8715 which resolves some of the comments already

}

export type ExecutionPayloadBidErrorType =
| {code: ExecutionPayloadBidErrorCode.BUILDER_NOT_ELIGIBLE; builderIndex: ValidatorIndex}
Copy link
Member

Choose a reason for hiding this comment

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

a lot of these builderIndex: ValidatorIndex we will need to change once builders are no longer validators

import {InsertOutcome} from "./types.js";
import {pruneBySlot} from "./utils.js";

const SLOTS_RETAINED: Slot = 2;
Copy link
Member

Choose a reason for hiding this comment

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

we should add a rational here why this number was chosen


// Handle valid payload bid by storing in a bid pool
try {
chain.executionPayloadBidPool.add(executionPayloadBid.message);
Copy link
Member

Choose a reason for hiding this comment

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

we don't really need to store any bids if we are not a proposer of the slot

import {pruneBySlot, signatureFromBytesNoCheck} from "./utils.js";

/**
* The number of slots that will be stored in the pool
Copy link
Member

Choose a reason for hiding this comment

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

this comment is not useful, should add a comment on why the number 2 was chosen

Comment on lines 25 to 27
type PayloadAttestation = gloas.PayloadAttestation;
type PayloadAttestationData = gloas.PayloadAttestationData;
type PayloadAttestationMessage = gloas.PayloadAttestationMessage;
Copy link
Member

Choose a reason for hiding this comment

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

this feels unnecessary and also not accurate since PayloadAttestation type implies it's a multi-fork type

[GossipType.bls_to_execution_change]: true,
[GossipType.execution_payload]: true,
[GossipType.payload_attestation_message]: true,
[GossipType.execution_payload_bid]: false,
Copy link
Member

Choose a reason for hiding this comment

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

the only ones publishing to this topic will be builders, or if we "act as builder", but either way it would be an api call and I am assuming builders will run fallback nodes so duplicate publishing can happen, any reason why we don't wanna ignore duplicates here?


// [IGNORE] `bid.parent_block_hash` is the block hash of a known execution
// payload in fork choice.
// TODO GLOAS: implement this
Copy link
Member

Choose a reason for hiding this comment

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

any reason why this is left out for now? we do have node.executionPayloadBlockHash in proto array and should be able to check this easily

const builder = state.validators.getReadonly(withdrawal.builderIndex);
const currentEpoch = computeEpochAtSlot(state.slot);

return builder.withdrawableEpoch >= currentEpoch || !builder.slashed;
Copy link
Member

Choose a reason for hiding this comment

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

not related to this PR but this should be

  return !builder.slashed || currentEpoch >= builder.withdrawableEpoch;

(likely not relevant though since we switch to non-validator builders)

help: "Total number of empty returns in SyncContributionAndProofPool.getAggregate(slot, root)",
}),
},
payloadAttestationPool: {
Copy link
Member

Choose a reason for hiding this comment

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

would be good to have same/similar metrics for bids pool

const EXECUTION_PAYLOAD_BID_WEIGHT = 0.05;

const beaconAttestationSubnetWeight = 1 / ATTESTATION_SUBNET_COUNT;
const maxPositiveScore =
Copy link
Member

Choose a reason for hiding this comment

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

don't we need to update maxPositiveScore?

Comment on lines +94 to +95
builderIndex?: number;
blockHashHex?: RootHex;
Copy link
Member

Choose a reason for hiding this comment

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

shouldn't this information be part of block input, was there any other use case besides validating the execution payload envelope?

I guess we still need to think about how BlockInput looks like for gloas but I'd think we can store it there and just remove block input if all data has been imported and keep it open otherwise

cc @matthewkeil @wemeetagain curious what you think

Copy link
Contributor

Choose a reason for hiding this comment

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

shouldn't this information be part of block input, was there any other use case besides validating the execution payload envelope?

given this fork-choice function in gloas spec:

def get_parent_payload_status(store: Store, block: BeaconBlock) -> PayloadStatus:
    parent = store.blocks[block.parent_root]
    parent_block_hash = block.body.signed_execution_payload_bid.message.parent_block_hash
    message_block_hash = parent.body.signed_execution_payload_bid.message.block_hash
    return PAYLOAD_STATUS_FULL if parent_block_hash == message_block_hash else PAYLOAD_STATUS_EMPTY

we have to store both blockHashHex and parentBlockHashHex because we don't have full BeaconBlock in fork-choice

also would be nice to know the blocks-chain of EL in our fork-choice too

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.

4 participants