Skip to content

feat: implement should_apply_proposer_boost for gloas#9233

Draft
ensi321 wants to merge 2 commits intounstablefrom
nc/should-apply-proposer-boost
Draft

feat: implement should_apply_proposer_boost for gloas#9233
ensi321 wants to merge 2 commits intounstablefrom
nc/should-apply-proposer-boost

Conversation

@ensi321
Copy link
Copy Markdown
Contributor

@ensi321 ensi321 commented Apr 18, 2026

Summary

Implements the should_apply_proposer_boost logic ethereum/consensus-specs/pull/4807.

Changes

  • Add ptcTimeliness and proposerIndex fields to ProtoBlock
  • Add isBlockPtcTimely to track PTC deadline timeliness
  • Add shouldApplyProposerBoost which withholds boost when the parent is a weak, equivocating block from the previous slot
  • Add findEquivocatingBlocks in ProtoArray to detect proposer equivocations
  • Gate proposer boost in getWeight on shouldApplyProposerBoost()
  • Pre-gloas blocks retain unconditional boost (backward compatible)

🤖 Generated with Claude Code

Implement the `should_apply_proposer_boost` logic from consensus-specs
commit 71d1151 (PR #4807). This addresses the builder reveal safety
concern where a colluding next-slot proposer could use proposer boost
to override a legitimately revealed block.

Changes:
- Add `ptcTimeliness` and `proposerIndex` fields to ProtoBlock
- Add `isBlockPtcTimely` to track PTC deadline timeliness
- Add `shouldApplyProposerBoost` which withholds boost when the parent
  is a weak, equivocating block from the previous slot
- Add `findEquivocatingBlocks` in ProtoArray to detect proposer
  equivocations by scanning for PTC-timely blocks at the same slot
  from the same proposer
- Gate proposer boost in `getWeight` on `shouldApplyProposerBoost()`
- Pre-gloas blocks retain unconditional boost (backward compatible)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ensi321 ensi321 requested a review from a team as a code owner April 18, 2026 02:23
Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request implements the Gloas fork-choice logic for proposer boost and PTC (Payload Timeliness Committee) timeliness. It adds ptcTimeliness and proposerIndex to block metadata and introduces the shouldApplyProposerBoost logic, which considers parent block weight and proposer equivocations. Feedback focuses on preventing a crash during the fork transition by using dynamic payload status for parent nodes and optimizing the performance of the equivocation check to avoid O(N) map iterations during head updates.

slotsPerEpoch: SLOTS_PER_EPOCH,
committeePercent: this.config.REORG_HEAD_WEIGHT_THRESHOLD,
});
const parentNode = this.protoArray.getNode(parentBlock.blockRoot, PayloadStatus.PENDING);
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.

high

The hardcoded PayloadStatus.PENDING will cause a crash during the fork transition. When the first Gloas block is processed, its parent is a pre-Gloas block. Pre-Gloas blocks only have the FULL variant, and protoArray.getNode throws a ProtoArrayError if PENDING or EMPTY is requested for a pre-Gloas block. Using parentBlock.payloadStatus ensures the correct variant is requested for both pre-Gloas and Gloas blocks.

Suggested change
const parentNode = this.protoArray.getNode(parentBlock.blockRoot, PayloadStatus.PENDING);
const parentNode = this.protoArray.getNode(parentBlock.blockRoot, parentBlock.payloadStatus);

Comment on lines +1878 to +1891
for (const [root, variantOrArr] of this.indices.entries()) {
if (root === excludeRoot) continue;
const nodeIndex = Array.isArray(variantOrArr) ? variantOrArr[0] : variantOrArr;
if (nodeIndex === undefined) continue;
const node = this.nodes[nodeIndex];
if (
node !== undefined &&
node.slot === slot &&
node.proposerIndex === proposerIndex &&
node.ptcTimeliness
) {
result.push(node);
}
}
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.

high

Iterating over all indices in the ProtoArray on every updateHead call (via shouldApplyProposerBoost) is a significant performance bottleneck, especially during periods of long unfinality when the number of nodes can be large. Since updateHead is called for every attestation and block, this $O(N)$ operation can lead to high CPU usage. Iterating this.nodes directly is more efficient than iterating Map entries, and we can filter by payloadStatus to ensure we only check one variant per block root.

    for (let i = this.nodes.length - 1; i >= 0; i--) {
      const node = this.nodes[i];
      if (
        node.slot === slot &&
        node.proposerIndex === proposerIndex &&
        node.blockRoot !== excludeRoot &&
        node.ptcTimeliness &&
        (isGloasBlock(node) ? node.payloadStatus === PayloadStatus.PENDING : true)
      ) {
        result.push(node);
      }
    }

@ensi321 ensi321 marked this pull request as draft April 18, 2026 02:25
Copy link
Copy Markdown

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

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: 9bc6eb6ce5

ℹ️ About Codex in GitHub

Codex has been enabled to automatically 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 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

slotsPerEpoch: SLOTS_PER_EPOCH,
committeePercent: this.config.REORG_HEAD_WEIGHT_THRESHOLD,
});
const parentNode = this.protoArray.getNode(parentBlock.blockRoot, PayloadStatus.PENDING);
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 Use parent’s canonical payload status for weak-head lookup

When shouldApplyProposerBoost() evaluates a Gloas boosted block whose parent is still pre-Gloas (the common fork-boundary case where parent is from the immediately previous slot), this.protoArray.getNode(parentBlock.blockRoot, PayloadStatus.PENDING) can throw INVALID_NODE_INDEX because pre-Gloas roots only have a FULL variant. This makes updateHead() fail during proposer-boost evaluation right at transition conditions instead of cleanly applying/withholding boost. The lookup should use the parent’s canonical/default payload status (or parentBlock.payloadStatus) rather than hard-coding PENDING.

Useful? React with 👍 / 👎.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 18, 2026

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: 9dbd249 Previous: 9f5db5b Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 857.71 us/op 1.3821 ms/op 0.62
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 38.271 us/op 39.546 us/op 0.97
BLS verify - blst 723.15 us/op 748.26 us/op 0.97
BLS verifyMultipleSignatures 3 - blst 1.2776 ms/op 1.3645 ms/op 0.94
BLS verifyMultipleSignatures 8 - blst 2.0497 ms/op 2.1716 ms/op 0.94
BLS verifyMultipleSignatures 32 - blst 6.4501 ms/op 6.9760 ms/op 0.92
BLS verifyMultipleSignatures 64 - blst 12.540 ms/op 13.364 ms/op 0.94
BLS verifyMultipleSignatures 128 - blst 24.390 ms/op 26.049 ms/op 0.94
BLS deserializing 10000 signatures 610.21 ms/op 635.95 ms/op 0.96
BLS deserializing 100000 signatures 6.0491 s/op 6.4979 s/op 0.93
BLS verifyMultipleSignatures - same message - 3 - blst 721.83 us/op 836.73 us/op 0.86
BLS verifyMultipleSignatures - same message - 8 - blst 847.05 us/op 980.10 us/op 0.86
BLS verifyMultipleSignatures - same message - 32 - blst 1.4801 ms/op 1.5695 ms/op 0.94
BLS verifyMultipleSignatures - same message - 64 - blst 2.2930 ms/op 2.4213 ms/op 0.95
BLS verifyMultipleSignatures - same message - 128 - blst 3.8001 ms/op 4.1543 ms/op 0.91
BLS aggregatePubkeys 32 - blst 16.632 us/op 17.909 us/op 0.93
BLS aggregatePubkeys 128 - blst 59.416 us/op 64.032 us/op 0.93
getSlashingsAndExits - default max 44.354 us/op 57.486 us/op 0.77
getSlashingsAndExits - 2k 327.71 us/op 462.79 us/op 0.71
proposeBlockBody type=full, size=empty 576.44 us/op 843.12 us/op 0.68
isKnown best case - 1 super set check 163.00 ns/op 182.00 ns/op 0.90
isKnown normal case - 2 super set checks 165.00 ns/op 168.00 ns/op 0.98
isKnown worse case - 16 super set checks 163.00 ns/op 172.00 ns/op 0.95
validate api signedAggregateAndProof - struct 1.4307 ms/op 1.5655 ms/op 0.91
validate gossip signedAggregateAndProof - struct 1.4254 ms/op 1.5617 ms/op 0.91
batch validate gossip attestation - vc 640000 - chunk 32 102.62 us/op 111.61 us/op 0.92
batch validate gossip attestation - vc 640000 - chunk 64 91.354 us/op 97.734 us/op 0.93
batch validate gossip attestation - vc 640000 - chunk 128 84.989 us/op 92.047 us/op 0.92
batch validate gossip attestation - vc 640000 - chunk 256 81.017 us/op 88.130 us/op 0.92
bytes32 toHexString 277.00 ns/op 292.00 ns/op 0.95
bytes32 Buffer.toString(hex) 179.00 ns/op 182.00 ns/op 0.98
bytes32 Buffer.toString(hex) from Uint8Array 250.00 ns/op 254.00 ns/op 0.98
bytes32 Buffer.toString(hex) + 0x 182.00 ns/op 180.00 ns/op 1.01
Return object 10000 times 0.20450 ns/op 0.21680 ns/op 0.94
Throw Error 10000 times 3.1242 us/op 3.3593 us/op 0.93
toHex 88.609 ns/op 96.769 ns/op 0.92
Buffer.from 80.000 ns/op 87.551 ns/op 0.91
shared Buffer 54.298 ns/op 59.331 ns/op 0.92
fastMsgIdFn sha256 / 200 bytes 1.4230 us/op 1.5370 us/op 0.93
fastMsgIdFn h32 xxhash / 200 bytes 159.00 ns/op 156.00 ns/op 1.02
fastMsgIdFn h64 xxhash / 200 bytes 207.00 ns/op 202.00 ns/op 1.02
fastMsgIdFn sha256 / 1000 bytes 4.4970 us/op 4.8470 us/op 0.93
fastMsgIdFn h32 xxhash / 1000 bytes 248.00 ns/op 257.00 ns/op 0.96
fastMsgIdFn h64 xxhash / 1000 bytes 254.00 ns/op 265.00 ns/op 0.96
fastMsgIdFn sha256 / 10000 bytes 39.723 us/op 42.485 us/op 0.93
fastMsgIdFn h32 xxhash / 10000 bytes 1.2100 us/op 1.2770 us/op 0.95
fastMsgIdFn h64 xxhash / 10000 bytes 796.00 ns/op 825.00 ns/op 0.96
send data - 1000 256B messages 4.3039 ms/op 4.1786 ms/op 1.03
send data - 1000 512B messages 4.2816 ms/op 4.2290 ms/op 1.01
send data - 1000 1024B messages 4.4744 ms/op 4.5719 ms/op 0.98
send data - 1000 1200B messages 4.8815 ms/op 4.6538 ms/op 1.05
send data - 1000 2048B messages 5.1045 ms/op 5.6032 ms/op 0.91
send data - 1000 4096B messages 5.6276 ms/op 6.5677 ms/op 0.86
send data - 1000 16384B messages 26.935 ms/op 44.468 ms/op 0.61
send data - 1000 65536B messages 106.48 ms/op 285.92 ms/op 0.37
enrSubnets - fastDeserialize 64 bits 742.00 ns/op 797.00 ns/op 0.93
enrSubnets - ssz BitVector 64 bits 270.00 ns/op 269.00 ns/op 1.00
enrSubnets - fastDeserialize 4 bits 107.00 ns/op 103.00 ns/op 1.04
enrSubnets - ssz BitVector 4 bits 268.00 ns/op 268.00 ns/op 1.00
prioritizePeers score -10:0 att 32-0.1 sync 2-0 198.98 us/op 216.33 us/op 0.92
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 227.27 us/op 249.01 us/op 0.91
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 326.79 us/op 349.79 us/op 0.93
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 576.04 us/op 599.68 us/op 0.96
prioritizePeers score 0:0 att 64-1 sync 4-1 672.70 us/op 713.15 us/op 0.94
array of 16000 items push then shift 1.2107 us/op 1.3100 us/op 0.92
LinkedList of 16000 items push then shift 7.1640 ns/op 7.2480 ns/op 0.99
array of 16000 items push then pop 62.004 ns/op 71.670 ns/op 0.87
LinkedList of 16000 items push then pop 5.6890 ns/op 6.0030 ns/op 0.95
array of 24000 items push then shift 1.7641 us/op 1.9299 us/op 0.91
LinkedList of 24000 items push then shift 6.7820 ns/op 6.8910 ns/op 0.98
array of 24000 items push then pop 88.342 ns/op 98.782 ns/op 0.89
LinkedList of 24000 items push then pop 5.7270 ns/op 6.0200 ns/op 0.95
intersect bitArray bitLen 8 4.5620 ns/op 4.7970 ns/op 0.95
intersect array and set length 8 27.756 ns/op 33.438 ns/op 0.83
intersect bitArray bitLen 128 23.012 ns/op 24.409 ns/op 0.94
intersect array and set length 128 471.38 ns/op 499.44 ns/op 0.94
bitArray.getTrueBitIndexes() bitLen 128 920.00 ns/op 1.0350 us/op 0.89
bitArray.getTrueBitIndexes() bitLen 248 1.6890 us/op 1.7900 us/op 0.94
bitArray.getTrueBitIndexes() bitLen 512 3.4100 us/op 3.6510 us/op 0.93
Full columns - reconstruct all 6 blobs 194.49 us/op 181.98 us/op 1.07
Full columns - reconstruct half of the blobs out of 6 98.871 us/op 70.320 us/op 1.41
Full columns - reconstruct single blob out of 6 41.449 us/op 71.601 us/op 0.58
Half columns - reconstruct all 6 blobs 364.36 ms/op 402.30 ms/op 0.91
Half columns - reconstruct half of the blobs out of 6 183.62 ms/op 198.37 ms/op 0.93
Half columns - reconstruct single blob out of 6 64.762 ms/op 71.915 ms/op 0.90
Full columns - reconstruct all 10 blobs 309.67 us/op 304.11 us/op 1.02
Full columns - reconstruct half of the blobs out of 10 154.02 us/op 294.43 us/op 0.52
Full columns - reconstruct single blob out of 10 30.535 us/op 59.980 us/op 0.51
Half columns - reconstruct all 10 blobs 625.67 ms/op 674.33 ms/op 0.93
Half columns - reconstruct half of the blobs out of 10 311.41 ms/op 341.60 ms/op 0.91
Half columns - reconstruct single blob out of 10 65.733 ms/op 68.301 ms/op 0.96
Full columns - reconstruct all 20 blobs 1.6858 ms/op 1.8874 ms/op 0.89
Full columns - reconstruct half of the blobs out of 20 171.88 us/op 220.26 us/op 0.78
Full columns - reconstruct single blob out of 20 31.072 us/op 34.450 us/op 0.90
Half columns - reconstruct all 20 blobs 1.2174 s/op 1.3277 s/op 0.92
Half columns - reconstruct half of the blobs out of 20 604.14 ms/op 659.83 ms/op 0.92
Half columns - reconstruct single blob out of 20 64.906 ms/op 70.486 ms/op 0.92
Set add up to 64 items then delete first 1.9586 us/op 2.1920 us/op 0.89
OrderedSet add up to 64 items then delete first 3.1361 us/op 3.4855 us/op 0.90
Set add up to 64 items then delete last 1.9816 us/op 2.1708 us/op 0.91
OrderedSet add up to 64 items then delete last 3.0455 us/op 3.3949 us/op 0.90
Set add up to 64 items then delete middle 1.9745 us/op 2.1708 us/op 0.91
OrderedSet add up to 64 items then delete middle 4.4275 us/op 4.8577 us/op 0.91
Set add up to 128 items then delete first 3.9229 us/op 4.3911 us/op 0.89
OrderedSet add up to 128 items then delete first 5.9553 us/op 6.7053 us/op 0.89
Set add up to 128 items then delete last 3.6118 us/op 3.9796 us/op 0.91
OrderedSet add up to 128 items then delete last 5.4800 us/op 5.8898 us/op 0.93
Set add up to 128 items then delete middle 3.5901 us/op 3.9596 us/op 0.91
OrderedSet add up to 128 items then delete middle 10.979 us/op 11.873 us/op 0.92
Set add up to 256 items then delete first 7.3565 us/op 8.0169 us/op 0.92
OrderedSet add up to 256 items then delete first 11.386 us/op 12.402 us/op 0.92
Set add up to 256 items then delete last 7.1342 us/op 7.7757 us/op 0.92
OrderedSet add up to 256 items then delete last 10.941 us/op 11.794 us/op 0.93
Set add up to 256 items then delete middle 7.0699 us/op 7.7358 us/op 0.91
OrderedSet add up to 256 items then delete middle 33.310 us/op 35.428 us/op 0.94
pass gossip attestations to forkchoice per slot 2.4132 ms/op 2.5756 ms/op 0.94
forkChoice updateHead vc 100000 bc 64 eq 0 375.11 us/op 424.66 us/op 0.88
forkChoice updateHead vc 600000 bc 64 eq 0 2.2146 ms/op 2.5029 ms/op 0.88
forkChoice updateHead vc 1000000 bc 64 eq 0 3.6623 ms/op 4.0971 ms/op 0.89
forkChoice updateHead vc 600000 bc 320 eq 0 2.2136 ms/op 2.4654 ms/op 0.90
forkChoice updateHead vc 600000 bc 1200 eq 0 2.2355 ms/op 2.9142 ms/op 0.77
forkChoice updateHead vc 600000 bc 7200 eq 0 2.5434 ms/op 2.8884 ms/op 0.88
forkChoice updateHead vc 600000 bc 64 eq 1000 2.7662 ms/op 3.0026 ms/op 0.92
forkChoice updateHead vc 600000 bc 64 eq 10000 2.8416 ms/op 3.1097 ms/op 0.91
forkChoice updateHead vc 600000 bc 64 eq 300000 6.6145 ms/op 7.3946 ms/op 0.89
computeDeltas 1400000 validators 0% inactive 11.784 ms/op 13.085 ms/op 0.90
computeDeltas 1400000 validators 10% inactive 11.155 ms/op 12.237 ms/op 0.91
computeDeltas 1400000 validators 20% inactive 10.099 ms/op 11.192 ms/op 0.90
computeDeltas 1400000 validators 50% inactive 7.7974 ms/op 8.5383 ms/op 0.91
computeDeltas 2100000 validators 0% inactive 17.725 ms/op 19.609 ms/op 0.90
computeDeltas 2100000 validators 10% inactive 16.617 ms/op 18.336 ms/op 0.91
computeDeltas 2100000 validators 20% inactive 15.206 ms/op 16.791 ms/op 0.91
computeDeltas 2100000 validators 50% inactive 8.8311 ms/op 9.8262 ms/op 0.90
altair processAttestation - 250000 vs - 7PWei normalcase 2.1883 ms/op 2.4809 ms/op 0.88
altair processAttestation - 250000 vs - 7PWei worstcase 2.4506 ms/op 3.1960 ms/op 0.77
altair processAttestation - setStatus - 1/6 committees join 92.364 us/op 104.36 us/op 0.89
altair processAttestation - setStatus - 1/3 committees join 188.12 us/op 213.45 us/op 0.88
altair processAttestation - setStatus - 1/2 committees join 260.98 us/op 293.43 us/op 0.89
altair processAttestation - setStatus - 2/3 committees join 348.72 us/op 385.10 us/op 0.91
altair processAttestation - setStatus - 4/5 committees join 477.75 us/op 539.71 us/op 0.89
altair processAttestation - setStatus - 100% committees join 568.81 us/op 621.65 us/op 0.92
altair processBlock - 250000 vs - 7PWei normalcase 4.3117 ms/op 4.4764 ms/op 0.96
altair processBlock - 250000 vs - 7PWei normalcase hashState 15.615 ms/op 17.574 ms/op 0.89
altair processBlock - 250000 vs - 7PWei worstcase 20.448 ms/op 23.353 ms/op 0.88
altair processBlock - 250000 vs - 7PWei worstcase hashState 42.050 ms/op 50.717 ms/op 0.83
phase0 processBlock - 250000 vs - 7PWei normalcase 1.2991 ms/op 1.5151 ms/op 0.86
phase0 processBlock - 250000 vs - 7PWei worstcase 15.484 ms/op 17.592 ms/op 0.88
altair processEth1Data - 250000 vs - 7PWei normalcase 269.97 us/op 305.08 us/op 0.88
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 3.0930 us/op 4.7210 us/op 0.66
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 20.164 us/op 22.416 us/op 0.90
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 5.3570 us/op 8.3250 us/op 0.64
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 3.3360 us/op 5.9940 us/op 0.56
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 88.508 us/op 99.526 us/op 0.89
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.3113 ms/op 1.4624 ms/op 0.90
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.7039 ms/op 1.9108 ms/op 0.89
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.7089 ms/op 1.9085 ms/op 0.90
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.6488 ms/op 4.2570 ms/op 0.86
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 1.9296 ms/op 2.1628 ms/op 0.89
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 3.9233 ms/op 6.4604 ms/op 0.61
Tree 40 250000 create 298.25 ms/op 327.49 ms/op 0.91
Tree 40 250000 get(125000) 85.962 ns/op 96.956 ns/op 0.89
Tree 40 250000 set(125000) 942.07 ns/op 1.0548 us/op 0.89
Tree 40 250000 toArray() 11.715 ms/op 12.434 ms/op 0.94
Tree 40 250000 iterate all - toArray() + loop 11.367 ms/op 17.949 ms/op 0.63
Tree 40 250000 iterate all - get(i) 33.901 ms/op 44.721 ms/op 0.76
Array 250000 create 1.9954 ms/op 2.3834 ms/op 0.84
Array 250000 clone - spread 617.81 us/op 856.12 us/op 0.72
Array 250000 get(125000) 0.29400 ns/op 0.30900 ns/op 0.95
Array 250000 set(125000) 0.29400 ns/op 0.31600 ns/op 0.93
Array 250000 iterate all - loop 54.811 us/op 59.581 us/op 0.92
phase0 afterProcessEpoch - 250000 vs - 7PWei 38.086 ms/op 40.218 ms/op 0.95
Array.fill - length 1000000 2.0109 ms/op 2.0798 ms/op 0.97
Array push - length 1000000 8.2091 ms/op 13.821 ms/op 0.59
Array.get 0.19380 ns/op 0.21760 ns/op 0.89
Uint8Array.get 0.22852 ns/op 0.24564 ns/op 0.93
phase0 beforeProcessEpoch - 250000 vs - 7PWei 14.548 ms/op 19.802 ms/op 0.73
altair processEpoch - mainnet_e81889 265.59 ms/op 283.11 ms/op 0.94
mainnet_e81889 - altair beforeProcessEpoch 15.934 ms/op 64.871 ms/op 0.25
mainnet_e81889 - altair processJustificationAndFinalization 5.4770 us/op 6.4140 us/op 0.85
mainnet_e81889 - altair processInactivityUpdates 4.1915 ms/op 4.2023 ms/op 1.00
mainnet_e81889 - altair processRewardsAndPenalties 19.191 ms/op 23.395 ms/op 0.82
mainnet_e81889 - altair processRegistryUpdates 529.00 ns/op 591.00 ns/op 0.90
mainnet_e81889 - altair processSlashings 136.00 ns/op 148.00 ns/op 0.92
mainnet_e81889 - altair processEth1DataReset 133.00 ns/op 153.00 ns/op 0.87
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.3342 ms/op 1.6603 ms/op 0.80
mainnet_e81889 - altair processSlashingsReset 678.00 ns/op 756.00 ns/op 0.90
mainnet_e81889 - altair processRandaoMixesReset 1.0990 us/op 1.2630 us/op 0.87
mainnet_e81889 - altair processHistoricalRootsUpdate 134.00 ns/op 148.00 ns/op 0.91
mainnet_e81889 - altair processParticipationFlagUpdates 412.00 ns/op 503.00 ns/op 0.82
mainnet_e81889 - altair processSyncCommitteeUpdates 112.00 ns/op 122.00 ns/op 0.92
mainnet_e81889 - altair afterProcessEpoch 39.173 ms/op 43.395 ms/op 0.90
capella processEpoch - mainnet_e217614 816.37 ms/op 916.75 ms/op 0.89
mainnet_e217614 - capella beforeProcessEpoch 62.231 ms/op 81.554 ms/op 0.76
mainnet_e217614 - capella processJustificationAndFinalization 6.0060 us/op 6.0810 us/op 0.99
mainnet_e217614 - capella processInactivityUpdates 16.729 ms/op 16.832 ms/op 0.99
mainnet_e217614 - capella processRewardsAndPenalties 84.384 ms/op 100.98 ms/op 0.84
mainnet_e217614 - capella processRegistryUpdates 4.2330 us/op 4.6990 us/op 0.90
mainnet_e217614 - capella processSlashings 136.00 ns/op 148.00 ns/op 0.92
mainnet_e217614 - capella processEth1DataReset 129.00 ns/op 142.00 ns/op 0.91
mainnet_e217614 - capella processEffectiveBalanceUpdates 12.842 ms/op 6.8053 ms/op 1.89
mainnet_e217614 - capella processSlashingsReset 666.00 ns/op 752.00 ns/op 0.89
mainnet_e217614 - capella processRandaoMixesReset 1.1840 us/op 1.6370 us/op 0.72
mainnet_e217614 - capella processHistoricalRootsUpdate 133.00 ns/op 147.00 ns/op 0.90
mainnet_e217614 - capella processParticipationFlagUpdates 419.00 ns/op 479.00 ns/op 0.87
mainnet_e217614 - capella afterProcessEpoch 104.76 ms/op 110.33 ms/op 0.95
phase0 processEpoch - mainnet_e58758 296.19 ms/op 348.54 ms/op 0.85
mainnet_e58758 - phase0 beforeProcessEpoch 64.443 ms/op 78.209 ms/op 0.82
mainnet_e58758 - phase0 processJustificationAndFinalization 5.8240 us/op 6.7100 us/op 0.87
mainnet_e58758 - phase0 processRewardsAndPenalties 15.788 ms/op 17.823 ms/op 0.89
mainnet_e58758 - phase0 processRegistryUpdates 2.2020 us/op 2.3480 us/op 0.94
mainnet_e58758 - phase0 processSlashings 249.00 ns/op 147.00 ns/op 1.69
mainnet_e58758 - phase0 processEth1DataReset 146.00 ns/op 142.00 ns/op 1.03
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 1.5538 ms/op 1.0102 ms/op 1.54
mainnet_e58758 - phase0 processSlashingsReset 843.00 ns/op 1.0420 us/op 0.81
mainnet_e58758 - phase0 processRandaoMixesReset 1.1660 us/op 1.5090 us/op 0.77
mainnet_e58758 - phase0 processHistoricalRootsUpdate 135.00 ns/op 152.00 ns/op 0.89
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.1310 us/op 1.3000 us/op 0.87
mainnet_e58758 - phase0 afterProcessEpoch 32.504 ms/op 34.877 ms/op 0.93
phase0 processEffectiveBalanceUpdates - 250000 normalcase 965.30 us/op 1.1322 ms/op 0.85
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.4968 ms/op 1.7956 ms/op 0.83
altair processInactivityUpdates - 250000 normalcase 13.671 ms/op 12.886 ms/op 1.06
altair processInactivityUpdates - 250000 worstcase 13.313 ms/op 13.308 ms/op 1.00
phase0 processRegistryUpdates - 250000 normalcase 2.2470 us/op 4.0690 us/op 0.55
phase0 processRegistryUpdates - 250000 badcase_full_deposits 137.65 us/op 158.65 us/op 0.87
phase0 processRegistryUpdates - 250000 worstcase 0.5 54.402 ms/op 70.288 ms/op 0.77
altair processRewardsAndPenalties - 250000 normalcase 15.460 ms/op 16.264 ms/op 0.95
altair processRewardsAndPenalties - 250000 worstcase 15.572 ms/op 15.980 ms/op 0.97
phase0 getAttestationDeltas - 250000 normalcase 5.1307 ms/op 5.8147 ms/op 0.88
phase0 getAttestationDeltas - 250000 worstcase 5.1749 ms/op 5.7397 ms/op 0.90
phase0 processSlashings - 250000 worstcase 57.329 us/op 68.787 us/op 0.83
altair processSyncCommitteeUpdates - 250000 9.8515 ms/op 10.701 ms/op 0.92
BeaconState.hashTreeRoot - No change 189.00 ns/op 209.00 ns/op 0.90
BeaconState.hashTreeRoot - 1 full validator 72.283 us/op 75.273 us/op 0.96
BeaconState.hashTreeRoot - 32 full validator 833.88 us/op 1.1809 ms/op 0.71
BeaconState.hashTreeRoot - 512 full validator 7.9470 ms/op 8.6784 ms/op 0.92
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 92.532 us/op 97.196 us/op 0.95
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.4503 ms/op 1.3795 ms/op 1.05
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 15.640 ms/op 14.543 ms/op 1.08
BeaconState.hashTreeRoot - 1 balances 72.176 us/op 78.841 us/op 0.92
BeaconState.hashTreeRoot - 32 balances 718.79 us/op 874.39 us/op 0.82
BeaconState.hashTreeRoot - 512 balances 5.8660 ms/op 5.7948 ms/op 1.01
BeaconState.hashTreeRoot - 250000 balances 182.24 ms/op 152.60 ms/op 1.19
aggregationBits - 2048 els - zipIndexesInBitList 18.551 us/op 21.466 us/op 0.86
regular array get 100000 times 22.122 us/op 23.668 us/op 0.93
wrappedArray get 100000 times 22.091 us/op 23.741 us/op 0.93
arrayWithProxy get 100000 times 9.4706 ms/op 10.065 ms/op 0.94
ssz.Root.equals 20.632 ns/op 22.175 ns/op 0.93
byteArrayEquals 20.403 ns/op 22.032 ns/op 0.93
Buffer.compare 8.4530 ns/op 9.1160 ns/op 0.93
processSlot - 1 slots 9.2590 us/op 10.135 us/op 0.91
processSlot - 32 slots 2.0599 ms/op 1.7319 ms/op 1.19
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 3.4194 ms/op 3.0860 ms/op 1.11
getCommitteeAssignments - req 1 vs - 250000 vc 1.5801 ms/op 1.7082 ms/op 0.92
getCommitteeAssignments - req 100 vs - 250000 vc 3.2522 ms/op 3.4717 ms/op 0.94
getCommitteeAssignments - req 1000 vs - 250000 vc 3.4908 ms/op 3.7483 ms/op 0.93
findModifiedValidators - 10000 modified validators 715.39 ms/op 770.72 ms/op 0.93
findModifiedValidators - 1000 modified validators 403.48 ms/op 529.31 ms/op 0.76
findModifiedValidators - 100 modified validators 278.71 ms/op 348.35 ms/op 0.80
findModifiedValidators - 10 modified validators 227.85 ms/op 237.64 ms/op 0.96
findModifiedValidators - 1 modified validators 161.21 ms/op 173.19 ms/op 0.93
findModifiedValidators - no difference 140.80 ms/op 171.50 ms/op 0.82
migrate state 1500000 validators, 3400 modified, 2000 new 2.8867 s/op 2.6846 s/op 1.08
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 3.6400 ns/op 3.8200 ns/op 0.95
state getBlockRootAtSlot - 250000 vs - 7PWei 327.42 ns/op 303.07 ns/op 1.08
computeProposerIndex 100000 validators 1.2909 ms/op 1.3289 ms/op 0.97
getNextSyncCommitteeIndices 1000 validators 2.7274 ms/op 2.8362 ms/op 0.96
getNextSyncCommitteeIndices 10000 validators 24.095 ms/op 25.183 ms/op 0.96
getNextSyncCommitteeIndices 100000 validators 84.015 ms/op 86.752 ms/op 0.97
computeProposers - vc 250000 528.49 us/op 561.12 us/op 0.94
computeEpochShuffling - vc 250000 38.126 ms/op 39.115 ms/op 0.97
getNextSyncCommittee - vc 250000 9.1505 ms/op 9.5636 ms/op 0.96
nodejs block root to RootHex using toHex 89.088 ns/op 95.760 ns/op 0.93
nodejs block root to RootHex using toRootHex 54.604 ns/op 60.459 ns/op 0.90
nodejs fromHex(blob) 726.09 us/op 953.59 us/op 0.76
nodejs fromHexInto(blob) 600.71 us/op 649.92 us/op 0.92
nodejs block root to RootHex using the deprecated toHexString 441.15 ns/op 546.10 ns/op 0.81
nodejs byteArrayEquals 32 bytes (block root) 24.808 ns/op 26.168 ns/op 0.95
nodejs byteArrayEquals 48 bytes (pubkey) 35.999 ns/op 38.063 ns/op 0.95
nodejs byteArrayEquals 96 bytes (signature) 32.271 ns/op 36.603 ns/op 0.88
nodejs byteArrayEquals 1024 bytes 38.714 ns/op 44.443 ns/op 0.87
nodejs byteArrayEquals 131072 bytes (blob) 1.6698 us/op 1.9345 us/op 0.86
browser block root to RootHex using toHex 137.73 ns/op 146.63 ns/op 0.94
browser block root to RootHex using toRootHex 124.44 ns/op 133.55 ns/op 0.93
browser fromHex(blob) 1.4453 ms/op 1.7402 ms/op 0.83
browser fromHexInto(blob) 621.44 us/op 646.68 us/op 0.96
browser block root to RootHex using the deprecated toHexString 436.40 ns/op 376.93 ns/op 1.16
browser byteArrayEquals 32 bytes (block root) 26.707 ns/op 28.567 ns/op 0.93
browser byteArrayEquals 48 bytes (pubkey) 37.664 ns/op 40.140 ns/op 0.94
browser byteArrayEquals 96 bytes (signature) 70.377 ns/op 74.855 ns/op 0.94
browser byteArrayEquals 1024 bytes 721.18 ns/op 766.75 ns/op 0.94
browser byteArrayEquals 131072 bytes (blob) 90.505 us/op 96.329 us/op 0.94

by benchmarkbot/action

GrapeBaBa added a commit to GrapeBaBa/lodestar that referenced this pull request May 1, 2026
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into
the existing `forkChoiceTest` runner. The on-disk layout matches the
standard spec-test layout
(`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`),
so it slots in alongside `fork_choice` and `sync` runners.

Three test-only accommodations the compliance fixtures require:

1. `bls_setting: 2` — every compliance fixture uses placeholder
   signatures. Pass `validSignatures: testcase.meta?.bls_setting !==
   BigInt(1)` to `chain.processBlock` so verification short-circuits.
   Standard `fork_choice` fixtures use `bls_setting: 1` so behavior
   there is unchanged.

2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally
   re-import the same block (`dup_shift` mutations in their
   `meta.yaml`). Spec semantics for `on_block(store, known_block)` is
   a no-op success. Production block import correctly rejects with
   ALREADY_KNOWN; this runner treats that case as success only when
   the step is `valid: true`.

3. Cross-epoch attestation shuffling — `on_attestation` decodes
   aggregation_bits using the state at the attestation's target
   checkpoint, not the head state. The runner now resolves the right
   shuffling via ShufflingCache + regen (mirroring the production
   validation path) instead of `headState.epochCtx.getIndexedAttestation`,
   which only worked when the attestation's epoch happened to be in
   the head's epoch cache (±1 epoch) and broke on cross-epoch fork
   attestations surfaced by the compliance suite.

Adds support for two compliance-only check fields:

- `viable_for_head_roots_and_weights` (consensus-specs#3831): compared
  via `getViableHeads()`. Both sides are sorted by root before
  comparison since the spec doesn't fix order.
- `head_payload_status` (gloas): mapped between our internal enum
  ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0,
  FULL=1, PENDING=2).

Pass rate against the latest comptests workflow `small.tar.gz` artifact:

  fulu/fork_choice_compliance: 253/1472 cases pass (17.2%)

Top remaining failures:
- ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced
  a `block.proposer_index == get_beacon_proposer_index(head_state)`
  guard in `update_proposer_boost_root` that we do not yet implement;
  affects all forks (not just gloas equivocation handling). Tracked
  for follow-up alongside ChainSafe#9233.
- ~1% `Invalid viable heads` — proposer-boost rounding on minimal
  preset (see `getViableHeads()` weight note).
GrapeBaBa added a commit to GrapeBaBa/lodestar that referenced this pull request May 1, 2026
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into
the existing `forkChoiceTest` runner. The on-disk layout matches the
standard spec-test layout
(`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`),
so it slots in alongside `fork_choice` and `sync` runners.

Three test-only accommodations the compliance fixtures require:

1. `bls_setting: 2` — every compliance fixture uses placeholder
   signatures. Pass `validSignatures: testcase.meta?.bls_setting !==
   BigInt(1)` to `chain.processBlock` so verification short-circuits.
   Standard `fork_choice` fixtures use `bls_setting: 1` so behavior
   there is unchanged.

2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally
   re-import the same block (`dup_shift` mutations in their
   `meta.yaml`). Spec semantics for `on_block(store, known_block)` is
   a no-op success. Production block import correctly rejects with
   ALREADY_KNOWN; this runner treats that case as success only when
   the step is `valid: true`.

3. Cross-epoch attestation shuffling — `on_attestation` decodes
   aggregation_bits using the state at the attestation's target
   checkpoint, not the head state. The runner now resolves the right
   shuffling via ShufflingCache + regen (mirroring the production
   validation path) instead of `headState.epochCtx.getIndexedAttestation`,
   which only worked when the attestation's epoch happened to be in
   the head's epoch cache (±1 epoch) and broke on cross-epoch fork
   attestations surfaced by the compliance suite.

Adds support for two compliance-only check fields:

- `viable_for_head_roots_and_weights` (consensus-specs#3831): compared
  via `getViableHeads()`. Both sides are sorted by root before
  comparison since the spec doesn't fix order.
- `head_payload_status` (gloas): mapped between our internal enum
  ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0,
  FULL=1, PENDING=2).

Pass rate against the latest comptests workflow `small.tar.gz` artifact:

  fulu/fork_choice_compliance: 253/1472 cases pass (17.2%)

Top remaining failures:
- ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced
  a `block.proposer_index == get_beacon_proposer_index(head_state)`
  guard in `update_proposer_boost_root` that we do not yet implement;
  affects all forks (not just gloas equivocation handling). Tracked
  for follow-up alongside ChainSafe#9233.
- ~1% `Invalid viable heads` — proposer-boost rounding on minimal
  preset (see `getViableHeads()` weight note).
GrapeBaBa added a commit to GrapeBaBa/lodestar that referenced this pull request May 1, 2026
Wire the consensus-specs Fork Choice Compliance suite (ChainSafe#3831) into
the existing `forkChoiceTest` runner. The on-disk layout matches the
standard spec-test layout
(`tests/<preset>/<fork>/fork_choice_compliance/<handler>/<suite>/<case>/`),
so it slots in alongside `fork_choice` and `sync` runners.

Three test-only accommodations the compliance fixtures require:

1. `bls_setting: 2` — every compliance fixture uses placeholder
   signatures. Pass `validSignatures: testcase.meta?.bls_setting !==
   BigInt(1)` to `chain.processBlock` so verification short-circuits.
   Standard `fork_choice` fixtures use `bls_setting: 1` so behavior
   there is unchanged.

2. `BLOCK_ERROR_ALREADY_KNOWN` — compliance fixtures intentionally
   re-import the same block (`dup_shift` mutations in their
   `meta.yaml`). Spec semantics for `on_block(store, known_block)` is
   a no-op success. Production block import correctly rejects with
   ALREADY_KNOWN; this runner treats that case as success only when
   the step is `valid: true`.

3. Cross-epoch attestation shuffling — `on_attestation` decodes
   aggregation_bits using the state at the attestation's target
   checkpoint, not the head state. The runner now resolves the right
   shuffling via ShufflingCache + regen (mirroring the production
   validation path) instead of `headState.epochCtx.getIndexedAttestation`,
   which only worked when the attestation's epoch happened to be in
   the head's epoch cache (±1 epoch) and broke on cross-epoch fork
   attestations surfaced by the compliance suite.

Adds support for two compliance-only check fields:

- `viable_for_head_roots_and_weights` (consensus-specs#3831): compared
  via `getViableHeads()`. Both sides are sorted by root before
  comparison since the spec doesn't fix order.
- `head_payload_status` (gloas): mapped between our internal enum
  ordering (PENDING=0, EMPTY=1, FULL=2) and spec ordering (EMPTY=0,
  FULL=1, PENDING=2).

Pass rate against the latest comptests workflow `small.tar.gz` artifact:

  fulu/fork_choice_compliance: 253/1472 cases pass (17.2%)

Top remaining failures:
- ~80% `Invalid proposer boost root` — consensus-specs#4807 introduced
  a `block.proposer_index == get_beacon_proposer_index(head_state)`
  guard in `update_proposer_boost_root` that we do not yet implement;
  affects all forks (not just gloas equivocation handling). Tracked
  for follow-up alongside ChainSafe#9233.
- ~1% `Invalid viable heads` — proposer-boost rounding on minimal
  preset (see `getViableHeads()` weight note).
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.

1 participant