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: b7ae7a6 Previous: 075956b Ratio
300 bytes - uncompress - snappy 22.657 us/op 2.9316 us/op 7.73
500 bytes - uncompress - snappy-wasm - prealloc 41.253 us/op 1.3202 us/op 31.25
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 1.3392 ms/op 433.04 us/op 3.09
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 2.3757 ms/op 757.99 us/op 3.13
Full columns - reconstruct half of the blobs out of 6 23.896 ms/op 157.97 us/op 151.27
computeDeltas 1400000 validators 0% inactive 53.419 ms/op 17.075 ms/op 3.13
altair processAttestation - 250000 vs - 7PWei worstcase 11.158 ms/op 3.6304 ms/op 3.07
altair processAttestation - setStatus - 1/2 committees join 1.1488 ms/op 365.08 us/op 3.15
altair processAttestation - setStatus - 100% committees join 2.6499 ms/op 849.52 us/op 3.12
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11777 6.3921 ms/op 1.9077 ms/op 3.35
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 7.7724 ms/op 2.4237 ms/op 3.21
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 18.620 ms/op 6.1504 ms/op 3.03
Full benchmark results
Benchmark suite Current: b7ae7a6 Previous: 075956b Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 1.2931 ms/op 3.4271 ms/op 0.38
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 40.616 us/op 114.99 us/op 0.35
BLS verify - blst 844.46 us/op 2.6756 ms/op 0.32
BLS verifyMultipleSignatures 3 - blst 1.2761 ms/op 3.3906 ms/op 0.38
BLS verifyMultipleSignatures 8 - blst 2.1988 ms/op 4.2442 ms/op 0.52
BLS verifyMultipleSignatures 32 - blst 6.0645 ms/op 16.239 ms/op 0.37
BLS verifyMultipleSignatures 64 - blst 11.651 ms/op 31.650 ms/op 0.37
BLS verifyMultipleSignatures 128 - blst 19.523 ms/op 39.618 ms/op 0.49
BLS deserializing 10000 signatures 759.39 ms/op 1.8305 s/op 0.41
BLS deserializing 100000 signatures 7.5677 s/op 11.514 s/op 0.66
BLS verifyMultipleSignatures - same message - 3 - blst 1.0296 ms/op 1.3588 ms/op 0.76
BLS verifyMultipleSignatures - same message - 8 - blst 1.1018 ms/op 1.6011 ms/op 0.69
BLS verifyMultipleSignatures - same message - 32 - blst 1.8424 ms/op 2.7898 ms/op 0.66
BLS verifyMultipleSignatures - same message - 64 - blst 2.8540 ms/op 3.8275 ms/op 0.75
BLS verifyMultipleSignatures - same message - 128 - blst 4.9249 ms/op 7.7559 ms/op 0.63
BLS aggregatePubkeys 32 - blst 20.954 us/op 66.051 us/op 0.32
BLS aggregatePubkeys 128 - blst 72.499 us/op 234.08 us/op 0.31
getSlashingsAndExits - default max 93.664 us/op 106.50 us/op 0.88
getSlashingsAndExits - 2k 323.60 us/op 398.52 us/op 0.81
isKnown best case - 1 super set check 224.00 ns/op 413.00 ns/op 0.54
isKnown normal case - 2 super set checks 219.00 ns/op 396.00 ns/op 0.55
isKnown worse case - 16 super set checks 218.00 ns/op 606.00 ns/op 0.36
InMemoryCheckpointStateCache - add get delete 3.2710 us/op 7.6380 us/op 0.43
validate api signedAggregateAndProof - struct 1.4896 ms/op 4.2864 ms/op 0.35
validate gossip signedAggregateAndProof - struct 1.5323 ms/op 4.1596 ms/op 0.37
batch validate gossip attestation - vc 640000 - chunk 32 136.31 us/op 224.25 us/op 0.61
batch validate gossip attestation - vc 640000 - chunk 64 112.41 us/op 193.44 us/op 0.58
batch validate gossip attestation - vc 640000 - chunk 128 103.43 us/op 206.33 us/op 0.50
batch validate gossip attestation - vc 640000 - chunk 256 101.29 us/op 193.38 us/op 0.52
bytes32 toHexString 378.00 ns/op 736.00 ns/op 0.51
bytes32 Buffer.toString(hex) 245.00 ns/op 486.00 ns/op 0.50
bytes32 Buffer.toString(hex) from Uint8Array 374.00 ns/op 720.00 ns/op 0.52
bytes32 Buffer.toString(hex) + 0x 244.00 ns/op 416.00 ns/op 0.59
Object access 1 prop 0.13000 ns/op 0.22400 ns/op 0.58
Map access 1 prop 0.14800 ns/op 0.20200 ns/op 0.73
Object get x1000 5.7460 ns/op 9.2410 ns/op 0.62
Map get x1000 0.43700 ns/op 0.97800 ns/op 0.45
Object set x1000 31.386 ns/op 67.590 ns/op 0.46
Map set x1000 21.874 ns/op 44.145 ns/op 0.50
Return object 10000 times 0.24630 ns/op 0.52160 ns/op 0.47
Throw Error 10000 times 4.4478 us/op 7.8934 us/op 0.56
toHex 153.89 ns/op 293.17 ns/op 0.52
Buffer.from 128.34 ns/op 241.83 ns/op 0.53
shared Buffer 84.926 ns/op 178.29 ns/op 0.48
fastMsgIdFn sha256 / 200 bytes 1.9490 us/op 3.5260 us/op 0.55
fastMsgIdFn h32 xxhash / 200 bytes 233.00 ns/op 329.00 ns/op 0.71
fastMsgIdFn h64 xxhash / 200 bytes 305.00 ns/op 404.00 ns/op 0.75
fastMsgIdFn sha256 / 1000 bytes 6.8380 us/op 9.8220 us/op 0.70
fastMsgIdFn h32 xxhash / 1000 bytes 339.00 ns/op 460.00 ns/op 0.74
fastMsgIdFn h64 xxhash / 1000 bytes 370.00 ns/op 504.00 ns/op 0.73
fastMsgIdFn sha256 / 10000 bytes 60.549 us/op 111.80 us/op 0.54
fastMsgIdFn h32 xxhash / 10000 bytes 1.4990 us/op 2.8060 us/op 0.53
fastMsgIdFn h64 xxhash / 10000 bytes 1.0070 us/op 1.9580 us/op 0.51
100 bytes - compress - snappyjs 1.7495 us/op 2.8717 us/op 0.61
100 bytes - compress - snappy 1.4877 us/op 4.8995 us/op 0.30
100 bytes - compress - snappy-wasm 945.35 ns/op 950.61 ns/op 0.99
100 bytes - compress - snappy-wasm - prealloc 1.8393 us/op 2.7877 us/op 0.66
200 bytes - compress - snappyjs 2.0906 us/op 4.7349 us/op 0.44
200 bytes - compress - snappy 1.6289 us/op 3.0993 us/op 0.53
200 bytes - compress - snappy-wasm 980.96 ns/op 1.8703 us/op 0.52
200 bytes - compress - snappy-wasm - prealloc 1.8952 us/op 2.9268 us/op 0.65
300 bytes - compress - snappyjs 2.6796 us/op 5.3764 us/op 0.50
300 bytes - compress - snappy 1.6009 us/op 3.2996 us/op 0.49
300 bytes - compress - snappy-wasm 1.0030 us/op 1.9700 us/op 0.51
300 bytes - compress - snappy-wasm - prealloc 2.1204 us/op 1.7004 us/op 1.25
400 bytes - compress - snappyjs 2.4138 us/op 4.7692 us/op 0.51
400 bytes - compress - snappy 2.0262 us/op 6.2985 us/op 0.32
400 bytes - compress - snappy-wasm 1.0701 us/op 2.3171 us/op 0.46
400 bytes - compress - snappy-wasm - prealloc 1.7312 us/op 2.6118 us/op 0.66
500 bytes - compress - snappyjs 3.1000 us/op 5.8726 us/op 0.53
500 bytes - compress - snappy 1.5567 us/op 3.1107 us/op 0.50
500 bytes - compress - snappy-wasm 1.4399 us/op 2.4447 us/op 0.59
500 bytes - compress - snappy-wasm - prealloc 2.0373 us/op 3.4590 us/op 0.59
1000 bytes - compress - snappyjs 5.9798 us/op 18.019 us/op 0.33
1000 bytes - compress - snappy 1.7254 us/op 3.2510 us/op 0.53
1000 bytes - compress - snappy-wasm 1.5829 us/op 3.5601 us/op 0.44
1000 bytes - compress - snappy-wasm - prealloc 2.2240 us/op 3.4618 us/op 0.64
10000 bytes - compress - snappyjs 35.872 us/op 46.122 us/op 0.78
10000 bytes - compress - snappy 33.911 us/op 22.125 us/op 1.53
10000 bytes - compress - snappy-wasm 23.161 us/op 26.054 us/op 0.89
10000 bytes - compress - snappy-wasm - prealloc 25.704 us/op 23.946 us/op 1.07
100 bytes - uncompress - snappyjs 915.53 ns/op 823.39 ns/op 1.11
100 bytes - uncompress - snappy 1.3276 us/op 1.2496 us/op 1.06
100 bytes - uncompress - snappy-wasm 909.96 ns/op 1.2770 us/op 0.71
100 bytes - uncompress - snappy-wasm - prealloc 1.1610 us/op 1.3101 us/op 0.89
200 bytes - uncompress - snappyjs 1.5797 us/op 1.2942 us/op 1.22
200 bytes - uncompress - snappy 2.6801 us/op 1.5743 us/op 1.70
200 bytes - uncompress - snappy-wasm 1.9102 us/op 944.03 ns/op 2.02
200 bytes - uncompress - snappy-wasm - prealloc 1.6163 us/op 1.6368 us/op 0.99
300 bytes - uncompress - snappyjs 3.9843 us/op 1.6530 us/op 2.41
300 bytes - uncompress - snappy 22.657 us/op 2.9316 us/op 7.73
300 bytes - uncompress - snappy-wasm 2.3529 us/op 1.1529 us/op 2.04
300 bytes - uncompress - snappy-wasm - prealloc 1.9314 us/op 1.4166 us/op 1.36
400 bytes - uncompress - snappyjs 3.7187 us/op 19.953 us/op 0.19
400 bytes - uncompress - snappy 3.4379 us/op 1.6121 us/op 2.13
400 bytes - uncompress - snappy-wasm 2.1436 us/op 24.819 us/op 0.09
400 bytes - uncompress - snappy-wasm - prealloc 2.7052 us/op 1.3052 us/op 2.07
500 bytes - uncompress - snappyjs 2.2515 us/op 1.9386 us/op 1.16
500 bytes - uncompress - snappy 3.1946 us/op 1.9468 us/op 1.64
500 bytes - uncompress - snappy-wasm 2.6804 us/op 1.1406 us/op 2.35
500 bytes - uncompress - snappy-wasm - prealloc 41.253 us/op 1.3202 us/op 31.25
1000 bytes - uncompress - snappyjs 4.3483 us/op 2.5819 us/op 1.68
1000 bytes - uncompress - snappy 2.7658 us/op 1.9957 us/op 1.39
1000 bytes - uncompress - snappy-wasm 2.7326 us/op 1.4718 us/op 1.86
1000 bytes - uncompress - snappy-wasm - prealloc 3.0023 us/op 2.0003 us/op 1.50
10000 bytes - uncompress - snappyjs 27.325 us/op 37.292 us/op 0.73
10000 bytes - uncompress - snappy 13.605 us/op 28.527 us/op 0.48
10000 bytes - uncompress - snappy-wasm 8.5856 us/op 16.204 us/op 0.53
10000 bytes - uncompress - snappy-wasm - prealloc 9.0294 us/op 20.137 us/op 0.45
send data - 1000 256B messages 23.929 ms/op 16.436 ms/op 1.46
send data - 1000 512B messages 41.308 ms/op 30.610 ms/op 1.35
send data - 1000 1024B messages 36.262 ms/op 30.263 ms/op 1.20
send data - 1000 1200B messages 53.263 ms/op 34.252 ms/op 1.56
send data - 1000 2048B messages 43.844 ms/op 32.930 ms/op 1.33
send data - 1000 4096B messages 61.756 ms/op 65.404 ms/op 0.94
send data - 1000 16384B messages 392.50 ms/op 148.17 ms/op 2.65
send data - 1000 65536B messages 954.57 ms/op 428.07 ms/op 2.23
enrSubnets - fastDeserialize 64 bits 1.4280 us/op 1.1070 us/op 1.29
enrSubnets - ssz BitVector 64 bits 780.00 ns/op 405.00 ns/op 1.93
enrSubnets - fastDeserialize 4 bits 374.00 ns/op 143.00 ns/op 2.62
enrSubnets - ssz BitVector 4 bits 963.00 ns/op 434.00 ns/op 2.22
prioritizePeers score -10:0 att 32-0.1 sync 2-0 473.85 us/op 255.96 us/op 1.85
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 880.59 us/op 322.21 us/op 2.73
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 1.3392 ms/op 433.04 us/op 3.09
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 2.3757 ms/op 757.99 us/op 3.13
prioritizePeers score 0:0 att 64-1 sync 4-1 2.8306 ms/op 951.39 us/op 2.98
array of 16000 items push then shift 4.5171 us/op 1.7433 us/op 2.59
LinkedList of 16000 items push then shift 21.547 ns/op 8.0470 ns/op 2.68
array of 16000 items push then pop 168.06 ns/op 80.373 ns/op 2.09
LinkedList of 16000 items push then pop 20.448 ns/op 7.8720 ns/op 2.60
array of 24000 items push then shift 5.9603 us/op 2.7018 us/op 2.21
LinkedList of 24000 items push then shift 11.035 ns/op 8.8750 ns/op 1.24
array of 24000 items push then pop 170.70 ns/op 114.96 ns/op 1.48
LinkedList of 24000 items push then pop 11.244 ns/op 8.0140 ns/op 1.40
intersect bitArray bitLen 8 9.6800 ns/op 6.3510 ns/op 1.52
intersect array and set length 8 55.622 ns/op 37.593 ns/op 1.48
intersect bitArray bitLen 128 45.567 ns/op 31.905 ns/op 1.43
intersect array and set length 128 901.41 ns/op 620.14 ns/op 1.45
bitArray.getTrueBitIndexes() bitLen 128 1.6200 us/op 1.2190 us/op 1.33
bitArray.getTrueBitIndexes() bitLen 248 2.8560 us/op 1.9880 us/op 1.44
bitArray.getTrueBitIndexes() bitLen 512 6.8540 us/op 4.0740 us/op 1.68
Full columns - reconstruct all 6 blobs 394.31 us/op 405.12 us/op 0.97
Full columns - reconstruct half of the blobs out of 6 23.896 ms/op 157.97 us/op 151.27
Full columns - reconstruct single blob out of 6 57.483 us/op 50.605 us/op 1.14
Half columns - reconstruct all 6 blobs 543.61 ms/op 370.56 ms/op 1.47
Half columns - reconstruct half of the blobs out of 6 303.08 ms/op 189.60 ms/op 1.60
Half columns - reconstruct single blob out of 6 100.31 ms/op 62.646 ms/op 1.60
Full columns - reconstruct all 10 blobs 369.28 us/op 327.19 us/op 1.13
Full columns - reconstruct half of the blobs out of 10 231.91 us/op 172.31 us/op 1.35
Full columns - reconstruct single blob out of 10 59.291 us/op 53.876 us/op 1.10
Half columns - reconstruct all 10 blobs 1.0046 s/op 600.14 ms/op 1.67
Half columns - reconstruct half of the blobs out of 10 464.20 ms/op 381.94 ms/op 1.22
Half columns - reconstruct single blob out of 10 114.56 ms/op 83.510 ms/op 1.37
Full columns - reconstruct all 20 blobs 688.18 us/op 967.66 us/op 0.71
Full columns - reconstruct half of the blobs out of 20 494.98 us/op 321.56 us/op 1.54
Full columns - reconstruct single blob out of 20 56.484 us/op 50.501 us/op 1.12
Half columns - reconstruct all 20 blobs 2.0456 s/op 1.5833 s/op 1.29
Half columns - reconstruct half of the blobs out of 20 945.56 ms/op 880.89 ms/op 1.07
Half columns - reconstruct single blob out of 20 98.344 ms/op 124.14 ms/op 0.79
Set add up to 64 items then delete first 5.8984 us/op 4.0430 us/op 1.46
OrderedSet add up to 64 items then delete first 10.899 us/op 5.3535 us/op 2.04
Set add up to 64 items then delete last 4.5188 us/op 4.7412 us/op 0.95
OrderedSet add up to 64 items then delete last 7.0963 us/op 7.5234 us/op 0.94
Set add up to 64 items then delete middle 4.5258 us/op 4.2136 us/op 1.07
OrderedSet add up to 64 items then delete middle 14.534 us/op 8.1017 us/op 1.79
Set add up to 128 items then delete first 10.592 us/op 8.9482 us/op 1.18
OrderedSet add up to 128 items then delete first 20.189 us/op 12.744 us/op 1.58
Set add up to 128 items then delete last 15.332 us/op 9.0322 us/op 1.70
OrderedSet add up to 128 items then delete last 20.433 us/op 13.835 us/op 1.48
Set add up to 128 items then delete middle 14.497 us/op 9.2591 us/op 1.57
OrderedSet add up to 128 items then delete middle 25.829 us/op 25.347 us/op 1.02
Set add up to 256 items then delete first 20.021 us/op 18.586 us/op 1.08
OrderedSet add up to 256 items then delete first 41.688 us/op 26.630 us/op 1.57
Set add up to 256 items then delete last 18.111 us/op 18.184 us/op 1.00
OrderedSet add up to 256 items then delete last 39.436 us/op 28.131 us/op 1.40
Set add up to 256 items then delete middle 28.767 us/op 17.099 us/op 1.68
OrderedSet add up to 256 items then delete middle 112.67 us/op 75.220 us/op 1.50
pass gossip attestations to forkchoice per slot 7.8553 ms/op 5.0711 ms/op 1.55
forkChoice updateHead vc 100000 bc 64 eq 0 1.7767 ms/op 1.2800 ms/op 1.39
forkChoice updateHead vc 600000 bc 64 eq 0 10.357 ms/op 5.2007 ms/op 1.99
forkChoice updateHead vc 1000000 bc 64 eq 0 11.949 ms/op 8.8987 ms/op 1.34
forkChoice updateHead vc 600000 bc 320 eq 0 6.8390 ms/op 6.0762 ms/op 1.13
forkChoice updateHead vc 600000 bc 1200 eq 0 10.218 ms/op 6.3278 ms/op 1.61
forkChoice updateHead vc 600000 bc 7200 eq 0 9.7159 ms/op 5.5688 ms/op 1.74
forkChoice updateHead vc 600000 bc 64 eq 1000 10.997 ms/op 5.4645 ms/op 2.01
forkChoice updateHead vc 600000 bc 64 eq 10000 11.786 ms/op 5.4210 ms/op 2.17
forkChoice updateHead vc 600000 bc 64 eq 300000 31.298 ms/op 11.670 ms/op 2.68
computeDeltas 1400000 validators 0% inactive 53.419 ms/op 17.075 ms/op 3.13
computeDeltas 1400000 validators 10% inactive 49.377 ms/op 21.709 ms/op 2.27
computeDeltas 1400000 validators 20% inactive 46.346 ms/op 18.227 ms/op 2.54
computeDeltas 1400000 validators 50% inactive 34.932 ms/op 13.916 ms/op 2.51
computeDeltas 2100000 validators 0% inactive 80.080 ms/op 31.695 ms/op 2.53
computeDeltas 2100000 validators 10% inactive 73.984 ms/op 29.187 ms/op 2.53
computeDeltas 2100000 validators 20% inactive 71.588 ms/op 29.049 ms/op 2.46
computeDeltas 2100000 validators 50% inactive 55.610 ms/op 25.334 ms/op 2.20
altair processAttestation - 250000 vs - 7PWei normalcase 4.8181 ms/op 2.3853 ms/op 2.02
altair processAttestation - 250000 vs - 7PWei worstcase 11.158 ms/op 3.6304 ms/op 3.07
altair processAttestation - setStatus - 1/6 committees join 410.72 us/op 150.65 us/op 2.73
altair processAttestation - setStatus - 1/3 committees join 463.78 us/op 256.05 us/op 1.81
altair processAttestation - setStatus - 1/2 committees join 1.1488 ms/op 365.08 us/op 3.15
altair processAttestation - setStatus - 2/3 committees join 1.3448 ms/op 492.79 us/op 2.73
altair processAttestation - setStatus - 4/5 committees join 1.1007 ms/op 709.32 us/op 1.55
altair processAttestation - setStatus - 100% committees join 2.6499 ms/op 849.52 us/op 3.12
altair processBlock - 250000 vs - 7PWei normalcase 14.405 ms/op 4.8631 ms/op 2.96
altair processBlock - 250000 vs - 7PWei normalcase hashState 31.781 ms/op 33.683 ms/op 0.94
altair processBlock - 250000 vs - 7PWei worstcase 58.283 ms/op 38.730 ms/op 1.50
altair processBlock - 250000 vs - 7PWei worstcase hashState 128.08 ms/op 88.992 ms/op 1.44
phase0 processBlock - 250000 vs - 7PWei normalcase 5.3458 ms/op 2.5929 ms/op 2.06
phase0 processBlock - 250000 vs - 7PWei worstcase 49.922 ms/op 21.811 ms/op 2.29
altair processEth1Data - 250000 vs - 7PWei normalcase 1.2642 ms/op 487.59 us/op 2.59
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:15 22.535 us/op 12.678 us/op 1.78
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:219 102.14 us/op 43.362 us/op 2.36
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:42 31.247 us/op 17.657 us/op 1.77
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:18 19.819 us/op 6.8530 us/op 2.89
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1020 372.13 us/op 143.65 us/op 2.59
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11777 6.3921 ms/op 1.9077 ms/op 3.35
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 7.7724 ms/op 2.4237 ms/op 3.21
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 7.7753 ms/op 2.6315 ms/op 2.95
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 18.620 ms/op 6.1504 ms/op 3.03
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 3.5374 ms/op 2.9340 ms/op 1.21
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 8.2228 ms/op 5.9112 ms/op 1.39
Tree 40 250000 create 584.07 ms/op 467.52 ms/op 1.25
Tree 40 250000 get(125000) 196.38 ns/op 164.48 ns/op 1.19
Tree 40 250000 set(125000) 1.9692 us/op 1.5852 us/op 1.24
Tree 40 250000 toArray() 22.011 ms/op 19.420 ms/op 1.13
Tree 40 250000 iterate all - toArray() + loop 21.829 ms/op 18.455 ms/op 1.18
Tree 40 250000 iterate all - get(i) 63.914 ms/op 57.942 ms/op 1.10
Array 250000 create 3.1894 ms/op 3.1252 ms/op 1.02
Array 250000 clone - spread 1.0027 ms/op 991.74 us/op 1.01
Array 250000 get(125000) 0.47700 ns/op 0.45400 ns/op 1.05
Array 250000 set(125000) 0.43500 ns/op 0.44200 ns/op 0.98
Array 250000 iterate all - loop 77.814 us/op 80.831 us/op 0.96
phase0 afterProcessEpoch - 250000 vs - 7PWei 60.587 ms/op 53.252 ms/op 1.14
Array.fill - length 1000000 3.5807 ms/op 4.1947 ms/op 0.85
Array push - length 1000000 17.817 ms/op 13.036 ms/op 1.37
Array.get 0.25107 ns/op 0.35980 ns/op 0.70
Uint8Array.get 0.26652 ns/op 0.31598 ns/op 0.84
phase0 beforeProcessEpoch - 250000 vs - 7PWei 19.903 ms/op 20.057 ms/op 0.99
altair processEpoch - mainnet_e81889 267.38 ms/op 275.28 ms/op 0.97
mainnet_e81889 - altair beforeProcessEpoch 18.075 ms/op 21.531 ms/op 0.84
mainnet_e81889 - altair processJustificationAndFinalization 6.4360 us/op 6.4690 us/op 0.99
mainnet_e81889 - altair processInactivityUpdates 4.3510 ms/op 4.9690 ms/op 0.88
mainnet_e81889 - altair processRewardsAndPenalties 20.535 ms/op 23.267 ms/op 0.88
mainnet_e81889 - altair processRegistryUpdates 767.00 ns/op 800.00 ns/op 0.96
mainnet_e81889 - altair processSlashings 197.00 ns/op 219.00 ns/op 0.90
mainnet_e81889 - altair processEth1DataReset 192.00 ns/op 211.00 ns/op 0.91
mainnet_e81889 - altair processEffectiveBalanceUpdates 7.4558 ms/op 3.4165 ms/op 2.18
mainnet_e81889 - altair processSlashingsReset 895.00 ns/op 1.0260 us/op 0.87
mainnet_e81889 - altair processRandaoMixesReset 1.1970 us/op 1.0920 us/op 1.10
mainnet_e81889 - altair processHistoricalRootsUpdate 177.00 ns/op 189.00 ns/op 0.94
mainnet_e81889 - altair processParticipationFlagUpdates 579.00 ns/op 614.00 ns/op 0.94
mainnet_e81889 - altair processSyncCommitteeUpdates 143.00 ns/op 155.00 ns/op 0.92
mainnet_e81889 - altair afterProcessEpoch 47.478 ms/op 49.848 ms/op 0.95
capella processEpoch - mainnet_e217614 800.84 ms/op 802.35 ms/op 1.00
mainnet_e217614 - capella beforeProcessEpoch 66.264 ms/op 81.772 ms/op 0.81
mainnet_e217614 - capella processJustificationAndFinalization 6.0220 us/op 6.1440 us/op 0.98
mainnet_e217614 - capella processInactivityUpdates 15.637 ms/op 16.922 ms/op 0.92
mainnet_e217614 - capella processRewardsAndPenalties 116.18 ms/op 106.56 ms/op 1.09
mainnet_e217614 - capella processRegistryUpdates 6.2450 us/op 6.8890 us/op 0.91
mainnet_e217614 - capella processSlashings 178.00 ns/op 214.00 ns/op 0.83
mainnet_e217614 - capella processEth1DataReset 170.00 ns/op 208.00 ns/op 0.82
mainnet_e217614 - capella processEffectiveBalanceUpdates 11.651 ms/op 22.742 ms/op 0.51
mainnet_e217614 - capella processSlashingsReset 861.00 ns/op 1.0230 us/op 0.84
mainnet_e217614 - capella processRandaoMixesReset 1.1480 us/op 1.2810 us/op 0.90
mainnet_e217614 - capella processHistoricalRootsUpdate 174.00 ns/op 205.00 ns/op 0.85
mainnet_e217614 - capella processParticipationFlagUpdates 500.00 ns/op 600.00 ns/op 0.83
mainnet_e217614 - capella afterProcessEpoch 121.63 ms/op 134.20 ms/op 0.91
phase0 processEpoch - mainnet_e58758 232.79 ms/op 346.07 ms/op 0.67
mainnet_e58758 - phase0 beforeProcessEpoch 46.753 ms/op 75.843 ms/op 0.62
mainnet_e58758 - phase0 processJustificationAndFinalization 5.5310 us/op 6.2560 us/op 0.88
mainnet_e58758 - phase0 processRewardsAndPenalties 18.443 ms/op 27.767 ms/op 0.66
mainnet_e58758 - phase0 processRegistryUpdates 2.9130 us/op 4.6520 us/op 0.63
mainnet_e58758 - phase0 processSlashings 169.00 ns/op 272.00 ns/op 0.62
mainnet_e58758 - phase0 processEth1DataReset 300.00 ns/op 265.00 ns/op 1.13
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 980.82 us/op 2.2150 ms/op 0.44
mainnet_e58758 - phase0 processSlashingsReset 931.00 ns/op 1.2620 us/op 0.74
mainnet_e58758 - phase0 processRandaoMixesReset 1.4090 us/op 2.0180 us/op 0.70
mainnet_e58758 - phase0 processHistoricalRootsUpdate 174.00 ns/op 232.00 ns/op 0.75
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.0190 us/op 1.0510 us/op 0.97
mainnet_e58758 - phase0 afterProcessEpoch 37.873 ms/op 42.992 ms/op 0.88
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.7231 ms/op 1.9686 ms/op 0.88
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.5861 ms/op 2.5869 ms/op 0.61
altair processInactivityUpdates - 250000 normalcase 13.478 ms/op 15.125 ms/op 0.89
altair processInactivityUpdates - 250000 worstcase 13.475 ms/op 15.353 ms/op 0.88
phase0 processRegistryUpdates - 250000 normalcase 7.5630 us/op 4.9800 us/op 1.52
phase0 processRegistryUpdates - 250000 badcase_full_deposits 367.74 us/op 286.53 us/op 1.28
phase0 processRegistryUpdates - 250000 worstcase 0.5 78.565 ms/op 81.125 ms/op 0.97
altair processRewardsAndPenalties - 250000 normalcase 18.564 ms/op 19.892 ms/op 0.93
altair processRewardsAndPenalties - 250000 worstcase 21.823 ms/op 21.300 ms/op 1.02
phase0 getAttestationDeltas - 250000 normalcase 6.9987 ms/op 8.4162 ms/op 0.83
phase0 getAttestationDeltas - 250000 worstcase 7.1906 ms/op 7.5914 ms/op 0.95
phase0 processSlashings - 250000 worstcase 127.18 us/op 85.670 us/op 1.48
altair processSyncCommitteeUpdates - 250000 11.619 ms/op 14.343 ms/op 0.81
BeaconState.hashTreeRoot - No change 207.00 ns/op 217.00 ns/op 0.95
BeaconState.hashTreeRoot - 1 full validator 86.519 us/op 98.424 us/op 0.88
BeaconState.hashTreeRoot - 32 full validator 1.0099 ms/op 1.2288 ms/op 0.82
BeaconState.hashTreeRoot - 512 full validator 7.8003 ms/op 8.0701 ms/op 0.97
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 109.63 us/op 110.83 us/op 0.99
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.5625 ms/op 1.7809 ms/op 0.88
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 22.316 ms/op 20.342 ms/op 1.10
BeaconState.hashTreeRoot - 1 balances 95.524 us/op 82.483 us/op 1.16
BeaconState.hashTreeRoot - 32 balances 816.02 us/op 885.62 us/op 0.92
BeaconState.hashTreeRoot - 512 balances 6.0551 ms/op 7.9211 ms/op 0.76
BeaconState.hashTreeRoot - 250000 balances 143.74 ms/op 194.23 ms/op 0.74
aggregationBits - 2048 els - zipIndexesInBitList 22.034 us/op 28.061 us/op 0.79
regular array get 100000 times 26.056 us/op 30.066 us/op 0.87
wrappedArray get 100000 times 26.075 us/op 29.855 us/op 0.87
arrayWithProxy get 100000 times 15.912 ms/op 16.027 ms/op 0.99
ssz.Root.equals 24.781 ns/op 26.850 ns/op 0.92
byteArrayEquals 24.255 ns/op 27.526 ns/op 0.88
Buffer.compare 10.399 ns/op 11.492 ns/op 0.90
processSlot - 1 slots 12.384 us/op 13.421 us/op 0.92
processSlot - 32 slots 2.4661 ms/op 2.6738 ms/op 0.92
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 3.8249 ms/op 3.7496 ms/op 1.02
getCommitteeAssignments - req 1 vs - 250000 vc 2.0199 ms/op 2.1313 ms/op 0.95
getCommitteeAssignments - req 100 vs - 250000 vc 3.9729 ms/op 4.3681 ms/op 0.91
getCommitteeAssignments - req 1000 vs - 250000 vc 4.2049 ms/op 4.5328 ms/op 0.93
findModifiedValidators - 10000 modified validators 704.18 ms/op 665.37 ms/op 1.06
findModifiedValidators - 1000 modified validators 564.36 ms/op 448.46 ms/op 1.26
findModifiedValidators - 100 modified validators 404.43 ms/op 162.02 ms/op 2.50
findModifiedValidators - 10 modified validators 310.53 ms/op 169.90 ms/op 1.83
findModifiedValidators - 1 modified validators 222.90 ms/op 159.40 ms/op 1.40
findModifiedValidators - no difference 239.02 ms/op 254.47 ms/op 0.94
migrate state 1500000 validators, 3400 modified, 2000 new 1.2100 s/op 1.3458 s/op 0.90
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 5.0400 ns/op 5.2000 ns/op 0.97
state getBlockRootAtSlot - 250000 vs - 7PWei 595.49 ns/op 720.37 ns/op 0.83
computeProposerIndex 100000 validators 1.7020 ms/op 1.9750 ms/op 0.86
getNextSyncCommitteeIndices 1000 validators 131.78 ms/op 139.34 ms/op 0.95
getNextSyncCommitteeIndices 10000 validators 133.16 ms/op 144.92 ms/op 0.92
getNextSyncCommitteeIndices 100000 validators 131.43 ms/op 157.53 ms/op 0.83
computeProposers - vc 250000 738.55 us/op 774.53 us/op 0.95
computeEpochShuffling - vc 250000 45.826 ms/op 47.983 ms/op 0.96
getNextSyncCommittee - vc 250000 11.670 ms/op 12.203 ms/op 0.96
nodejs block root to RootHex using toHex 148.96 ns/op 163.84 ns/op 0.91
nodejs block root to RootHex using toRootHex 91.114 ns/op 106.01 ns/op 0.86
nodejs fromHex(blob) 325.87 us/op 570.34 us/op 0.57
nodejs fromHexInto(blob) 733.31 us/op 851.92 us/op 0.86
nodejs block root to RootHex using the deprecated toHexString 548.23 ns/op 694.84 ns/op 0.79
browser block root to RootHex using toHex 292.33 ns/op 529.73 ns/op 0.55
browser block root to RootHex using toRootHex 155.49 ns/op 198.89 ns/op 0.78
browser fromHex(blob) 1.2215 ms/op 1.6801 ms/op 0.73
browser fromHexInto(blob) 759.59 us/op 1.1151 ms/op 0.68
browser block root to RootHex using the deprecated toHexString 382.54 ns/op 720.68 ns/op 0.53

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yea there was a correction to the spec after we've merged our state transition code

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

Copy link
Contributor

Choose a reason for hiding this comment

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

also fork-choice needs to index by block hashes in order to validate execution payload bid

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