Skip to content

feat: implement forkchoice is_head_weak()#9654

Open
twoeths wants to merge 2 commits into
unstablefrom
te/is_head_weak
Open

feat: implement forkchoice is_head_weak()#9654
twoeths wants to merge 2 commits into
unstablefrom
te/is_head_weak

Conversation

@twoeths

@twoeths twoeths commented Jul 14, 2026

Copy link
Copy Markdown
Member

Motivation

Description

  • implement cross-fork is_head_weak() spec api and use it in getProposerHead()
  • track attestation weight in ProtoNode separately, weight still means total weight
  • back propagate boost deltas and attestation deltas separately

AI Assistance Disclosure

  • created with the help of Claude

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

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.

Code Review

This pull request refactors the fork choice implementation to support the Gloas fork's modified is_head_weak specification. It splits the tracking of node weights into two separate components: attestationScore (representing attester votes only) and weight (which includes proposer boost). The isHeadWeak logic is extracted into a private helper method in ForkChoice that handles both pre-Gloas and post-Gloas specifications, including adding back the weight of equivocating validators for post-Gloas blocks. Additionally, deltas has been renamed to attestationDeltas across the codebase for clarity, and comprehensive unit tests have been added to verify the new behavior. I have no feedback to provide.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

@github-actions

github-actions Bot commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: 7af1505 Previous: 242f99e Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 957.67 us/op 849.75 us/op 1.13
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 39.876 us/op 40.335 us/op 0.99
BLS verify - blst 714.72 us/op 774.62 us/op 0.92
BLS verifyMultipleSignatures 3 - blst 1.3770 ms/op 1.3995 ms/op 0.98
BLS verifyMultipleSignatures 8 - blst 2.1981 ms/op 2.2066 ms/op 1.00
BLS verifyMultipleSignatures 32 - blst 6.9725 ms/op 6.8880 ms/op 1.01
BLS verifyMultipleSignatures 64 - blst 13.345 ms/op 13.358 ms/op 1.00
BLS verifyMultipleSignatures 128 - blst 26.105 ms/op 25.886 ms/op 1.01
BLS deserializing 10000 signatures 650.33 ms/op 642.21 ms/op 1.01
BLS deserializing 100000 signatures 6.5432 s/op 6.3907 s/op 1.02
BLS verifyMultipleSignatures - same message - 3 - blst 814.29 us/op 823.40 us/op 0.99
BLS verifyMultipleSignatures - same message - 8 - blst 964.12 us/op 960.64 us/op 1.00
BLS verifyMultipleSignatures - same message - 32 - blst 1.6015 ms/op 1.6328 ms/op 0.98
BLS verifyMultipleSignatures - same message - 64 - blst 2.4975 ms/op 2.3814 ms/op 1.05
BLS verifyMultipleSignatures - same message - 128 - blst 4.0728 ms/op 4.1683 ms/op 0.98
BLS aggregatePubkeys 32 - blst 18.082 us/op 17.647 us/op 1.02
BLS aggregatePubkeys 128 - blst 64.561 us/op 64.377 us/op 1.00
getSlashingsAndExits - default max 51.054 us/op 48.178 us/op 1.06
getSlashingsAndExits - 2k 408.05 us/op 327.53 us/op 1.25
proposeBlockBody type=full, size=empty 634.91 us/op 889.08 us/op 0.71
isKnown best case - 1 super set check 175.00 ns/op 160.00 ns/op 1.09
isKnown normal case - 2 super set checks 172.00 ns/op 163.00 ns/op 1.06
isKnown worse case - 16 super set checks 178.00 ns/op 159.00 ns/op 1.12
validate api signedAggregateAndProof - struct 1.5562 ms/op 1.5611 ms/op 1.00
validate gossip signedAggregateAndProof - struct 1.5567 ms/op 1.5537 ms/op 1.00
batch validate gossip attestation - vc 640000 - chunk 32 108.75 us/op 109.46 us/op 0.99
batch validate gossip attestation - vc 640000 - chunk 64 92.934 us/op 94.226 us/op 0.99
batch validate gossip attestation - vc 640000 - chunk 128 86.972 us/op 87.893 us/op 0.99
batch validate gossip attestation - vc 640000 - chunk 256 84.579 us/op 84.951 us/op 1.00
bytes32 toHexString 297.00 ns/op 299.00 ns/op 0.99
bytes32 Buffer.toString(hex) 168.00 ns/op 158.00 ns/op 1.06
bytes32 Buffer.toString(hex) from Uint8Array 239.00 ns/op 218.00 ns/op 1.10
bytes32 Buffer.toString(hex) + 0x 168.00 ns/op 158.00 ns/op 1.06
Return object 10000 times 0.21690 ns/op 0.21170 ns/op 1.02
Throw Error 10000 times 3.4505 us/op 3.3242 us/op 1.04
toHex 108.80 ns/op 99.550 ns/op 1.09
Buffer.from 94.034 ns/op 88.863 ns/op 1.06
shared Buffer 62.781 ns/op 60.890 ns/op 1.03
fastMsgIdFn sha256 / 200 bytes 1.5120 us/op 1.4880 us/op 1.02
fastMsgIdFn h32 xxhash / 200 bytes 160.00 ns/op 155.00 ns/op 1.03
fastMsgIdFn h64 xxhash / 200 bytes 209.00 ns/op 196.00 ns/op 1.07
fastMsgIdFn sha256 / 1000 bytes 4.7870 us/op 4.8110 us/op 1.00
fastMsgIdFn h32 xxhash / 1000 bytes 251.00 ns/op 245.00 ns/op 1.02
fastMsgIdFn h64 xxhash / 1000 bytes 256.00 ns/op 248.00 ns/op 1.03
fastMsgIdFn sha256 / 10000 bytes 42.256 us/op 42.939 us/op 0.98
fastMsgIdFn h32 xxhash / 10000 bytes 1.2760 us/op 1.2550 us/op 1.02
fastMsgIdFn h64 xxhash / 10000 bytes 823.00 ns/op 813.00 ns/op 1.01
send data - 1000 256B messages 4.1272 ms/op 3.9462 ms/op 1.05
send data - 1000 512B messages 5.3282 ms/op 4.8017 ms/op 1.11
send data - 1000 1024B messages 5.5829 ms/op 5.2959 ms/op 1.05
send data - 1000 1200B messages 6.8184 ms/op 6.0873 ms/op 1.12
send data - 1000 2048B messages 15.194 ms/op 17.904 ms/op 0.85
send data - 1000 4096B messages 61.482 ms/op 101.56 ms/op 0.61
send data - 1000 16384B messages 351.46 ms/op 299.08 ms/op 1.18
send data - 1000 65536B messages 1.2454 s/op 1.3788 s/op 0.90
enrSubnets - fastDeserialize 64 bits 761.00 ns/op 768.00 ns/op 0.99
enrSubnets - ssz BitVector 64 bits 265.00 ns/op 256.00 ns/op 1.04
enrSubnets - fastDeserialize 4 bits 104.00 ns/op 103.00 ns/op 1.01
enrSubnets - ssz BitVector 4 bits 269.00 ns/op 264.00 ns/op 1.02
prioritizePeers score -10:0 att 32-0.1 sync 2-0 212.61 us/op 210.45 us/op 1.01
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 242.65 us/op 239.43 us/op 1.01
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 364.58 us/op 347.13 us/op 1.05
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 630.82 us/op 625.55 us/op 1.01
prioritizePeers score 0:0 att 64-1 sync 4-1 728.10 us/op 718.79 us/op 1.01
array of 16000 items push then shift 1.3349 us/op 1.3033 us/op 1.02
LinkedList of 16000 items push then shift 7.1740 ns/op 6.9030 ns/op 1.04
array of 16000 items push then pop 70.624 ns/op 66.728 ns/op 1.06
LinkedList of 16000 items push then pop 6.0750 ns/op 5.9840 ns/op 1.02
array of 24000 items push then shift 1.9620 us/op 1.9184 us/op 1.02
LinkedList of 24000 items push then shift 6.7030 ns/op 6.5110 ns/op 1.03
array of 24000 items push then pop 102.02 ns/op 93.364 ns/op 1.09
LinkedList of 24000 items push then pop 6.1930 ns/op 6.0900 ns/op 1.02
intersect bitArray bitLen 8 4.8660 ns/op 4.7540 ns/op 1.02
intersect array and set length 8 30.735 ns/op 31.272 ns/op 0.98
intersect bitArray bitLen 128 24.421 ns/op 23.986 ns/op 1.02
intersect array and set length 128 520.81 ns/op 506.21 ns/op 1.03
bitArray.getTrueBitIndexes() bitLen 128 1.0550 us/op 1.0790 us/op 0.98
bitArray.getTrueBitIndexes() bitLen 248 1.8140 us/op 1.7720 us/op 1.02
bitArray.getTrueBitIndexes() bitLen 512 3.7390 us/op 3.5980 us/op 1.04
Full columns - reconstruct all 6 blobs 177.80 us/op 112.48 us/op 1.58
Full columns - reconstruct half of the blobs out of 6 69.897 us/op 126.12 us/op 0.55
Full columns - reconstruct single blob out of 6 35.390 us/op 32.456 us/op 1.09
Half columns - reconstruct all 6 blobs 403.00 ms/op 391.21 ms/op 1.03
Half columns - reconstruct half of the blobs out of 6 201.14 ms/op 198.02 ms/op 1.02
Half columns - reconstruct single blob out of 6 70.845 ms/op 68.949 ms/op 1.03
Set add up to 64 items then delete first 1.7163 us/op 1.6908 us/op 1.02
OrderedSet add up to 64 items then delete first 2.5949 us/op 2.5632 us/op 1.01
Set add up to 64 items then delete last 1.9491 us/op 2.0332 us/op 0.96
OrderedSet add up to 64 items then delete last 2.8728 us/op 2.8726 us/op 1.00
Set add up to 64 items then delete middle 1.9475 us/op 1.9459 us/op 1.00
OrderedSet add up to 64 items then delete middle 4.3524 us/op 4.3348 us/op 1.00
Set add up to 128 items then delete first 3.9250 us/op 3.9569 us/op 0.99
OrderedSet add up to 128 items then delete first 5.9827 us/op 5.8860 us/op 1.02
Set add up to 128 items then delete last 3.7873 us/op 3.9394 us/op 0.96
OrderedSet add up to 128 items then delete last 5.6963 us/op 5.8259 us/op 0.98
Set add up to 128 items then delete middle 3.7437 us/op 3.7874 us/op 0.99
OrderedSet add up to 128 items then delete middle 11.584 us/op 11.390 us/op 1.02
Set add up to 256 items then delete first 7.6326 us/op 7.8167 us/op 0.98
OrderedSet add up to 256 items then delete first 12.019 us/op 12.139 us/op 0.99
Set add up to 256 items then delete last 7.4206 us/op 7.9914 us/op 0.93
OrderedSet add up to 256 items then delete last 11.208 us/op 11.449 us/op 0.98
Set add up to 256 items then delete middle 7.3954 us/op 7.5067 us/op 0.99
OrderedSet add up to 256 items then delete middle 35.522 us/op 34.879 us/op 1.02
runFastConfirmationRules vc:100000 bc:96 eq:0 5.1479 ms/op 5.2062 ms/op 0.99
runFastConfirmationRules vc:600000 bc:96 eq:0 35.558 ms/op 34.913 ms/op 1.02
runFastConfirmationRules vc:1000000 bc:96 eq:0 59.991 ms/op 58.268 ms/op 1.03
runFastConfirmationRules vc:600000 bc:320 eq:0 36.290 ms/op 34.264 ms/op 1.06
runFastConfirmationRules vc:100000 bc:96 eq:1000 1.1312 s/op 1.0972 s/op 1.03
pass gossip attestations to forkchoice per slot 2.6358 ms/op 2.5620 ms/op 1.03
forkChoice updateHead vc 100000 bc 64 eq 0 443.55 us/op 469.25 us/op 0.95
forkChoice updateHead vc 600000 bc 64 eq 0 2.7392 ms/op 2.6961 ms/op 1.02
forkChoice updateHead vc 1000000 bc 64 eq 0 4.5235 ms/op 4.5438 ms/op 1.00
forkChoice updateHead vc 600000 bc 320 eq 0 2.7146 ms/op 2.7730 ms/op 0.98
forkChoice updateHead vc 600000 bc 1200 eq 0 2.8366 ms/op 2.7414 ms/op 1.03
forkChoice updateHead vc 600000 bc 7200 eq 0 3.0697 ms/op 3.0956 ms/op 0.99
forkChoice updateHead vc 600000 bc 64 eq 1000 2.7550 ms/op 2.8101 ms/op 0.98
forkChoice updateHead vc 600000 bc 64 eq 10000 2.8512 ms/op 2.8573 ms/op 1.00
forkChoice updateHead vc 600000 bc 64 eq 300000 6.7504 ms/op 6.9766 ms/op 0.97
computeDeltas 1400000 validators 0% inactive 13.517 ms/op 13.643 ms/op 0.99
computeDeltas 1400000 validators 10% inactive 12.537 ms/op 12.650 ms/op 0.99
computeDeltas 1400000 validators 20% inactive 11.886 ms/op 11.926 ms/op 1.00
computeDeltas 1400000 validators 50% inactive 9.5324 ms/op 9.6828 ms/op 0.98
computeDeltas 2100000 validators 0% inactive 20.145 ms/op 19.912 ms/op 1.01
computeDeltas 2100000 validators 10% inactive 18.566 ms/op 18.535 ms/op 1.00
computeDeltas 2100000 validators 20% inactive 18.152 ms/op 17.921 ms/op 1.01
computeDeltas 2100000 validators 50% inactive 14.362 ms/op 14.587 ms/op 0.98
altair processAttestation - 250000 vs - 7PWei normalcase 1.9578 ms/op 1.6992 ms/op 1.15
altair processAttestation - 250000 vs - 7PWei worstcase 3.1967 ms/op 2.4287 ms/op 1.32
altair processAttestation - setStatus - 1/6 committees join 110.84 us/op 100.00 us/op 1.11
altair processAttestation - setStatus - 1/3 committees join 216.00 us/op 201.11 us/op 1.07
altair processAttestation - setStatus - 1/2 committees join 307.94 us/op 287.20 us/op 1.07
altair processAttestation - setStatus - 2/3 committees join 384.95 us/op 377.70 us/op 1.02
altair processAttestation - setStatus - 4/5 committees join 534.16 us/op 526.53 us/op 1.01
altair processAttestation - setStatus - 100% committees join 646.89 us/op 627.51 us/op 1.03
altair processBlock - 250000 vs - 7PWei normalcase 3.5285 ms/op 3.0758 ms/op 1.15
altair processBlock - 250000 vs - 7PWei normalcase hashState 15.514 ms/op 11.982 ms/op 1.29
altair processBlock - 250000 vs - 7PWei worstcase 21.373 ms/op 20.063 ms/op 1.07
altair processBlock - 250000 vs - 7PWei worstcase hashState 41.714 ms/op 40.500 ms/op 1.03
phase0 processBlock - 250000 vs - 7PWei normalcase 1.3559 ms/op 1.2782 ms/op 1.06
phase0 processBlock - 250000 vs - 7PWei worstcase 17.367 ms/op 17.330 ms/op 1.00
altair processEth1Data - 250000 vs - 7PWei normalcase 313.34 us/op 302.30 us/op 1.04
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 3.5490 us/op 7.9130 us/op 0.45
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 22.370 us/op 21.598 us/op 1.04
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 6.3940 us/op 6.9170 us/op 0.92
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 3.7680 us/op 3.7450 us/op 1.01
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 97.631 us/op 97.795 us/op 1.00
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.4344 ms/op 1.4110 ms/op 1.02
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.8969 ms/op 1.8415 ms/op 1.03
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.8537 ms/op 1.8406 ms/op 1.01
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.7072 ms/op 3.6775 ms/op 1.01
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.1022 ms/op 2.0596 ms/op 1.02
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 4.1698 ms/op 4.1019 ms/op 1.02
Tree 40 250000 create 368.14 ms/op 323.94 ms/op 1.14
Tree 40 250000 get(125000) 98.919 ns/op 103.30 ns/op 0.96
Tree 40 250000 set(125000) 1.1087 us/op 1.1147 us/op 0.99
Tree 40 250000 toArray() 13.063 ms/op 9.9179 ms/op 1.32
Tree 40 250000 iterate all - toArray() + loop 13.160 ms/op 9.7793 ms/op 1.35
Tree 40 250000 iterate all - get(i) 47.820 ms/op 39.048 ms/op 1.22
Array 250000 create 2.5274 ms/op 2.1598 ms/op 1.17
Array 250000 clone - spread 794.30 us/op 666.17 us/op 1.19
Array 250000 get(125000) 0.30100 ns/op 0.29600 ns/op 1.02
Array 250000 set(125000) 0.30300 ns/op 0.29600 ns/op 1.02
Array 250000 iterate all - loop 58.404 us/op 56.803 us/op 1.03
phase0 afterProcessEpoch - 250000 vs - 7PWei 41.656 ms/op 52.527 ms/op 0.79
Array.fill - length 1000000 2.2715 ms/op 2.2633 ms/op 1.00
Array push - length 1000000 8.1723 ms/op 7.8570 ms/op 1.04
Array.get 0.20823 ns/op 0.20569 ns/op 1.01
Uint8Array.get 0.24816 ns/op 0.25173 ns/op 0.99
phase0 beforeProcessEpoch - 250000 vs - 7PWei 14.564 ms/op 14.411 ms/op 1.01
altair processEpoch - mainnet_e81889 256.00 ms/op 261.00 ms/op 0.98
mainnet_e81889 - altair beforeProcessEpoch 21.432 ms/op 14.689 ms/op 1.46
mainnet_e81889 - altair processJustificationAndFinalization 5.4600 us/op 5.4560 us/op 1.00
mainnet_e81889 - altair processInactivityUpdates 3.5801 ms/op 3.7063 ms/op 0.97
mainnet_e81889 - altair processRewardsAndPenalties 18.751 ms/op 19.795 ms/op 0.95
mainnet_e81889 - altair processRegistryUpdates 540.00 ns/op 565.00 ns/op 0.96
mainnet_e81889 - altair processSlashings 145.00 ns/op 137.00 ns/op 1.06
mainnet_e81889 - altair processEth1DataReset 145.00 ns/op 128.00 ns/op 1.13
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.2007 ms/op 1.7601 ms/op 0.68
mainnet_e81889 - altair processSlashingsReset 800.00 ns/op 694.00 ns/op 1.15
mainnet_e81889 - altair processRandaoMixesReset 1.0520 us/op 1.1210 us/op 0.94
mainnet_e81889 - altair processHistoricalRootsUpdate 146.00 ns/op 133.00 ns/op 1.10
mainnet_e81889 - altair processParticipationFlagUpdates 457.00 ns/op 433.00 ns/op 1.06
mainnet_e81889 - altair processSyncCommitteeUpdates 113.00 ns/op 117.00 ns/op 0.97
mainnet_e81889 - altair afterProcessEpoch 41.678 ms/op 43.501 ms/op 0.96
capella processEpoch - mainnet_e217614 779.70 ms/op 780.16 ms/op 1.00
mainnet_e217614 - capella beforeProcessEpoch 63.465 ms/op 57.543 ms/op 1.10
mainnet_e217614 - capella processJustificationAndFinalization 6.3490 us/op 5.4830 us/op 1.16
mainnet_e217614 - capella processInactivityUpdates 17.371 ms/op 11.423 ms/op 1.52
mainnet_e217614 - capella processRewardsAndPenalties 94.391 ms/op 91.911 ms/op 1.03
mainnet_e217614 - capella processRegistryUpdates 4.5250 us/op 4.6530 us/op 0.97
mainnet_e217614 - capella processSlashings 135.00 ns/op 131.00 ns/op 1.03
mainnet_e217614 - capella processEth1DataReset 132.00 ns/op 126.00 ns/op 1.05
mainnet_e217614 - capella processEffectiveBalanceUpdates 5.7979 ms/op 8.4449 ms/op 0.69
mainnet_e217614 - capella processSlashingsReset 706.00 ns/op 674.00 ns/op 1.05
mainnet_e217614 - capella processRandaoMixesReset 1.1090 us/op 1.0410 us/op 1.07
mainnet_e217614 - capella processHistoricalRootsUpdate 136.00 ns/op 126.00 ns/op 1.08
mainnet_e217614 - capella processParticipationFlagUpdates 448.00 ns/op 419.00 ns/op 1.07
mainnet_e217614 - capella afterProcessEpoch 109.14 ms/op 110.85 ms/op 0.98
phase0 processEpoch - mainnet_e58758 297.88 ms/op 274.52 ms/op 1.09
mainnet_e58758 - phase0 beforeProcessEpoch 62.843 ms/op 56.788 ms/op 1.11
mainnet_e58758 - phase0 processJustificationAndFinalization 5.9810 us/op 5.5860 us/op 1.07
mainnet_e58758 - phase0 processRewardsAndPenalties 16.927 ms/op 15.674 ms/op 1.08
mainnet_e58758 - phase0 processRegistryUpdates 2.3200 us/op 2.2610 us/op 1.03
mainnet_e58758 - phase0 processSlashings 263.00 ns/op 256.00 ns/op 1.03
mainnet_e58758 - phase0 processEth1DataReset 147.00 ns/op 131.00 ns/op 1.12
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 845.94 us/op 863.10 us/op 0.98
mainnet_e58758 - phase0 processSlashingsReset 894.00 ns/op 852.00 ns/op 1.05
mainnet_e58758 - phase0 processRandaoMixesReset 1.0970 us/op 1.0990 us/op 1.00
mainnet_e58758 - phase0 processHistoricalRootsUpdate 147.00 ns/op 132.00 ns/op 1.11
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.0820 us/op 960.00 ns/op 1.13
mainnet_e58758 - phase0 afterProcessEpoch 34.291 ms/op 33.471 ms/op 1.02
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.0389 ms/op 1.0212 ms/op 1.02
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.6830 ms/op 1.6336 ms/op 1.03
altair processInactivityUpdates - 250000 normalcase 10.875 ms/op 10.583 ms/op 1.03
altair processInactivityUpdates - 250000 worstcase 10.777 ms/op 10.613 ms/op 1.02
phase0 processRegistryUpdates - 250000 normalcase 2.4620 us/op 2.2770 us/op 1.08
phase0 processRegistryUpdates - 250000 badcase_full_deposits 150.59 us/op 147.14 us/op 1.02
phase0 processRegistryUpdates - 250000 worstcase 0.5 70.921 ms/op 59.306 ms/op 1.20
altair processRewardsAndPenalties - 250000 normalcase 14.603 ms/op 13.827 ms/op 1.06
altair processRewardsAndPenalties - 250000 worstcase 13.816 ms/op 13.516 ms/op 1.02
phase0 getAttestationDeltas - 250000 normalcase 5.5620 ms/op 5.3247 ms/op 1.04
phase0 getAttestationDeltas - 250000 worstcase 5.7070 ms/op 5.3756 ms/op 1.06
phase0 processSlashings - 250000 worstcase 67.660 us/op 60.273 us/op 1.12
altair processSyncCommitteeUpdates - 250000 10.514 ms/op 9.9461 ms/op 1.06
BeaconState.hashTreeRoot - No change 179.00 ns/op 173.00 ns/op 1.03
BeaconState.hashTreeRoot - 1 full validator 74.710 us/op 56.923 us/op 1.31
BeaconState.hashTreeRoot - 32 full validator 855.31 us/op 665.08 us/op 1.29
BeaconState.hashTreeRoot - 512 full validator 8.8003 ms/op 5.8858 ms/op 1.50
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 100.17 us/op 68.798 us/op 1.46
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.6417 ms/op 1.0610 ms/op 1.55
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 17.727 ms/op 11.897 ms/op 1.49
BeaconState.hashTreeRoot - 1 balances 78.245 us/op 71.137 us/op 1.10
BeaconState.hashTreeRoot - 32 balances 770.08 us/op 562.37 us/op 1.37
BeaconState.hashTreeRoot - 512 balances 6.6796 ms/op 4.6270 ms/op 1.44
BeaconState.hashTreeRoot - 250000 balances 136.87 ms/op 106.70 ms/op 1.28
aggregationBits - 2048 els - zipIndexesInBitList 22.627 us/op 19.844 us/op 1.14
regular array get 100000 times 24.025 us/op 22.863 us/op 1.05
wrappedArray get 100000 times 23.972 us/op 22.756 us/op 1.05
arrayWithProxy get 100000 times 11.368 ms/op 17.410 ms/op 0.65
ssz.Root.equals 22.262 ns/op 21.518 ns/op 1.03
byteArrayEquals 22.039 ns/op 21.170 ns/op 1.04
Buffer.compare 9.1150 ns/op 9.3030 ns/op 0.98
processSlot - 1 slots 10.382 us/op 7.9370 us/op 1.31
processSlot - 32 slots 1.9346 ms/op 1.5649 ms/op 1.24
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 3.4987 ms/op 1.9117 ms/op 1.83
getCommitteeAssignments - req 1 vs - 250000 vc 1.7486 ms/op 1.6804 ms/op 1.04
getCommitteeAssignments - req 100 vs - 250000 vc 3.5417 ms/op 3.3894 ms/op 1.04
getCommitteeAssignments - req 1000 vs - 250000 vc 3.8011 ms/op 3.6354 ms/op 1.05
findModifiedValidators - 10000 modified validators 953.30 ms/op 650.38 ms/op 1.47
findModifiedValidators - 1000 modified validators 555.83 ms/op 674.03 ms/op 0.82
findModifiedValidators - 100 modified validators 304.85 ms/op 383.39 ms/op 0.80
findModifiedValidators - 10 modified validators 302.99 ms/op 146.11 ms/op 2.07
findModifiedValidators - 1 modified validators 135.20 ms/op 157.95 ms/op 0.86
findModifiedValidators - no difference 187.11 ms/op 160.99 ms/op 1.16
migrate state 1500000 validators, 3400 modified, 2000 new 2.9362 s/op 2.6809 s/op 1.10
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 3.9600 ns/op 3.7000 ns/op 1.07
state getBlockRootAtSlot - 250000 vs - 7PWei 346.14 ns/op 270.43 ns/op 1.28
computeProposerIndex 100000 validators 1.4108 ms/op 1.3293 ms/op 1.06
getNextSyncCommitteeIndices 1000 validators 3.0310 ms/op 2.8647 ms/op 1.06
getNextSyncCommitteeIndices 10000 validators 27.170 ms/op 25.300 ms/op 1.07
getNextSyncCommitteeIndices 100000 validators 90.973 ms/op 87.516 ms/op 1.04
computeProposers - vc 250000 553.54 us/op 548.54 us/op 1.01
computeEpochShuffling - vc 250000 40.997 ms/op 39.375 ms/op 1.04
getNextSyncCommittee - vc 250000 9.8560 ms/op 9.5858 ms/op 1.03
nodejs block root to RootHex using toHex 104.04 ns/op 103.12 ns/op 1.01
nodejs block root to RootHex using toRootHex 64.101 ns/op 67.877 ns/op 0.94
nodejs fromHex(blob) 770.54 us/op 792.46 us/op 0.97
nodejs fromHexInto(blob) 646.09 us/op 649.46 us/op 0.99
nodejs block root to RootHex using the deprecated toHexString 482.57 ns/op 491.75 ns/op 0.98
nodejs byteArrayEquals 32 bytes (block root) 27.065 ns/op 26.350 ns/op 1.03
nodejs byteArrayEquals 48 bytes (pubkey) 38.866 ns/op 38.259 ns/op 1.02
nodejs byteArrayEquals 96 bytes (signature) 36.114 ns/op 36.405 ns/op 0.99
nodejs byteArrayEquals 1024 bytes 42.230 ns/op 43.771 ns/op 0.96
nodejs byteArrayEquals 131072 bytes (blob) 1.8100 us/op 1.7840 us/op 1.01
browser block root to RootHex using toHex 148.50 ns/op 147.11 ns/op 1.01
browser block root to RootHex using toRootHex 134.97 ns/op 132.43 ns/op 1.02
browser fromHex(blob) 1.5052 ms/op 1.6748 ms/op 0.90
browser fromHexInto(blob) 645.32 us/op 654.91 us/op 0.99
browser block root to RootHex using the deprecated toHexString 334.37 ns/op 345.30 ns/op 0.97
browser byteArrayEquals 32 bytes (block root) 28.718 ns/op 28.109 ns/op 1.02
browser byteArrayEquals 48 bytes (pubkey) 40.782 ns/op 39.616 ns/op 1.03
browser byteArrayEquals 96 bytes (signature) 76.549 ns/op 74.107 ns/op 1.03
browser byteArrayEquals 1024 bytes 777.86 ns/op 755.51 ns/op 1.03
browser byteArrayEquals 131072 bytes (blob) 98.004 us/op 95.201 us/op 1.03

by benchmarkbot/action

@twoeths twoeths marked this pull request as ready for review July 14, 2026 11:05
@twoeths twoeths requested a review from a team as a code owner July 14, 2026 11:05
@codecov

codecov Bot commented Jul 14, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.48%. Comparing base (242f99e) to head (6e4cd4e).

Additional details and impacted files
@@             Coverage Diff              @@
##           unstable    #9654      +/-   ##
============================================
- Coverage     52.49%   52.48%   -0.02%     
============================================
  Files           848      848              
  Lines         60485    60458      -27     
  Branches       4466     4459       -7     
============================================
- Hits          31754    31732      -22     
+ Misses        28670    28665       -5     
  Partials         61       61              
🚀 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.

// The default variant is PENDING for gloas, FULL pre-gloas. PENDING is the variant gloas measures
// support on, ie. support for the beacon block root regardless of its payload status.
// Only ever called on a block already in fork choice, so a miss is a broken invariant.
const node = this.protoArray.getNodeDefaultStatus(blockRoot);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

So we already have an established pattern of getDefaultNodeIndex + getNodeByIndex in the code ie.

const nodeIndex = this.getDefaultNodeIndex(blockRoot);
const node = nodeIndex !== undefined ? this.getNodeByIndex(nodeIndex) : undefined;
if (node === undefined || node.slot !== slot) {
return;
}

const proposerBoostIndex = this.getDefaultNodeIndex(proposerBoostRoot);
const proposerBoostBlock = proposerBoostIndex !== undefined ? this.getNodeByIndex(proposerBoostIndex) : undefined;
if (!proposerBoostBlock) {
// Proposer boost block not found, default to extending payload
return true;
}

and

const justifiedIndex = this.getDefaultNodeIndex(justifiedRoot);
if (justifiedIndex === undefined) {
throw new ProtoArrayError({
code: ProtoArrayErrorCode.JUSTIFIED_NODE_UNKNOWN,
root: justifiedRoot,
});
}
const justifiedNode = this.nodes[justifiedIndex];
if (justifiedNode === undefined) {
throw new ProtoArrayError({
code: ProtoArrayErrorCode.INVALID_JUSTIFIED_INDEX,
index: justifiedIndex,
});
}

So we either keep that pattern, or refactor so these 3 places also use this.protoArray.getNodeDefaultStatus

const parentStrongVariant = isGloasBlock(parentBlock) ? PayloadStatus.PENDING : PayloadStatus.FULL;
const parentNode = this.protoArray.getNode(parentBlock.blockRoot, parentStrongVariant);
// If parentNode is unavailable, give up reorg
if (parentNode === undefined || parentNode.weight <= parentThreshold) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Make sure is_parent_strong is using parentNode.attestationScore post-gloas, and keep parentNode.weight pre-gloas.
Because gloas's spec uses get_attestation_score

for (let index = 0; index < state.getBeaconCommitteeCountPerSlot(epoch); index++) {
for (const validatorIndex of state.getBeaconCommittee(node.slot, index)) {
if (equivocatingIndices.has(validatorIndex)) {
headWeight += this.fcStore.justified.balances[validatorIndex] ?? 0;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We can't store's justified balances here because it's 0 for equivocating validators.

Because we zero them here

if (slashed) {
// slashed validator
effectiveBalanceIncrementsZeroInactive[i] = 0;
}

that ultimately get propagated to store's justified balances.

We need the justified state's effectiveBalanceIncrements to get the non-zero'ed balance of equivocators.

node.weight += nodeDelta;

// Update the parent delta (if any)
const isInvalid = node.executionStatus === ExecutionStatus.Invalid;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We probably need a unit test to test the code branch of node.executionStatus === ExecutionStatus.Invalid

// Only ever called on a block already in fork choice, so a miss is a broken invariant.
const node = this.protoArray.getNodeDefaultStatus(blockRoot);
if (node === undefined) {
throw new ForkChoiceError({code: ForkChoiceErrorCode.MISSING_PROTO_ARRAY_BLOCK, root: blockRoot});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Note that if we throw error from this function, the error is uncaught all the way up to produceBlockV4 and produceEngineOrBuilderBlock leading to the entire block not being produced. I think if there is error, we should not re-org. We probably need to add a NotReorgedReason.ErrorEncountered or something similar if this happens.

Same with BEACON_STATE_ERROR below

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