Skip to content

feat: implement EIP-8045 exclude slashed validators from proposing#9422

Merged
nflaig merged 6 commits into
unstablefrom
nflaig/eip-8045
Jun 2, 2026
Merged

feat: implement EIP-8045 exclude slashed validators from proposing#9422
nflaig merged 6 commits into
unstablefrom
nflaig/eip-8045

Conversation

@nflaig

@nflaig nflaig commented May 29, 2026

Copy link
Copy Markdown
Member

@nflaig nflaig requested a review from a team as a code owner May 29, 2026 14:23

@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 updates the processProposerLookahead function to exclude slashed validators from proposing under EIP-8045, specifically when the fork is greater than or equal to ForkSeq.gloas. It filters shuffling.activeIndices using the FLAG_UNSLASHED flag before passing them to computeProposerIndices. There are no review comments, so I have no feedback to provide.

@github-actions

github-actions Bot commented May 29, 2026

Copy link
Copy Markdown
Contributor

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: d3cf586 Previous: 2e689b4 Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 1.4149 ms/op 1.0785 ms/op 1.31
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 39.393 us/op 43.636 us/op 0.90
BLS verify - blst 733.99 us/op 654.89 us/op 1.12
BLS verifyMultipleSignatures 3 - blst 1.3494 ms/op 1.4150 ms/op 0.95
BLS verifyMultipleSignatures 8 - blst 2.1475 ms/op 2.4208 ms/op 0.89
BLS verifyMultipleSignatures 32 - blst 6.8309 ms/op 6.9483 ms/op 0.98
BLS verifyMultipleSignatures 64 - blst 13.135 ms/op 13.124 ms/op 1.00
BLS verifyMultipleSignatures 128 - blst 26.076 ms/op 25.597 ms/op 1.02
BLS deserializing 10000 signatures 637.81 ms/op 648.43 ms/op 0.98
BLS deserializing 100000 signatures 6.3481 s/op 6.4719 s/op 0.98
BLS verifyMultipleSignatures - same message - 3 - blst 776.03 us/op 770.43 us/op 1.01
BLS verifyMultipleSignatures - same message - 8 - blst 945.19 us/op 899.24 us/op 1.05
BLS verifyMultipleSignatures - same message - 32 - blst 1.5723 ms/op 1.4809 ms/op 1.06
BLS verifyMultipleSignatures - same message - 64 - blst 2.3082 ms/op 2.3849 ms/op 0.97
BLS verifyMultipleSignatures - same message - 128 - blst 3.9726 ms/op 4.1197 ms/op 0.96
BLS aggregatePubkeys 32 - blst 17.535 us/op 18.066 us/op 0.97
BLS aggregatePubkeys 128 - blst 62.512 us/op 64.395 us/op 0.97
getSlashingsAndExits - default max 53.176 us/op 61.716 us/op 0.86
getSlashingsAndExits - 2k 321.88 us/op 409.46 us/op 0.79
proposeBlockBody type=full, size=empty 719.40 us/op 874.92 us/op 0.82
isKnown best case - 1 super set check 161.00 ns/op 172.00 ns/op 0.94
isKnown normal case - 2 super set checks 164.00 ns/op 169.00 ns/op 0.97
isKnown worse case - 16 super set checks 162.00 ns/op 170.00 ns/op 0.95
validate api signedAggregateAndProof - struct 1.5033 ms/op 1.5184 ms/op 0.99
validate gossip signedAggregateAndProof - struct 1.5015 ms/op 1.5206 ms/op 0.99
batch validate gossip attestation - vc 640000 - chunk 32 104.65 us/op 109.27 us/op 0.96
batch validate gossip attestation - vc 640000 - chunk 64 90.462 us/op 99.336 us/op 0.91
batch validate gossip attestation - vc 640000 - chunk 128 85.363 us/op 92.500 us/op 0.92
batch validate gossip attestation - vc 640000 - chunk 256 81.758 us/op 89.211 us/op 0.92
bytes32 toHexString 296.00 ns/op 287.00 ns/op 1.03
bytes32 Buffer.toString(hex) 170.00 ns/op 165.00 ns/op 1.03
bytes32 Buffer.toString(hex) from Uint8Array 233.00 ns/op 234.00 ns/op 1.00
bytes32 Buffer.toString(hex) + 0x 171.00 ns/op 159.00 ns/op 1.08
Return object 10000 times 0.21120 ns/op 0.21370 ns/op 0.99
Throw Error 10000 times 3.2640 us/op 3.3065 us/op 0.99
toHex 93.361 ns/op 88.527 ns/op 1.05
Buffer.from 83.649 ns/op 86.095 ns/op 0.97
shared Buffer 57.016 ns/op 52.477 ns/op 1.09
fastMsgIdFn sha256 / 200 bytes 1.4600 us/op 1.4730 us/op 0.99
fastMsgIdFn h32 xxhash / 200 bytes 152.00 ns/op 158.00 ns/op 0.96
fastMsgIdFn h64 xxhash / 200 bytes 206.00 ns/op 205.00 ns/op 1.00
fastMsgIdFn sha256 / 1000 bytes 4.7180 us/op 4.8000 us/op 0.98
fastMsgIdFn h32 xxhash / 1000 bytes 237.00 ns/op 249.00 ns/op 0.95
fastMsgIdFn h64 xxhash / 1000 bytes 255.00 ns/op 249.00 ns/op 1.02
fastMsgIdFn sha256 / 10000 bytes 41.770 us/op 43.298 us/op 0.96
fastMsgIdFn h32 xxhash / 10000 bytes 1.2740 us/op 1.2640 us/op 1.01
fastMsgIdFn h64 xxhash / 10000 bytes 821.00 ns/op 804.00 ns/op 1.02
send data - 1000 256B messages 4.0143 ms/op 4.9460 ms/op 0.81
send data - 1000 512B messages 4.1623 ms/op 4.9954 ms/op 0.83
send data - 1000 1024B messages 4.2957 ms/op 5.1021 ms/op 0.84
send data - 1000 1200B messages 4.4634 ms/op 5.5398 ms/op 0.81
send data - 1000 2048B messages 4.7374 ms/op 6.1396 ms/op 0.77
send data - 1000 4096B messages 5.5650 ms/op 7.0511 ms/op 0.79
send data - 1000 16384B messages 21.170 ms/op 22.447 ms/op 0.94
send data - 1000 65536B messages 217.52 ms/op 120.61 ms/op 1.80
enrSubnets - fastDeserialize 64 bits 739.00 ns/op 733.00 ns/op 1.01
enrSubnets - ssz BitVector 64 bits 254.00 ns/op 251.00 ns/op 1.01
enrSubnets - fastDeserialize 4 bits 103.00 ns/op 102.00 ns/op 1.01
enrSubnets - ssz BitVector 4 bits 252.00 ns/op 247.00 ns/op 1.02
prioritizePeers score -10:0 att 32-0.1 sync 2-0 202.36 us/op 207.10 us/op 0.98
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 235.04 us/op 425.28 us/op 0.55
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 346.52 us/op 358.49 us/op 0.97
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 609.54 us/op 611.20 us/op 1.00
prioritizePeers score 0:0 att 64-1 sync 4-1 684.45 us/op 695.35 us/op 0.98
array of 16000 items push then shift 1.2942 us/op 1.2197 us/op 1.06
LinkedList of 16000 items push then shift 7.1880 ns/op 7.5140 ns/op 0.96
array of 16000 items push then pop 68.836 ns/op 67.554 ns/op 1.02
LinkedList of 16000 items push then pop 5.9440 ns/op 5.9840 ns/op 0.99
array of 24000 items push then shift 1.9029 us/op 1.7932 us/op 1.06
LinkedList of 24000 items push then shift 6.6960 ns/op 7.4560 ns/op 0.90
array of 24000 items push then pop 98.005 ns/op 98.161 ns/op 1.00
LinkedList of 24000 items push then pop 5.9650 ns/op 6.4230 ns/op 0.93
intersect bitArray bitLen 8 4.7670 ns/op 4.6700 ns/op 1.02
intersect array and set length 8 29.351 ns/op 28.516 ns/op 1.03
intersect bitArray bitLen 128 24.072 ns/op 23.160 ns/op 1.04
intersect array and set length 128 499.30 ns/op 487.02 ns/op 1.03
bitArray.getTrueBitIndexes() bitLen 128 1.0620 us/op 1.0110 us/op 1.05
bitArray.getTrueBitIndexes() bitLen 248 1.7620 us/op 1.7400 us/op 1.01
bitArray.getTrueBitIndexes() bitLen 512 3.4770 us/op 3.5580 us/op 0.98
Full columns - reconstruct all 6 blobs 181.58 us/op 218.00 us/op 0.83
Full columns - reconstruct half of the blobs out of 6 116.25 us/op 104.45 us/op 1.11
Full columns - reconstruct single blob out of 6 31.690 us/op 41.269 us/op 0.77
Half columns - reconstruct all 6 blobs 379.59 ms/op 380.79 ms/op 1.00
Half columns - reconstruct half of the blobs out of 6 191.21 ms/op 190.13 ms/op 1.01
Half columns - reconstruct single blob out of 6 67.236 ms/op 68.187 ms/op 0.99
Full columns - reconstruct all 10 blobs 291.48 us/op 298.97 us/op 0.97
Full columns - reconstruct half of the blobs out of 10 116.37 us/op 199.80 us/op 0.58
Full columns - reconstruct single blob out of 10 34.332 us/op 60.783 us/op 0.56
Half columns - reconstruct all 10 blobs 640.87 ms/op 646.12 ms/op 0.99
Half columns - reconstruct half of the blobs out of 10 318.24 ms/op 325.10 ms/op 0.98
Half columns - reconstruct single blob out of 10 66.229 ms/op 71.936 ms/op 0.92
Full columns - reconstruct all 20 blobs 706.70 us/op 600.25 us/op 1.18
Full columns - reconstruct half of the blobs out of 20 229.42 us/op 342.49 us/op 0.67
Full columns - reconstruct single blob out of 20 32.416 us/op 30.670 us/op 1.06
Half columns - reconstruct all 20 blobs 1.2672 s/op 1.2549 s/op 1.01
Half columns - reconstruct half of the blobs out of 20 643.94 ms/op 632.71 ms/op 1.02
Half columns - reconstruct single blob out of 20 67.376 ms/op 66.834 ms/op 1.01
Set add up to 64 items then delete first 2.1250 us/op 1.9740 us/op 1.08
OrderedSet add up to 64 items then delete first 3.3725 us/op 3.1543 us/op 1.07
Set add up to 64 items then delete last 2.1410 us/op 2.0294 us/op 1.05
OrderedSet add up to 64 items then delete last 3.2784 us/op 3.2489 us/op 1.01
Set add up to 64 items then delete middle 2.1227 us/op 2.0964 us/op 1.01
OrderedSet add up to 64 items then delete middle 4.7692 us/op 4.7924 us/op 1.00
Set add up to 128 items then delete first 4.2159 us/op 4.1395 us/op 1.02
OrderedSet add up to 128 items then delete first 6.5507 us/op 5.8876 us/op 1.11
Set add up to 128 items then delete last 3.8585 us/op 3.9331 us/op 0.98
OrderedSet add up to 128 items then delete last 5.7834 us/op 7.6562 us/op 0.76
Set add up to 128 items then delete middle 3.8372 us/op 4.8757 us/op 0.79
OrderedSet add up to 128 items then delete middle 11.610 us/op 14.240 us/op 0.82
Set add up to 256 items then delete first 7.7924 us/op 9.3791 us/op 0.83
OrderedSet add up to 256 items then delete first 12.107 us/op 16.319 us/op 0.74
Set add up to 256 items then delete last 7.5928 us/op 10.262 us/op 0.74
OrderedSet add up to 256 items then delete last 11.429 us/op 14.088 us/op 0.81
Set add up to 256 items then delete middle 7.5698 us/op 8.0674 us/op 0.94
OrderedSet add up to 256 items then delete middle 34.883 us/op 39.805 us/op 0.88
pass gossip attestations to forkchoice per slot 2.6417 ms/op 2.4974 ms/op 1.06
forkChoice updateHead vc 100000 bc 64 eq 0 405.19 us/op 389.86 us/op 1.04
forkChoice updateHead vc 600000 bc 64 eq 0 2.3988 ms/op 2.2227 ms/op 1.08
forkChoice updateHead vc 1000000 bc 64 eq 0 4.0368 ms/op 3.7068 ms/op 1.09
forkChoice updateHead vc 600000 bc 320 eq 0 2.4261 ms/op 2.2506 ms/op 1.08
forkChoice updateHead vc 600000 bc 1200 eq 0 2.4402 ms/op 2.3640 ms/op 1.03
forkChoice updateHead vc 600000 bc 7200 eq 0 2.7251 ms/op 3.3149 ms/op 0.82
forkChoice updateHead vc 600000 bc 64 eq 1000 2.9500 ms/op 2.7823 ms/op 1.06
forkChoice updateHead vc 600000 bc 64 eq 10000 3.0206 ms/op 3.0262 ms/op 1.00
forkChoice updateHead vc 600000 bc 64 eq 300000 7.3019 ms/op 7.0111 ms/op 1.04
computeDeltas 1400000 validators 0% inactive 12.691 ms/op 12.515 ms/op 1.01
computeDeltas 1400000 validators 10% inactive 11.857 ms/op 11.812 ms/op 1.00
computeDeltas 1400000 validators 20% inactive 10.814 ms/op 10.596 ms/op 1.02
computeDeltas 1400000 validators 50% inactive 8.4196 ms/op 8.0472 ms/op 1.05
computeDeltas 2100000 validators 0% inactive 19.230 ms/op 18.706 ms/op 1.03
computeDeltas 2100000 validators 10% inactive 18.002 ms/op 17.803 ms/op 1.01
computeDeltas 2100000 validators 20% inactive 16.315 ms/op 16.165 ms/op 1.01
computeDeltas 2100000 validators 50% inactive 9.5803 ms/op 9.5266 ms/op 1.01
altair processAttestation - 250000 vs - 7PWei normalcase 1.7928 ms/op 2.4686 ms/op 0.73
altair processAttestation - 250000 vs - 7PWei worstcase 2.5567 ms/op 3.5976 ms/op 0.71
altair processAttestation - setStatus - 1/6 committees join 109.77 us/op 96.782 us/op 1.13
altair processAttestation - setStatus - 1/3 committees join 199.36 us/op 219.50 us/op 0.91
altair processAttestation - setStatus - 1/2 committees join 289.80 us/op 281.15 us/op 1.03
altair processAttestation - setStatus - 2/3 committees join 384.69 us/op 355.96 us/op 1.08
altair processAttestation - setStatus - 4/5 committees join 534.81 us/op 540.14 us/op 0.99
altair processAttestation - setStatus - 100% committees join 623.88 us/op 613.75 us/op 1.02
altair processBlock - 250000 vs - 7PWei normalcase 3.1148 ms/op 4.4057 ms/op 0.71
altair processBlock - 250000 vs - 7PWei normalcase hashState 14.269 ms/op 17.246 ms/op 0.83
altair processBlock - 250000 vs - 7PWei worstcase 20.721 ms/op 22.803 ms/op 0.91
altair processBlock - 250000 vs - 7PWei worstcase hashState 39.954 ms/op 42.375 ms/op 0.94
phase0 processBlock - 250000 vs - 7PWei normalcase 1.2241 ms/op 1.4336 ms/op 0.85
phase0 processBlock - 250000 vs - 7PWei worstcase 17.375 ms/op 17.429 ms/op 1.00
altair processEth1Data - 250000 vs - 7PWei normalcase 290.54 us/op 275.82 us/op 1.05
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 3.3810 us/op 3.0100 us/op 1.12
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 21.144 us/op 18.765 us/op 1.13
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 5.9760 us/op 5.6920 us/op 1.05
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 3.6530 us/op 4.4060 us/op 0.83
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 94.871 us/op 86.826 us/op 1.09
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.3621 ms/op 1.4322 ms/op 0.95
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.8050 ms/op 1.8114 ms/op 1.00
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.7634 ms/op 1.7985 ms/op 0.98
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.5761 ms/op 4.5213 ms/op 0.79
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.0195 ms/op 2.0123 ms/op 1.00
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 3.8560 ms/op 3.7384 ms/op 1.03
Tree 40 250000 create 298.52 ms/op 335.87 ms/op 0.89
Tree 40 250000 get(125000) 96.138 ns/op 88.860 ns/op 1.08
Tree 40 250000 set(125000) 1.0277 us/op 967.62 ns/op 1.06
Tree 40 250000 toArray() 9.2139 ms/op 16.364 ms/op 0.56
Tree 40 250000 iterate all - toArray() + loop 8.9097 ms/op 21.474 ms/op 0.41
Tree 40 250000 iterate all - get(i) 34.291 ms/op 42.052 ms/op 0.82
Array 250000 create 1.9531 ms/op 2.3758 ms/op 0.82
Array 250000 clone - spread 629.85 us/op 716.48 us/op 0.88
Array 250000 get(125000) 0.29200 ns/op 0.29900 ns/op 0.98
Array 250000 set(125000) 0.29400 ns/op 0.30400 ns/op 0.97
Array 250000 iterate all - loop 56.150 us/op 57.676 us/op 0.97
phase0 afterProcessEpoch - 250000 vs - 7PWei 38.874 ms/op 47.557 ms/op 0.82
Array.fill - length 1000000 2.3306 ms/op 2.9463 ms/op 0.79
Array push - length 1000000 7.3556 ms/op 10.147 ms/op 0.72
Array.get 0.20390 ns/op 0.20565 ns/op 0.99
Uint8Array.get 0.25298 ns/op 0.23343 ns/op 1.08
phase0 beforeProcessEpoch - 250000 vs - 7PWei 11.078 ms/op 17.140 ms/op 0.65
altair processEpoch - mainnet_e81889 220.40 ms/op 267.08 ms/op 0.83
mainnet_e81889 - altair beforeProcessEpoch 18.193 ms/op 18.222 ms/op 1.00
mainnet_e81889 - altair processJustificationAndFinalization 4.6570 us/op 7.1760 us/op 0.65
mainnet_e81889 - altair processInactivityUpdates 3.4373 ms/op 7.1201 ms/op 0.48
mainnet_e81889 - altair processRewardsAndPenalties 16.248 ms/op 22.439 ms/op 0.72
mainnet_e81889 - altair processRegistryUpdates 520.00 ns/op 550.00 ns/op 0.95
mainnet_e81889 - altair processSlashings 129.00 ns/op 146.00 ns/op 0.88
mainnet_e81889 - altair processEth1DataReset 129.00 ns/op 136.00 ns/op 0.95
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.1858 ms/op 6.0572 ms/op 0.20
mainnet_e81889 - altair processSlashingsReset 687.00 ns/op 683.00 ns/op 1.01
mainnet_e81889 - altair processRandaoMixesReset 1.0140 us/op 1.3390 us/op 0.76
mainnet_e81889 - altair processHistoricalRootsUpdate 127.00 ns/op 132.00 ns/op 0.96
mainnet_e81889 - altair processParticipationFlagUpdates 415.00 ns/op 444.00 ns/op 0.93
mainnet_e81889 - altair processSyncCommitteeUpdates 101.00 ns/op 112.00 ns/op 0.90
mainnet_e81889 - altair afterProcessEpoch 40.997 ms/op 41.694 ms/op 0.98
capella processEpoch - mainnet_e217614 730.93 ms/op 764.40 ms/op 0.96
mainnet_e217614 - capella beforeProcessEpoch 53.713 ms/op 55.066 ms/op 0.98
mainnet_e217614 - capella processJustificationAndFinalization 5.5480 us/op 5.4190 us/op 1.02
mainnet_e217614 - capella processInactivityUpdates 11.047 ms/op 14.346 ms/op 0.77
mainnet_e217614 - capella processRewardsAndPenalties 84.493 ms/op 84.333 ms/op 1.00
mainnet_e217614 - capella processRegistryUpdates 4.4180 us/op 4.3540 us/op 1.01
mainnet_e217614 - capella processSlashings 135.00 ns/op 127.00 ns/op 1.06
mainnet_e217614 - capella processEth1DataReset 125.00 ns/op 127.00 ns/op 0.98
mainnet_e217614 - capella processEffectiveBalanceUpdates 5.2436 ms/op 11.857 ms/op 0.44
mainnet_e217614 - capella processSlashingsReset 664.00 ns/op 653.00 ns/op 1.02
mainnet_e217614 - capella processRandaoMixesReset 1.0730 us/op 1.0880 us/op 0.99
mainnet_e217614 - capella processHistoricalRootsUpdate 127.00 ns/op 125.00 ns/op 1.02
mainnet_e217614 - capella processParticipationFlagUpdates 422.00 ns/op 408.00 ns/op 1.03
mainnet_e217614 - capella afterProcessEpoch 104.42 ms/op 105.29 ms/op 0.99
phase0 processEpoch - mainnet_e58758 243.54 ms/op 289.13 ms/op 0.84
mainnet_e58758 - phase0 beforeProcessEpoch 46.528 ms/op 60.324 ms/op 0.77
mainnet_e58758 - phase0 processJustificationAndFinalization 4.7720 us/op 5.6000 us/op 0.85
mainnet_e58758 - phase0 processRewardsAndPenalties 14.619 ms/op 15.269 ms/op 0.96
mainnet_e58758 - phase0 processRegistryUpdates 2.2110 us/op 2.1750 us/op 1.02
mainnet_e58758 - phase0 processSlashings 127.00 ns/op 130.00 ns/op 0.98
mainnet_e58758 - phase0 processEth1DataReset 124.00 ns/op 259.00 ns/op 0.48
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 812.18 us/op 800.18 us/op 1.01
mainnet_e58758 - phase0 processSlashingsReset 780.00 ns/op 822.00 ns/op 0.95
mainnet_e58758 - phase0 processRandaoMixesReset 1.0630 us/op 1.1910 us/op 0.89
mainnet_e58758 - phase0 processHistoricalRootsUpdate 126.00 ns/op 126.00 ns/op 1.00
mainnet_e58758 - phase0 processParticipationRecordUpdates 999.00 ns/op 951.00 ns/op 1.05
mainnet_e58758 - phase0 afterProcessEpoch 33.195 ms/op 32.005 ms/op 1.04
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.0014 ms/op 947.75 us/op 1.06
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.5612 ms/op 1.9485 ms/op 0.80
altair processInactivityUpdates - 250000 normalcase 10.335 ms/op 10.327 ms/op 1.00
altair processInactivityUpdates - 250000 worstcase 10.325 ms/op 10.192 ms/op 1.01
phase0 processRegistryUpdates - 250000 normalcase 2.1340 us/op 2.1810 us/op 0.98
phase0 processRegistryUpdates - 250000 badcase_full_deposits 143.00 us/op 130.04 us/op 1.10
phase0 processRegistryUpdates - 250000 worstcase 0.5 46.538 ms/op 59.517 ms/op 0.78
altair processRewardsAndPenalties - 250000 normalcase 13.474 ms/op 16.003 ms/op 0.84
altair processRewardsAndPenalties - 250000 worstcase 13.225 ms/op 15.256 ms/op 0.87
phase0 getAttestationDeltas - 250000 normalcase 5.3015 ms/op 5.0876 ms/op 1.04
phase0 getAttestationDeltas - 250000 worstcase 5.3071 ms/op 7.9911 ms/op 0.66
phase0 processSlashings - 250000 worstcase 58.067 us/op 56.276 us/op 1.03
altair processSyncCommitteeUpdates - 250000 9.9480 ms/op 9.5739 ms/op 1.04
BeaconState.hashTreeRoot - No change 169.00 ns/op 172.00 ns/op 0.98
BeaconState.hashTreeRoot - 1 full validator 55.353 us/op 78.398 us/op 0.71
BeaconState.hashTreeRoot - 32 full validator 614.63 us/op 819.77 us/op 0.75
BeaconState.hashTreeRoot - 512 full validator 5.7757 ms/op 8.7617 ms/op 0.66
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 68.316 us/op 95.440 us/op 0.72
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.0074 ms/op 1.3696 ms/op 0.74
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 12.016 ms/op 17.029 ms/op 0.71
BeaconState.hashTreeRoot - 1 balances 53.768 us/op 70.368 us/op 0.76
BeaconState.hashTreeRoot - 32 balances 521.86 us/op 700.26 us/op 0.75
BeaconState.hashTreeRoot - 512 balances 4.5578 ms/op 5.9649 ms/op 0.76
BeaconState.hashTreeRoot - 250000 balances 97.679 ms/op 126.22 ms/op 0.77
aggregationBits - 2048 els - zipIndexesInBitList 18.729 us/op 18.314 us/op 1.02
regular array get 100000 times 22.469 us/op 21.951 us/op 1.02
wrappedArray get 100000 times 22.490 us/op 22.112 us/op 1.02
arrayWithProxy get 100000 times 10.206 ms/op 16.791 ms/op 0.61
ssz.Root.equals 21.242 ns/op 20.547 ns/op 1.03
byteArrayEquals 21.065 ns/op 20.422 ns/op 1.03
Buffer.compare 8.7640 ns/op 8.4600 ns/op 1.04
processSlot - 1 slots 7.7950 us/op 8.6800 us/op 0.90
processSlot - 32 slots 1.5207 ms/op 1.8763 ms/op 0.81
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 2.7534 ms/op 3.5573 ms/op 0.77
getCommitteeAssignments - req 1 vs - 250000 vc 1.6678 ms/op 1.5898 ms/op 1.05
getCommitteeAssignments - req 100 vs - 250000 vc 3.3831 ms/op 3.2733 ms/op 1.03
getCommitteeAssignments - req 1000 vs - 250000 vc 3.6323 ms/op 3.4983 ms/op 1.04
findModifiedValidators - 10000 modified validators 731.15 ms/op 671.22 ms/op 1.09
findModifiedValidators - 1000 modified validators 425.02 ms/op 392.96 ms/op 1.08
findModifiedValidators - 100 modified validators 270.71 ms/op 275.43 ms/op 0.98
findModifiedValidators - 10 modified validators 231.49 ms/op 195.69 ms/op 1.18
findModifiedValidators - 1 modified validators 222.81 ms/op 141.65 ms/op 1.57
findModifiedValidators - no difference 151.18 ms/op 152.58 ms/op 0.99
migrate state 1500000 validators, 3400 modified, 2000 new 2.5960 s/op 2.6225 s/op 0.99
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 3.7300 ns/op 3.6400 ns/op 1.02
state getBlockRootAtSlot - 250000 vs - 7PWei 277.04 ns/op 330.75 ns/op 0.84
computeProposerIndex 100000 validators 1.3061 ms/op 1.2639 ms/op 1.03
getNextSyncCommitteeIndices 1000 validators 2.8258 ms/op 2.7579 ms/op 1.02
getNextSyncCommitteeIndices 10000 validators 24.855 ms/op 24.135 ms/op 1.03
getNextSyncCommitteeIndices 100000 validators 85.866 ms/op 79.462 ms/op 1.08
computeProposers - vc 250000 535.74 us/op 552.18 us/op 0.97
computeEpochShuffling - vc 250000 38.875 ms/op 38.116 ms/op 1.02
getNextSyncCommittee - vc 250000 9.4159 ms/op 9.1994 ms/op 1.02
nodejs block root to RootHex using toHex 89.972 ns/op 84.136 ns/op 1.07
nodejs block root to RootHex using toRootHex 59.184 ns/op 49.338 ns/op 1.20
nodejs fromHex(blob) 726.26 us/op 680.49 us/op 1.07
nodejs fromHexInto(blob) 629.22 us/op 599.34 us/op 1.05
nodejs block root to RootHex using the deprecated toHexString 497.78 ns/op 442.91 ns/op 1.12
nodejs byteArrayEquals 32 bytes (block root) 25.704 ns/op 24.900 ns/op 1.03
nodejs byteArrayEquals 48 bytes (pubkey) 37.020 ns/op 35.695 ns/op 1.04
nodejs byteArrayEquals 96 bytes (signature) 37.340 ns/op 31.936 ns/op 1.17
nodejs byteArrayEquals 1024 bytes 44.990 ns/op 39.579 ns/op 1.14
nodejs byteArrayEquals 131072 bytes (blob) 1.7272 us/op 1.6856 us/op 1.02
browser block root to RootHex using toHex 143.81 ns/op 137.62 ns/op 1.05
browser block root to RootHex using toRootHex 129.57 ns/op 124.72 ns/op 1.04
browser fromHex(blob) 1.5121 ms/op 1.4143 ms/op 1.07
browser fromHexInto(blob) 628.67 us/op 598.15 us/op 1.05
browser block root to RootHex using the deprecated toHexString 338.39 ns/op 312.36 ns/op 1.08
browser byteArrayEquals 32 bytes (block root) 27.635 ns/op 26.673 ns/op 1.04
browser byteArrayEquals 48 bytes (pubkey) 39.023 ns/op 37.484 ns/op 1.04
browser byteArrayEquals 96 bytes (signature) 73.065 ns/op 70.744 ns/op 1.03
browser byteArrayEquals 1024 bytes 742.79 ns/op 724.60 ns/op 1.03
browser byteArrayEquals 131072 bytes (blob) 93.962 us/op 90.996 us/op 1.03

by benchmarkbot/action

@lodekeeper lodekeeper 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.

LGTM — implementation matches consensus-specs#5115 and all 4 new spec test cases pass locally.

Test results

Built gloas+minimal reftests from consensus-specs#5115 (head 52a38c40a), overlaid onto packages/beacon-node/spec-tests/tests/minimal/gloas/, ran against this branch (head 689969e423):

Suite Test Result
epoch_processing/proposer_lookahead proposer_lookahead_does_not_contain_slashed_validators ✅ pass
epoch_processing/proposer_lookahead proposer_lookahead_full_with_many_slashed_validators ✅ pass
sanity/blocks proposer_lookahead_excludes_slashed_validators ✅ pass
fork/fork fork_proposer_lookahead_slashed_validators_carried_over ✅ pass

Implementation review

  • fork >= ForkSeq.gloas filter in processProposerLookahead.ts correctly mirrors the spec's [index for index in get_active_validator_indices(state, epoch) if not state.validators[index].slashed]. FLAG_UNSLASHED is set in beforeProcessEpoch (epochTransitionCache.ts:283) directly from state.validators[i].slashed, and is consistent at the time processProposerLookahead runs.
  • The Fulu→Gloas fork transition is covered by the existing upgradeStateToGloas.ts:67 which carries proposerLookahead over unchanged, matching the spec test's expectation that pre-fork (Fulu-computed) entries persist with their slashed validators through the boundary.
  • util/fulu.ts::initializeProposerLookahead is only called from upgradeStateToFulu.ts (Electra→Fulu boundary), not on any Gloas path — no EIP-8045 gap there.

Edit: A previous "Side note" about 2 pre-existing failures in the pinned v1.7.0-alpha.8 tarball was incorrect — my local spec-tests dir was stuck at v1.7.0-alpha.2 (6 versions stale). Those tests pass on CI with fresh alpha.8 data. Apologies for the noise.

wemeetagain
wemeetagain previously approved these changes Jun 2, 2026
// (consensus-specs #5275) are implemented. New gloas on_attestation vectors in v1.7.0-alpha.9.
name.endsWith("validate_on_attestation_beacon_root_payload_check") ||
name.endsWith("validate_on_attestation_payload_invalid_index") ||
name.endsWith("validate_on_attestation_same_slot_full_vote_rejected"),

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

a bit confused about these but can be addressed in a follow up

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.

Yeah, these 3 are the new gloas validate_on_attestation payload-status vectors from consensus-specs #5275 (merged 2026-05-29):

  • validate_on_attestation_same_slot_full_vote_rejected — at the attested block's slot, a full-node vote (payload_index=1) must be rejected even if a verified envelope exists.
  • validate_on_attestation_payload_invalid_indexpayload_index=2 (or any non-{0,1}) is rejected.
  • validate_on_attestation_beacon_root_payload_check — across an epoch boundary, a full-node vote requires the beacon block root's payload to be verified.

Same on_attestation handler we skipped under the on_payload_attestation_message umbrella for the PTC fork-choice work — these explicitly cover the attestation-side payload validation. Will unskip together with the broader Gloas fork-choice PTC work in a follow-up.

// unconditionally. Only the altair vectors exercise the changed condition (other forks pass).
name.endsWith("altair/fork_choice/on_block/pyspec_tests/justified_update_always_if_better") ||
name.endsWith("altair/fork_choice/on_block/pyspec_tests/justified_update_not_realized_finality") ||
name.endsWith("altair/fork_choice/get_head/pyspec_tests/voting_source_beyond_two_epoch") ||

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

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.

Confirmed — these 3 altair vectors were added in consensus-specs #5306 (merged 2026-06-01) to switch proposer boost from "same proposer index" to "same dependent root".

Spec diff in specs/phase0/fork-choice.md:

# Before
if block.proposer_index == get_beacon_proposer_index(head_state):
    store.proposer_boost_root = root

# After (#5306)
is_same_dependent_root = get_dependent_root(store, root) == get_dependent_root(store, head)
if is_timely and is_first_block and is_same_dependent_root:
    store.proposer_boost_root = root

Lodestar's check is at packages/fork-choice/src/forkChoice/forkChoice.ts:677-685 — still using block.proposerIndex === expectedProposerIndex. Will unskip once we add get_dependent_root and swap the predicate in a follow-up.

// implemented. v1.7.0-alpha.9 regenerated these transition vectors so eth1-bridge deposits are
// no longer processed across the Electra to Fulu boundary; Lodestar still applies the Electra
// deposit flow, diverging the post-fork state root.
/^fulu\/transition\/.*/,

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

there's more code we might be able to remove due to ethereum/consensus-specs#4704, can be done in separate PR

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.

Confirmed. #4704 drops the legacy eth1-bridge deposit path in Fulu+:

  • process_operations in Fulu+ asserts len(body.deposits) == 0 and no longer calls process_deposit
  • process_pending_deposits loses the "Eth1 bridge deposits not yet applied" guard
  • Gloas spec page just reuses the Fulu version

Lodestar mirror:

  • state-transition/src/block/processOperations.ts:42-61getEth1DepositCount + for (const deposit of body.deposits) processDeposit(...) should be guarded fork < ForkSeq.fulu (and the loop dropped for ≥ Fulu).
  • state-transition/src/epoch/processPendingDeposits.ts — drop the eth1_deposit_index < deposit_requests_start_index early-break in Fulu+.

Will unskip fulu/transition/* and clean these up in a separate PR.

Comment thread specrefs/.ethspecify.yml
- get_base_reward#phase0
- get_checkpoint_block#phase0
- get_current_store_epoch#phase0
- get_dependent_root#phase0

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

worth to revisit this, we should be able to point this to the proper function in our code

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.

Good catch — followed the same pattern as is_eligible_for_activation / is_eligible_for_activation_queue (both already point to epochTransitionCache.ts via an inline Python signature comment).

ethspecify check passes (1102 valid, was 1101).

Diff (apply with git am < patch or copy directly — I can't push to your branch on ChainSafe):

diff --git a/packages/state-transition/src/cache/epochTransitionCache.ts b/packages/state-transition/src/cache/epochTransitionCache.ts
--- a/packages/state-transition/src/cache/epochTransitionCache.ts
+++ b/packages/state-transition/src/cache/epochTransitionCache.ts
@@ -296,6 +296,16 @@ export function beforeProcessEpoch(
     // Both active validators and slashed-but-not-yet-withdrawn validators are eligible to receive penalties.
     // This is done to prevent self-slashing from being a way to escape inactivity leaks.
     // TODO: Consider using an array of `eligibleValidatorIndices: number[]`
+    // ```python
+    // def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]:
+    //   previous_epoch = get_previous_epoch(state)
+    //   return [
+    //     ValidatorIndex(index)
+    //     for index, v in enumerate(state.validators)
+    //     if is_active_validator(v, previous_epoch)
+    //     or (v.slashed and previous_epoch + 1 < v.withdrawable_epoch)
+    //   ]
+    // ```
     if (isActivePrev || (validator.slashed && prevEpoch + 1 < validator.withdrawableEpoch)) {
       flag |= FLAG_ELIGIBLE_ATTESTER;
     }
diff --git a/specrefs/.ethspecify.yml b/specrefs/.ethspecify.yml
--- a/specrefs/.ethspecify.yml
+++ b/specrefs/.ethspecify.yml
@@ -153,7 +153,6 @@ exceptions:
     - get_checkpoint_block#phase0
     - get_current_store_epoch#phase0
     - get_dependent_root#phase0
-    - get_eligible_validator_indices#phase0
     - get_eth1_vote#phase0
     - get_filtered_block_tree#phase0
     - get_forkchoice_store#phase0
diff --git a/specrefs/functions.yml b/specrefs/functions.yml
--- a/specrefs/functions.yml
+++ b/specrefs/functions.yml
@@ -3630,7 +3630,9 @@
     </spec>

 - name: get_eligible_validator_indices#phase0
-  sources: []
+  sources:
+    - file: packages/state-transition/src/cache/epochTransitionCache.ts
+      search: "// def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]:"
   spec: |
     <spec fn="get_eligible_validator_indices" fork="phase0" hash="22a24ce4">
     def get_eligible_validator_indices(state: BeaconState) -> Sequence[ValidatorIndex]:

wemeetagain
wemeetagain previously approved these changes Jun 2, 2026
Comment on lines -29 to +36
const lastEpochProposerLookahead = computeProposerIndices(fork, state, shuffling, epoch);
const activeIndices =
fork >= ForkSeq.gloas
? // Exclude slashed validators from proposing (EIP-8045)
shuffling.activeIndices.filter((index) => (cache.flags[index] & FLAG_UNSLASHED) !== 0)
: shuffling.activeIndices;

const lastEpochProposerLookahead = computeProposerIndices(fork, state, {activeIndices}, epoch);

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

for reference, that's the only real change in this PR

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.

👍 Yep, this 7-line filter is the entire EIP-8045 surface — FLAG_UNSLASHED is set in epochTransitionCache.ts:283 from state.validators[i].slashed, so the gated shuffling.activeIndices.filter is the exact mirror of consensus-specs#5115's pre-compute_proposer_indices slashed filter.

// TODO: decide whether to expose EMPTY_BLOCK_HASH in the config API. New bellatrix constant in
// v1.7.0-alpha.9; a zero Hash32 only used in the terminal-block fork-choice check, so skipped for
// now like the PAYLOAD_STATUS_* constants above.
"EMPTY_BLOCK_HASH",

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

another follow up we need to handle

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.

Investigated. Recommendation: keep skipped, tighten the rationale comment so it's no longer a TODO.

What it is: EMPTY_BLOCK_HASH: Hash32 = Hash32() — a 32-byte zero sentinel introduced in v1.7.0-alpha.9. Only spec use is the if TERMINAL_BLOCK_HASH != EMPTY_BLOCK_HASH: check (validate_merge_block, is_valid_terminal_pow_block, notify_new_payload).

Why skip is the right call:

  1. Other CL clients don't expose itEMPTY_BLOCK_HASH returns 0 hits on Lighthouse, Prysm, and Teku via GitHub code search. The constant is purely a spec-readability rename of the literal zero hash; nobody imports it.
  2. It's a fixed sentinel, not configurable — always 0x0000…0000. Clients comparing chainConfig.TERMINAL_BLOCK_HASH against the literal cover the same check.
  3. Matches existing precedentPAYLOAD_STATUS_VALID/INVALIDATED/NOT_VALIDATED (also bellatrix-introduced named sentinels) are skipped on the same grounds.

If we want to permanently bless the skip, suggested wording (replacing the current TODO):

-  // TODO: decide whether to expose EMPTY_BLOCK_HASH in the config API. New bellatrix constant in
-  // v1.7.0-alpha.9; a zero Hash32 only used in the terminal-block fork-choice check, so skipped for
-  // now like the PAYLOAD_STATUS_* constants above.
+  // EMPTY_BLOCK_HASH (bellatrix, v1.7.0-alpha.9) is a 32-byte zero sentinel used only for
+  // `TERMINAL_BLOCK_HASH != EMPTY_BLOCK_HASH` comparisons. Not exposed via the config API:
+  // it's a fixed value, not configurable, and Lighthouse/Prysm/Teku don't expose it either.
+  // Same treatment as the PAYLOAD_STATUS_* sentinels above.
   "EMPTY_BLOCK_HASH",

Happy to either:

  1. Push that comment-only tightening to the branch (if I had access — diff above is ready), or
  2. Reverse the call and actually expose it (one line: add EMPTY_BLOCK_HASH to specConstants in packages/beacon-node/src/api/impl/config/constants.ts as toHexBytes32(EMPTY_BLOCK_HASH)).

Your call. Tracking as a 🟢 follow-up in the meantime.

nflaig pushed a commit that referenced this pull request Jun 6, 2026
**Motivation**

Three Gloas `on_attestation` spec test vectors added in
ethereum/consensus-specs#5275 were skipped in #9422 with a TODO pending
investigation. This PR resolves that investigation and re-enables the
tests.

**Description**

The validation logic in `validateAttestationData` was already correct —
all three payload-status checks were implemented and firing as expected.
The root cause was that the attestation step handler in
`fork_choice.test.ts` had no `valid` flag handling: it called
`onAttestation` unconditionally and let any thrown `ForkChoiceError`
propagate as a test failure, even when the spec vector marks the
attestation as `valid: false`.

Fix: wrap `onAttestation` in the same `try/catch` + `isValid` pattern
already used by the `execution_payload` step handler, then unskip the
three tests.

Closes #9447
@wemeetagain

Copy link
Copy Markdown
Member

🎉 This PR is included in v1.44.0 🎉

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.

3 participants