Skip to content

feat: force the proposer to reorg unavailable blocks - #9387

Merged
wemeetagain merged 7 commits into
unstablefrom
te/should_build_on_full
May 21, 2026
Merged

feat: force the proposer to reorg unavailable blocks#9387
wemeetagain merged 7 commits into
unstablefrom
te/should_build_on_full

Conversation

@twoeths

@twoeths twoeths commented May 20, 2026

Copy link
Copy Markdown
Member

Motivation

Description

  • track blob data available in a new daVotes
  • thread blob data available from block import, gossip handler and api
  • track ptc voted in a new ptcAttested
  • count NO votes and implement shouldBuildOnFull() when producing block

AI Assistance Disclosure

Created with 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 pul l request upd a t e s t h e G l o a s i m p l e m e n t a t i o n t o a l i g n w i t h c o n s e n s u s - s p e c s v 1 . 7 . 0 - a l p h a . 8 . K e y c h a n g e s i n c l u d e r e n a m i n g g a s _ l i m i t t o t a r g e t _ g a s _ l i m i t a c r o s s t h e v a l i d a t o r a n d e x e c u t i o n l a y e r s , i m p l e m e n t i n g t h e s h o u l d _ b u i l d _ o n _ f u l l f o r k c h o i c e l o g i c , a n d r e f i n i n g p a y l o a d t i m e l i n e s s c h e c k s b a s e d o n a r r i v a l t i m e s t a m p s . T h e P r o t o A r r a y w a s e x p a n d e d t o t r a c k b l o b d a t a a v a i l a b i l i t y a n d e x p l i c i t ' n o ' v o t e s f r o m t h e P a y l o a d T i m e l i n e s s C o m m i t t e e ( P T C ) . R e v i e w f e e d b a c k h i g h l i g h t s t h e n e e d t o r e f i n e t h e f a l l b a c k l o g i c f o r t h e p r o p o s e r ' s t a r g e t g a s l i m i t w h e n p a r e n t b l o c k s a r e e m p t y a n d s u g g e s t s a d d i n g l e n g t h v a l i d a t i o n t o t h e n e w b i t w i s e v o t e c o u n t i n g u t i l i t y t o p r e v e n t p o t e n t i a l o u t - o f - b o u n d s a c c e s s .

Comment on lines +917 to +919
// TODO GLOAS: state.latestExecutionPayloadBid is the latest *bid*, not the latest *executed*
// payload — for EMPTY parents this drifts. Consider having a default value like Prysm's DefaultBuilderGasLimit.
return Number(pref ? pref.message.targetGasLimit : state.latestExecutionPayloadBid.gasLimit);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The fallback to state.latestExecutionPayloadBid.gasLimit when no proposer preferences are pooled might be problematic if the parent block was EMPTY. In that case, the bid in the state reflects the proposer's intent for the skipped payload, which may have drifted from the actual gas limit of the last executed block. Consider using a safer default or a value derived from the last successfully executed payload header if available.

Comment on lines +34 to +46
export function countNoVotes(attended: BitArray, yes: BitArray): number {
const a = attended.uint8Array;
const y = yes.uint8Array;
let count = 0;
for (let i = 0; i < a.length; i++) {
let byte = a[i] & ~y[i] & 0xff;
while (byte) {
byte &= byte - 1;
count++;
}
}
return count;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The countNoVotes function is a critical utility for determining reorg triggers. While the current bitwise implementation is correct and efficient, ensure that the BitArray lengths for attended and yes are always consistent before accessing their uint8Array directly to avoid potential out-of-bounds issues if the underlying buffers differ in size.

Suggested change
export function countNoVotes(attended: BitArray, yes: BitArray): number {
const a = attended.uint8Array;
const y = yes.uint8Array;
let count = 0;
for (let i = 0; i < a.length; i++) {
let byte = a[i] & ~y[i] & 0xff;
while (byte) {
byte &= byte - 1;
count++;
}
}
return count;
}
export function countNoVotes(attended: BitArray, yes: BitArray): number {
const a = attended.uint8Array;
const y = yes.uint8Array;
if (a.length !== y.length) {
throw new Error("BitArray lengths mismatch in countNoVotes");
}
let count = 0;
for (let i = 0; i < a.length; i++) {
let byte = a[i] & ~y[i];
while (byte) {
byte &= byte - 1;
count++;
}
}
return count;
}

@github-actions

github-actions Bot commented May 20, 2026

Copy link
Copy Markdown
Contributor

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: e69f812 Previous: 6ef8199 Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 1.2977 ms/op 843.35 us/op 1.54
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 40.189 us/op 38.309 us/op 1.05
BLS verify - blst 691.16 us/op 710.87 us/op 0.97
BLS verifyMultipleSignatures 3 - blst 1.3005 ms/op 1.3137 ms/op 0.99
BLS verifyMultipleSignatures 8 - blst 2.1118 ms/op 2.0513 ms/op 1.03
BLS verifyMultipleSignatures 32 - blst 6.7039 ms/op 6.5744 ms/op 1.02
BLS verifyMultipleSignatures 64 - blst 12.635 ms/op 12.839 ms/op 0.98
BLS verifyMultipleSignatures 128 - blst 25.211 ms/op 24.774 ms/op 1.02
BLS deserializing 10000 signatures 633.21 ms/op 622.63 ms/op 1.02
BLS deserializing 100000 signatures 6.2539 s/op 6.1969 s/op 1.01
BLS verifyMultipleSignatures - same message - 3 - blst 759.05 us/op 797.64 us/op 0.95
BLS verifyMultipleSignatures - same message - 8 - blst 912.25 us/op 930.05 us/op 0.98
BLS verifyMultipleSignatures - same message - 32 - blst 1.4861 ms/op 1.5066 ms/op 0.99
BLS verifyMultipleSignatures - same message - 64 - blst 2.2420 ms/op 2.3501 ms/op 0.95
BLS verifyMultipleSignatures - same message - 128 - blst 3.8403 ms/op 3.9782 ms/op 0.97
BLS aggregatePubkeys 32 - blst 16.962 us/op 17.546 us/op 0.97
BLS aggregatePubkeys 128 - blst 60.874 us/op 63.394 us/op 0.96
getSlashingsAndExits - default max 78.148 us/op 45.581 us/op 1.71
getSlashingsAndExits - 2k 446.05 us/op 340.53 us/op 1.31
proposeBlockBody type=full, size=empty 645.15 us/op 662.93 us/op 0.97
isKnown best case - 1 super set check 187.00 ns/op 160.00 ns/op 1.17
isKnown normal case - 2 super set checks 162.00 ns/op 160.00 ns/op 1.01
isKnown worse case - 16 super set checks 161.00 ns/op 158.00 ns/op 1.02
validate api signedAggregateAndProof - struct 1.4626 ms/op 1.4537 ms/op 1.01
validate gossip signedAggregateAndProof - struct 1.4626 ms/op 1.4530 ms/op 1.01
batch validate gossip attestation - vc 640000 - chunk 32 105.28 us/op 104.05 us/op 1.01
batch validate gossip attestation - vc 640000 - chunk 64 92.040 us/op 91.086 us/op 1.01
batch validate gossip attestation - vc 640000 - chunk 128 85.846 us/op 84.206 us/op 1.02
batch validate gossip attestation - vc 640000 - chunk 256 83.593 us/op 81.756 us/op 1.02
bytes32 toHexString 281.00 ns/op 287.00 ns/op 0.98
bytes32 Buffer.toString(hex) 172.00 ns/op 171.00 ns/op 1.01
bytes32 Buffer.toString(hex) from Uint8Array 246.00 ns/op 235.00 ns/op 1.05
bytes32 Buffer.toString(hex) + 0x 171.00 ns/op 175.00 ns/op 0.98
Return object 10000 times 0.21050 ns/op 0.20720 ns/op 1.02
Throw Error 10000 times 3.2948 us/op 3.2702 us/op 1.01
toHex 93.467 ns/op 88.580 ns/op 1.06
Buffer.from 93.615 ns/op 81.843 ns/op 1.14
shared Buffer 56.770 ns/op 53.652 ns/op 1.06
fastMsgIdFn sha256 / 200 bytes 1.4490 us/op 1.4260 us/op 1.02
fastMsgIdFn h32 xxhash / 200 bytes 155.00 ns/op 152.00 ns/op 1.02
fastMsgIdFn h64 xxhash / 200 bytes 210.00 ns/op 205.00 ns/op 1.02
fastMsgIdFn sha256 / 1000 bytes 4.6470 us/op 4.5830 us/op 1.01
fastMsgIdFn h32 xxhash / 1000 bytes 244.00 ns/op 235.00 ns/op 1.04
fastMsgIdFn h64 xxhash / 1000 bytes 256.00 ns/op 247.00 ns/op 1.04
fastMsgIdFn sha256 / 10000 bytes 41.246 us/op 40.409 us/op 1.02
fastMsgIdFn h32 xxhash / 10000 bytes 1.2380 us/op 1.2090 us/op 1.02
fastMsgIdFn h64 xxhash / 10000 bytes 814.00 ns/op 775.00 ns/op 1.05
send data - 1000 256B messages 4.5366 ms/op 4.3022 ms/op 1.05
send data - 1000 512B messages 4.5960 ms/op 4.3743 ms/op 1.05
send data - 1000 1024B messages 5.1852 ms/op 4.5772 ms/op 1.13
send data - 1000 1200B messages 5.5046 ms/op 4.7144 ms/op 1.17
send data - 1000 2048B messages 4.9832 ms/op 5.0084 ms/op 0.99
send data - 1000 4096B messages 5.8528 ms/op 5.6369 ms/op 1.04
send data - 1000 16384B messages 24.337 ms/op 25.358 ms/op 0.96
send data - 1000 65536B messages 146.77 ms/op 186.35 ms/op 0.79
enrSubnets - fastDeserialize 64 bits 722.00 ns/op 735.00 ns/op 0.98
enrSubnets - ssz BitVector 64 bits 269.00 ns/op 267.00 ns/op 1.01
enrSubnets - fastDeserialize 4 bits 101.00 ns/op 97.000 ns/op 1.04
enrSubnets - ssz BitVector 4 bits 259.00 ns/op 270.00 ns/op 0.96
prioritizePeers score -10:0 att 32-0.1 sync 2-0 203.47 us/op 197.55 us/op 1.03
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 233.80 us/op 242.14 us/op 0.97
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 339.29 us/op 332.46 us/op 1.02
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 607.48 us/op 590.01 us/op 1.03
prioritizePeers score 0:0 att 64-1 sync 4-1 707.49 us/op 686.13 us/op 1.03
array of 16000 items push then shift 1.2377 us/op 1.2082 us/op 1.02
LinkedList of 16000 items push then shift 7.5120 ns/op 7.3040 ns/op 1.03
array of 16000 items push then pop 67.020 ns/op 66.068 ns/op 1.01
LinkedList of 16000 items push then pop 5.9240 ns/op 5.7850 ns/op 1.02
array of 24000 items push then shift 1.8371 us/op 1.7887 us/op 1.03
LinkedList of 24000 items push then shift 7.6470 ns/op 7.0620 ns/op 1.08
array of 24000 items push then pop 97.181 ns/op 91.844 ns/op 1.06
LinkedList of 24000 items push then pop 6.0410 ns/op 5.8660 ns/op 1.03
intersect bitArray bitLen 8 4.6690 ns/op 4.6120 ns/op 1.01
intersect array and set length 8 28.590 ns/op 28.187 ns/op 1.01
intersect bitArray bitLen 128 23.645 ns/op 23.124 ns/op 1.02
intersect array and set length 128 492.33 ns/op 475.42 ns/op 1.04
bitArray.getTrueBitIndexes() bitLen 128 1.0780 us/op 985.00 ns/op 1.09
bitArray.getTrueBitIndexes() bitLen 248 1.7760 us/op 1.6810 us/op 1.06
bitArray.getTrueBitIndexes() bitLen 512 3.5800 us/op 3.4500 us/op 1.04
Full columns - reconstruct all 6 blobs 185.79 us/op 123.11 us/op 1.51
Full columns - reconstruct half of the blobs out of 6 90.687 us/op 84.167 us/op 1.08
Full columns - reconstruct single blob out of 6 33.799 us/op 31.725 us/op 1.07
Half columns - reconstruct all 6 blobs 382.05 ms/op 372.66 ms/op 1.03
Half columns - reconstruct half of the blobs out of 6 189.73 ms/op 187.62 ms/op 1.01
Half columns - reconstruct single blob out of 6 68.034 ms/op 66.743 ms/op 1.02
Full columns - reconstruct all 10 blobs 266.10 us/op 266.81 us/op 1.00
Full columns - reconstruct half of the blobs out of 10 137.21 us/op 126.23 us/op 1.09
Full columns - reconstruct single blob out of 10 61.898 us/op 42.855 us/op 1.44
Half columns - reconstruct all 10 blobs 624.73 ms/op 618.64 ms/op 1.01
Half columns - reconstruct half of the blobs out of 10 315.53 ms/op 309.99 ms/op 1.02
Half columns - reconstruct single blob out of 10 66.490 ms/op 66.625 ms/op 1.00
Full columns - reconstruct all 20 blobs 555.50 us/op 1.5943 ms/op 0.35
Full columns - reconstruct half of the blobs out of 20 268.07 us/op 186.79 us/op 1.44
Full columns - reconstruct single blob out of 20 30.564 us/op 28.666 us/op 1.07
Half columns - reconstruct all 20 blobs 1.2736 s/op 1.2375 s/op 1.03
Half columns - reconstruct half of the blobs out of 20 647.07 ms/op 616.79 ms/op 1.05
Half columns - reconstruct single blob out of 20 68.949 ms/op 67.540 ms/op 1.02
Set add up to 64 items then delete first 2.5743 us/op 1.9972 us/op 1.29
OrderedSet add up to 64 items then delete first 3.3449 us/op 3.1885 us/op 1.05
Set add up to 64 items then delete last 2.3443 us/op 2.0343 us/op 1.15
OrderedSet add up to 64 items then delete last 3.2404 us/op 3.1417 us/op 1.03
Set add up to 64 items then delete middle 2.1283 us/op 2.0050 us/op 1.06
OrderedSet add up to 64 items then delete middle 4.7167 us/op 4.5521 us/op 1.04
Set add up to 128 items then delete first 4.3376 us/op 3.9885 us/op 1.09
OrderedSet add up to 128 items then delete first 6.6875 us/op 6.1887 us/op 1.08
Set add up to 128 items then delete last 3.8856 us/op 3.7051 us/op 1.05
OrderedSet add up to 128 items then delete last 5.8099 us/op 5.6214 us/op 1.03
Set add up to 128 items then delete middle 4.0000 us/op 3.6621 us/op 1.09
OrderedSet add up to 128 items then delete middle 11.936 us/op 11.693 us/op 1.02
Set add up to 256 items then delete first 7.7713 us/op 7.7033 us/op 1.01
OrderedSet add up to 256 items then delete first 12.864 us/op 11.897 us/op 1.08
Set add up to 256 items then delete last 8.0045 us/op 7.4735 us/op 1.07
OrderedSet add up to 256 items then delete last 12.491 us/op 11.586 us/op 1.08
Set add up to 256 items then delete middle 7.7026 us/op 7.5824 us/op 1.02
OrderedSet add up to 256 items then delete middle 35.775 us/op 36.241 us/op 0.99
pass gossip attestations to forkchoice per slot 2.5340 ms/op 2.4954 ms/op 1.02
forkChoice updateHead vc 100000 bc 64 eq 0 377.63 us/op 381.00 us/op 0.99
forkChoice updateHead vc 600000 bc 64 eq 0 2.2674 ms/op 2.2715 ms/op 1.00
forkChoice updateHead vc 1000000 bc 64 eq 0 3.7757 ms/op 3.7472 ms/op 1.01
forkChoice updateHead vc 600000 bc 320 eq 0 2.3149 ms/op 2.3029 ms/op 1.01
forkChoice updateHead vc 600000 bc 1200 eq 0 2.4533 ms/op 2.3433 ms/op 1.05
forkChoice updateHead vc 600000 bc 7200 eq 0 3.4668 ms/op 3.1834 ms/op 1.09
forkChoice updateHead vc 600000 bc 64 eq 1000 2.9186 ms/op 2.8761 ms/op 1.01
forkChoice updateHead vc 600000 bc 64 eq 10000 2.9737 ms/op 2.9168 ms/op 1.02
forkChoice updateHead vc 600000 bc 64 eq 300000 6.9008 ms/op 6.6031 ms/op 1.05
computeDeltas 1400000 validators 0% inactive 12.423 ms/op 12.219 ms/op 1.02
computeDeltas 1400000 validators 10% inactive 11.765 ms/op 11.424 ms/op 1.03
computeDeltas 1400000 validators 20% inactive 10.429 ms/op 10.409 ms/op 1.00
computeDeltas 1400000 validators 50% inactive 7.9980 ms/op 8.0940 ms/op 0.99
computeDeltas 2100000 validators 0% inactive 18.468 ms/op 18.229 ms/op 1.01
computeDeltas 2100000 validators 10% inactive 17.423 ms/op 17.222 ms/op 1.01
computeDeltas 2100000 validators 20% inactive 15.672 ms/op 15.792 ms/op 0.99
computeDeltas 2100000 validators 50% inactive 9.1869 ms/op 9.2459 ms/op 0.99
altair processAttestation - 250000 vs - 7PWei normalcase 2.3517 ms/op 2.4773 ms/op 0.95
altair processAttestation - 250000 vs - 7PWei worstcase 3.1069 ms/op 3.5677 ms/op 0.87
altair processAttestation - setStatus - 1/6 committees join 97.932 us/op 108.12 us/op 0.91
altair processAttestation - setStatus - 1/3 committees join 194.04 us/op 200.87 us/op 0.97
altair processAttestation - setStatus - 1/2 committees join 275.62 us/op 279.04 us/op 0.99
altair processAttestation - setStatus - 2/3 committees join 352.56 us/op 363.93 us/op 0.97
altair processAttestation - setStatus - 4/5 committees join 514.36 us/op 513.10 us/op 1.00
altair processAttestation - setStatus - 100% committees join 598.13 us/op 620.34 us/op 0.96
altair processBlock - 250000 vs - 7PWei normalcase 4.7128 ms/op 4.1849 ms/op 1.13
altair processBlock - 250000 vs - 7PWei normalcase hashState 16.000 ms/op 14.744 ms/op 1.09
altair processBlock - 250000 vs - 7PWei worstcase 22.957 ms/op 23.293 ms/op 0.99
altair processBlock - 250000 vs - 7PWei worstcase hashState 44.925 ms/op 46.001 ms/op 0.98
phase0 processBlock - 250000 vs - 7PWei normalcase 1.3176 ms/op 1.4412 ms/op 0.91
phase0 processBlock - 250000 vs - 7PWei worstcase 16.066 ms/op 17.600 ms/op 0.91
altair processEth1Data - 250000 vs - 7PWei normalcase 281.35 us/op 288.40 us/op 0.98
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 3.1350 us/op 6.3690 us/op 0.49
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 19.974 us/op 22.905 us/op 0.87
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 5.8280 us/op 7.9790 us/op 0.73
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 3.3020 us/op 4.8760 us/op 0.68
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 85.245 us/op 96.229 us/op 0.89
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.3567 ms/op 1.4118 ms/op 0.96
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.7907 ms/op 2.0534 ms/op 0.87
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.7835 ms/op 1.8072 ms/op 0.99
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.5569 ms/op 4.2109 ms/op 0.84
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 1.9405 ms/op 2.1099 ms/op 0.92
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 3.7864 ms/op 4.0502 ms/op 0.93
Tree 40 250000 create 334.22 ms/op 349.13 ms/op 0.96
Tree 40 250000 get(125000) 87.390 ns/op 90.134 ns/op 0.97
Tree 40 250000 set(125000) 962.43 ns/op 987.64 ns/op 0.97
Tree 40 250000 toArray() 14.646 ms/op 16.206 ms/op 0.90
Tree 40 250000 iterate all - toArray() + loop 16.204 ms/op 16.158 ms/op 1.00
Tree 40 250000 iterate all - get(i) 38.843 ms/op 39.077 ms/op 0.99
Array 250000 create 2.2192 ms/op 2.1687 ms/op 1.02
Array 250000 clone - spread 671.79 us/op 674.11 us/op 1.00
Array 250000 get(125000) 0.28600 ns/op 0.29400 ns/op 0.97
Array 250000 set(125000) 0.28700 ns/op 0.29300 ns/op 0.98
Array 250000 iterate all - loop 55.302 us/op 56.096 us/op 0.99
phase0 afterProcessEpoch - 250000 vs - 7PWei 37.324 ms/op 40.047 ms/op 0.93
Array.fill - length 1000000 2.0332 ms/op 2.1537 ms/op 0.94
Array push - length 1000000 9.0532 ms/op 9.6209 ms/op 0.94
Array.get 0.20324 ns/op 0.20228 ns/op 1.00
Uint8Array.get 0.23309 ns/op 0.23599 ns/op 0.99
phase0 beforeProcessEpoch - 250000 vs - 7PWei 15.180 ms/op 19.616 ms/op 0.77
altair processEpoch - mainnet_e81889 263.04 ms/op 276.17 ms/op 0.95
mainnet_e81889 - altair beforeProcessEpoch 14.713 ms/op 39.919 ms/op 0.37
mainnet_e81889 - altair processJustificationAndFinalization 5.7750 us/op 6.7360 us/op 0.86
mainnet_e81889 - altair processInactivityUpdates 3.3570 ms/op 4.8550 ms/op 0.69
mainnet_e81889 - altair processRewardsAndPenalties 18.837 ms/op 20.885 ms/op 0.90
mainnet_e81889 - altair processRegistryUpdates 512.00 ns/op 530.00 ns/op 0.97
mainnet_e81889 - altair processSlashings 135.00 ns/op 135.00 ns/op 1.00
mainnet_e81889 - altair processEth1DataReset 137.00 ns/op 132.00 ns/op 1.04
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.6959 ms/op 2.9307 ms/op 0.58
mainnet_e81889 - altair processSlashingsReset 679.00 ns/op 679.00 ns/op 1.00
mainnet_e81889 - altair processRandaoMixesReset 1.2130 us/op 1.3420 us/op 0.90
mainnet_e81889 - altair processHistoricalRootsUpdate 126.00 ns/op 133.00 ns/op 0.95
mainnet_e81889 - altair processParticipationFlagUpdates 416.00 ns/op 435.00 ns/op 0.96
mainnet_e81889 - altair processSyncCommitteeUpdates 106.00 ns/op 116.00 ns/op 0.91
mainnet_e81889 - altair afterProcessEpoch 39.729 ms/op 42.233 ms/op 0.94
capella processEpoch - mainnet_e217614 821.16 ms/op 863.15 ms/op 0.95
mainnet_e217614 - capella beforeProcessEpoch 59.270 ms/op 72.152 ms/op 0.82
mainnet_e217614 - capella processJustificationAndFinalization 6.6220 us/op 6.5370 us/op 1.01
mainnet_e217614 - capella processInactivityUpdates 14.882 ms/op 22.702 ms/op 0.66
mainnet_e217614 - capella processRewardsAndPenalties 83.422 ms/op 97.877 ms/op 0.85
mainnet_e217614 - capella processRegistryUpdates 4.2870 us/op 4.4400 us/op 0.97
mainnet_e217614 - capella processSlashings 134.00 ns/op 127.00 ns/op 1.06
mainnet_e217614 - capella processEth1DataReset 126.00 ns/op 126.00 ns/op 1.00
mainnet_e217614 - capella processEffectiveBalanceUpdates 12.341 ms/op 18.656 ms/op 0.66
mainnet_e217614 - capella processSlashingsReset 664.00 ns/op 695.00 ns/op 0.96
mainnet_e217614 - capella processRandaoMixesReset 1.3510 us/op 1.3580 us/op 0.99
mainnet_e217614 - capella processHistoricalRootsUpdate 131.00 ns/op 126.00 ns/op 1.04
mainnet_e217614 - capella processParticipationFlagUpdates 428.00 ns/op 430.00 ns/op 1.00
mainnet_e217614 - capella afterProcessEpoch 106.67 ms/op 109.52 ms/op 0.97
phase0 processEpoch - mainnet_e58758 302.23 ms/op 313.89 ms/op 0.96
mainnet_e58758 - phase0 beforeProcessEpoch 64.358 ms/op 66.872 ms/op 0.96
mainnet_e58758 - phase0 processJustificationAndFinalization 6.1080 us/op 6.3800 us/op 0.96
mainnet_e58758 - phase0 processRewardsAndPenalties 15.409 ms/op 16.525 ms/op 0.93
mainnet_e58758 - phase0 processRegistryUpdates 2.1610 us/op 2.1910 us/op 0.99
mainnet_e58758 - phase0 processSlashings 136.00 ns/op 130.00 ns/op 1.05
mainnet_e58758 - phase0 processEth1DataReset 123.00 ns/op 127.00 ns/op 0.97
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 900.82 us/op 994.61 us/op 0.91
mainnet_e58758 - phase0 processSlashingsReset 876.00 ns/op 887.00 ns/op 0.99
mainnet_e58758 - phase0 processRandaoMixesReset 1.2400 us/op 1.2890 us/op 0.96
mainnet_e58758 - phase0 processHistoricalRootsUpdate 132.00 ns/op 133.00 ns/op 0.99
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.0730 us/op 1.2390 us/op 0.87
mainnet_e58758 - phase0 afterProcessEpoch 32.125 ms/op 32.577 ms/op 0.99
phase0 processEffectiveBalanceUpdates - 250000 normalcase 955.06 us/op 982.46 us/op 0.97
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.4808 ms/op 1.5218 ms/op 0.97
altair processInactivityUpdates - 250000 normalcase 10.318 ms/op 12.504 ms/op 0.83
altair processInactivityUpdates - 250000 worstcase 10.383 ms/op 12.690 ms/op 0.82
phase0 processRegistryUpdates - 250000 normalcase 2.0220 us/op 2.3670 us/op 0.85
phase0 processRegistryUpdates - 250000 badcase_full_deposits 137.54 us/op 145.47 us/op 0.95
phase0 processRegistryUpdates - 250000 worstcase 0.5 56.676 ms/op 60.315 ms/op 0.94
altair processRewardsAndPenalties - 250000 normalcase 14.932 ms/op 16.670 ms/op 0.90
altair processRewardsAndPenalties - 250000 worstcase 14.679 ms/op 16.201 ms/op 0.91
phase0 getAttestationDeltas - 250000 normalcase 5.1350 ms/op 5.3875 ms/op 0.95
phase0 getAttestationDeltas - 250000 worstcase 5.1715 ms/op 5.1873 ms/op 1.00
phase0 processSlashings - 250000 worstcase 57.573 us/op 59.803 us/op 0.96
altair processSyncCommitteeUpdates - 250000 9.7866 ms/op 10.151 ms/op 0.96
BeaconState.hashTreeRoot - No change 166.00 ns/op 169.00 ns/op 0.98
BeaconState.hashTreeRoot - 1 full validator 75.878 us/op 73.268 us/op 1.04
BeaconState.hashTreeRoot - 32 full validator 858.13 us/op 828.33 us/op 1.04
BeaconState.hashTreeRoot - 512 full validator 8.0942 ms/op 7.4177 ms/op 1.09
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 104.75 us/op 91.293 us/op 1.15
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.4427 ms/op 1.3453 ms/op 1.07
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 18.518 ms/op 16.162 ms/op 1.15
BeaconState.hashTreeRoot - 1 balances 73.698 us/op 70.413 us/op 1.05
BeaconState.hashTreeRoot - 32 balances 732.92 us/op 697.17 us/op 1.05
BeaconState.hashTreeRoot - 512 balances 6.1478 ms/op 5.9960 ms/op 1.03
BeaconState.hashTreeRoot - 250000 balances 126.67 ms/op 137.26 ms/op 0.92
aggregationBits - 2048 els - zipIndexesInBitList 18.828 us/op 19.899 us/op 0.95
regular array get 100000 times 21.999 us/op 22.520 us/op 0.98
wrappedArray get 100000 times 22.016 us/op 22.566 us/op 0.98
arrayWithProxy get 100000 times 9.1268 ms/op 9.5559 ms/op 0.96
ssz.Root.equals 20.626 ns/op 21.057 ns/op 0.98
byteArrayEquals 20.322 ns/op 20.825 ns/op 0.98
Buffer.compare 8.4080 ns/op 9.6800 ns/op 0.87
processSlot - 1 slots 8.0650 us/op 9.7700 us/op 0.83
processSlot - 32 slots 1.8795 ms/op 2.1349 ms/op 0.88
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 3.7080 ms/op 4.6355 ms/op 0.80
getCommitteeAssignments - req 1 vs - 250000 vc 1.6184 ms/op 1.6128 ms/op 1.00
getCommitteeAssignments - req 100 vs - 250000 vc 3.2809 ms/op 3.3261 ms/op 0.99
getCommitteeAssignments - req 1000 vs - 250000 vc 3.5705 ms/op 3.6107 ms/op 0.99
findModifiedValidators - 10000 modified validators 670.47 ms/op 870.33 ms/op 0.77
findModifiedValidators - 1000 modified validators 401.60 ms/op 453.49 ms/op 0.89
findModifiedValidators - 100 modified validators 274.32 ms/op 341.90 ms/op 0.80
findModifiedValidators - 10 modified validators 209.86 ms/op 210.52 ms/op 1.00
findModifiedValidators - 1 modified validators 148.15 ms/op 168.99 ms/op 0.88
findModifiedValidators - no difference 144.84 ms/op 224.34 ms/op 0.65
migrate state 1500000 validators, 3400 modified, 2000 new 2.8476 s/op 3.2638 s/op 0.87
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 3.5800 ns/op 3.6400 ns/op 0.98
state getBlockRootAtSlot - 250000 vs - 7PWei 343.25 ns/op 402.03 ns/op 0.85
computeProposerIndex 100000 validators 1.3044 ms/op 1.3087 ms/op 1.00
getNextSyncCommitteeIndices 1000 validators 2.7847 ms/op 2.8129 ms/op 0.99
getNextSyncCommitteeIndices 10000 validators 24.461 ms/op 24.777 ms/op 0.99
getNextSyncCommitteeIndices 100000 validators 83.098 ms/op 87.690 ms/op 0.95
computeProposers - vc 250000 532.59 us/op 559.88 us/op 0.95
computeEpochShuffling - vc 250000 37.696 ms/op 38.836 ms/op 0.97
getNextSyncCommittee - vc 250000 9.1930 ms/op 9.7415 ms/op 0.94
nodejs block root to RootHex using toHex 89.505 ns/op 88.872 ns/op 1.01
nodejs block root to RootHex using toRootHex 54.502 ns/op 55.047 ns/op 0.99
nodejs fromHex(blob) 728.98 us/op 752.31 us/op 0.97
nodejs fromHexInto(blob) 598.77 us/op 601.41 us/op 1.00
nodejs block root to RootHex using the deprecated toHexString 506.83 ns/op 465.29 ns/op 1.09
nodejs byteArrayEquals 32 bytes (block root) 24.944 ns/op 24.706 ns/op 1.01
nodejs byteArrayEquals 48 bytes (pubkey) 36.000 ns/op 35.595 ns/op 1.01
nodejs byteArrayEquals 96 bytes (signature) 32.399 ns/op 32.271 ns/op 1.00
nodejs byteArrayEquals 1024 bytes 38.972 ns/op 39.366 ns/op 0.99
nodejs byteArrayEquals 131072 bytes (blob) 1.6823 us/op 1.6593 us/op 1.01
browser block root to RootHex using toHex 137.22 ns/op 135.77 ns/op 1.01
browser block root to RootHex using toRootHex 124.86 ns/op 123.35 ns/op 1.01
browser fromHex(blob) 1.4336 ms/op 1.4507 ms/op 0.99
browser fromHexInto(blob) 597.07 us/op 611.11 us/op 0.98
browser block root to RootHex using the deprecated toHexString 509.33 ns/op 482.57 ns/op 1.06
browser byteArrayEquals 32 bytes (block root) 26.735 ns/op 27.533 ns/op 0.97
browser byteArrayEquals 48 bytes (pubkey) 37.698 ns/op 39.056 ns/op 0.97
browser byteArrayEquals 96 bytes (signature) 70.618 ns/op 73.400 ns/op 0.96
browser byteArrayEquals 1024 bytes 722.98 ns/op 742.62 ns/op 0.97
browser byteArrayEquals 131072 bytes (blob) 90.830 us/op 93.674 us/op 0.97

by benchmarkbot/action

wemeetagain
wemeetagain previously approved these changes May 20, 2026
Comment on lines +95 to +108
/**
* Blob data availability votes per block.
* Spec: gloas/fork-choice.md#modified-store (payload_data_availability_vote)
*
* Bit i = PTC member i voted blobDataAvailable=true (DA YES vote)
*/
private daVotes = new Map<RootHex, BitArray>();
/**
* Tracks which PTC members have attested at all (any payload_status).
* Without this, we cannot tell "didn't vote" (None) from "voted false" —
* a distinction required by payload_timeliness/payload_data_availability
* when called with the negative parameter value.
*/
private ptcAttested = new Map<RootHex, BitArray>();

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.

This representation is fine, but worth noting the optimization in #9284 if we don't need attribution of votes. (9x less memory there, but with a max savings of ~55MB in worst-case non-finality) (Or we can just leapfrog to lodestar-z fork choice 😂)

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.

@wemeetagain I liked the event based approach, can we reopen that PR, see #9387 (comment), it relates to that

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.

(Or we can just leapfrog to lodestar-z fork choice 😂)

please no, obviously the zig fork choice has a bunch of bugs, I am assuming we just LLM copy-pasted the bugs from lodestar ts fork choice, I would be surprised if that is not the case

unless we have fork choice compliance tests wired up and passing I don't think switching to the zig implementation is viable

@wemeetagain
wemeetagain marked this pull request as ready for review May 20, 2026 14:02
@wemeetagain
wemeetagain requested a review from a team as a code owner May 20, 2026 14:02

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: acfe0296e7

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +762 to +764
if (head.payloadStatus === PayloadStatus.PENDING) {
throw new Error("shouldBuildOnFull called with PENDING head");
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Handle PENDING heads without throwing

shouldBuildOnFull now throws for PayloadStatus.PENDING, but proposer flows pass fork-choice heads directly into this method (via recomputeForkChoiceHead() / getProposerHead()), and findHead() explicitly allows Gloas heads to be PENDING. In that state (e.g., before FULL/EMPTY resolution), this exception can bubble out of slot preparation or block production instead of safely defaulting to EMPTY behavior, which can cause missed proposal work.

Useful? React with 👍 / 👎.

@wemeetagain
wemeetagain merged commit b506aab into unstable May 21, 2026
19 checks passed
@wemeetagain
wemeetagain deleted the te/should_build_on_full branch May 21, 2026 13:39
throw new Error("shouldBuildOnFull called with PENDING head");
}
if (head.payloadStatus === PayloadStatus.EMPTY) return false;
return !this.isPayloadDataNotAvailable(head.blockRoot);

@nflaig nflaig May 26, 2026

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.

Just a thought, this seems mostly relevant if we are not a (semi) supernode as we cannot validate data availability ourselves, but otherwise, we could ignore this check if we have observed all data. This is still debatable imo if we even wanna consider this case at all or keep as is for any node type

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.

I don't see supernode/semi-supernode matters here
we should always consult PTC for this, instead of checking how we receive columns/blobs. That's what PTC is designed for, it's a consensus of a committee, instead of deciding it on our own
there should be a spec test for this too

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.

That's what PTC is designed for

it's mostly relevant for timeliness, if we are a supernode and seed the columns to the network, I don't think it's required to rely on the PTC for data availability as it's very likely that attesters will have all the data by the attestation deadline (since we seeded the columns) and vote for our block if we build on FULL. In any case, this scenario seems unlikely and not worth to handle separately

nflaig pushed a commit that referenced this pull request May 30, 2026
## Motivation

Align the private field name in `ProtoArray` with the gloas fork-choice
spec, which calls this store entry
[`payload_timeliness_vote`](https://github.com/ethereum/consensus-specs/blob/dev/specs/gloas/fork-choice.md#modified-store).
The companion field for blob-data availability is already tracked via
the existing `daVotes` map.

This was the last unmerged piece of lodekeeper#8 — the
data-availability tracking landed independently in #9416 and force-reorg
in #9387, so only the rename is left.

## Changes

- `packages/fork-choice/src/protoArray/protoArray.ts`: rename `private
ptcVotes` → `private payloadTimelinessVotes` and all 6 in-class
references.
- `packages/fork-choice/test/unit/protoArray/gloas.test.ts`: update 2
comments that reference the old field name.

Pure rename — no behavior change. Public `getPTCVotes()` API,
`isPayloadTimely`/`isPayloadNotTimely`, and surrounding spec comments
are unchanged.

## Verification

- `pnpm check-types` clean in `packages/fork-choice`.
- `pnpm biome check` clean on the two touched files.
- `vitest run test/unit/protoArray/gloas.test.ts` → 80/80 pass.

🤖 Generated with AI assistance

---------

Co-authored-by: lodekeeper <lodekeeper@users.noreply.github.com>
if (isStatePostGloas(updatedPrepareState)) {
if (this.chain.forkChoice.shouldExtendPayload(updatedHead.blockRoot)) {
// Spec: should_build_on_full(store, head) — see produceBlockBody.ts for context.
if (this.chain.forkChoice.shouldBuildOnFull(updatedHead)) {

@nflaig nflaig May 31, 2026

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.

worth noting that the timing when this is called is not correct, unless I am missing some other changes we did in the meantime but #9164 (comment) and suggestion along the lines here #9164 (comment) are still relevant

we will still reorg the payload when we call shouldBuildOnFull() == false in produceBlockBody.ts but that's at the start of the next slot and the execution payload we prepared here will be on top of the blockHash and not the parentBlockHash

likely delaying this call to PTC deadline + 1 second is fine, but may not be ideal as we also don't wanna delay the FCU for too long as it gives less time to the execution client

likely the ideal approach is

  • call FCU at 8 seconds (as today) if we have verified the parent's payload, or as soon as we receive it afterswards
  • evaluate shouldBuildOnFull() again at ~10 seconds (with PTC votes), or proactively if threshold is reached via event based trigger (something like here) and if parentBlockHash changes, call FCU again, overriding the previous call
  • keep the final decision in produceBlockBody.ts at start of next slot but it should be rare that the parentBlockHash will be different from the prepared block hash if we follow the above

also I noticed we should probably add more logs here to know if we do a payload reorg

@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