feat: gloas lc support - #9687
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for the Gloas fork in the light client, including block-to-header conversion, proof generation for the execution block hash, and upgrades to the light client sync spec tests to handle multi-fork scenarios. The review feedback highlights two potential runtime crashes: one in blockToLightClientHeader due to unsafe nested property access on signedExecutionPayloadBid, and another in computeBranchRoot where a malformed branch could result in an out-of-bounds array access during hashing. Implementing defensive checks in both locations will improve the robustness of the codebase.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| if (isForkPostGloas(fork)) { | ||
| const blockBody = block.body as BeaconBlockBody<ForkPostGloas>; | ||
| return { | ||
| beacon, | ||
| executionBlockHash: blockBody.signedExecutionPayloadBid.message.parentBlockHash, | ||
| executionBranch: getBlockBodyExecutionBlockHashProof(fork, blockBody), | ||
| }; | ||
| } |
There was a problem hiding this comment.
In blockToLightClientHeader, accessing blockBody.signedExecutionPayloadBid.message.parentBlockHash directly can cause a runtime crash (TypeError: Cannot read properties of undefined) if signedExecutionPayloadBid is undefined or null (which can happen with mock/partial blocks in tests or malformed blocks). Adding a defensive check with optional chaining ensures safety.
if (isForkPostGloas(fork)) {
const blockBody = block.body as BeaconBlockBody<ForkPostGloas>;
const parentBlockHash = blockBody.signedExecutionPayloadBid?.message?.parentBlockHash;
if (!parentBlockHash) {
throw new Error("signedExecutionPayloadBid or parentBlockHash is missing in Gloas block body");
}
return {
beacon,
executionBlockHash: parentBlockHash,
executionBranch: getBlockBodyExecutionBlockHashProof(fork, blockBody),
};
}There was a problem hiding this comment.
this doesn't seem valid, we do hashTreeRoot(block.body) before in blockToLightClientHeader() so if the bid is undefined that should already fail
There was a problem hiding this comment.
@gemini-code-assist I agree with the follow-up here that this one is not a real issue. blockToLightClientHeader() computes beacon.bodyRoot with the fork-specific BeaconBlockBody.hashTreeRoot(block.body) before reading signedExecutionPayloadBid, so a Gloas body missing that required field should already fail while hashing the body. Adding optional chaining would just move the failure and weaken the typed block-body assumption.
| function computeBranchRoot(leaf: Uint8Array, branch: Uint8Array[], gindex: bigint): Uint8Array { | ||
| const depth = getGindexDepth(Number(gindex)); | ||
| const proof = branch.slice(-depth); | ||
| const index = getGindexIndex(Number(gindex)); | ||
| let root = leaf; | ||
|
|
||
| for (let i = 0; i < depth; i++) { | ||
| root = Math.floor(index / 2 ** i) % 2 === 1 ? hash(proof[i], root) : hash(root, proof[i]); | ||
| } | ||
|
|
||
| return root; | ||
| } |
There was a problem hiding this comment.
In computeBranchRoot, if the branch parameter is shorter than the expected depth (for example, due to a malformed or truncated executionBranch in a light client header), proof.length will be less than depth. This will cause proof[i] to be undefined during the loop, leading to a runtime crash or unexpected behavior in the hash function. Adding a defensive check to ensure proof.length === depth prevents potential crashes.
function computeBranchRoot(leaf: Uint8Array, branch: Uint8Array[], gindex: bigint): Uint8Array {
const depth = getGindexDepth(Number(gindex));
const proof = branch.slice(-depth);
if (proof.length < depth) {
throw new Error("Invalid branch length: expected at least " + depth + ", got " + proof.length);
}
const index = getGindexIndex(Number(gindex));
let root = leaf;
for (let i = 0; i < depth; i++) {
root = Math.floor(index / 2 ** i) % 2 === 1 ? hash(proof[i], root) : hash(root, proof[i]);
}
return root;
}There was a problem hiding this comment.
couldn't hurt to add a check, done in 575528a
There was a problem hiding this comment.
@gemini-code-assist This is handled on current head: computeBranchRoot() now checks branch.length < depth before slicing and entering the loop, so proof[i] is defined for the full expected branch depth. The check was added in 575528a7f44228eaf1c3e308717bfda8f125125b.
|
| Benchmark suite | Current: ce03cd2 | Previous: de5f89e | Ratio |
|---|---|---|---|
| proposeBlockBody type=full, size=empty | 3.6279 ms/op | 660.73 us/op | 5.49 |
Full benchmark results
| Benchmark suite | Current: ce03cd2 | Previous: de5f89e | Ratio |
|---|---|---|---|
| getPubkeys - index2pubkey - req 1000 vs - 250000 vc | 1.3480 ms/op | 836.62 us/op | 1.61 |
| getPubkeys - validatorsArr - req 1000 vs - 250000 vc | 44.006 us/op | 39.187 us/op | 1.12 |
| BLS verify - blst | 786.98 us/op | 641.83 us/op | 1.23 |
| BLS verifyMultipleSignatures 3 - blst | 1.4360 ms/op | 1.3627 ms/op | 1.05 |
| BLS verifyMultipleSignatures 8 - blst | 2.2783 ms/op | 2.1629 ms/op | 1.05 |
| BLS verifyMultipleSignatures 32 - blst | 7.0990 ms/op | 6.9541 ms/op | 1.02 |
| BLS verifyMultipleSignatures 64 - blst | 13.822 ms/op | 13.398 ms/op | 1.03 |
| BLS verifyMultipleSignatures 128 - blst | 26.750 ms/op | 25.436 ms/op | 1.05 |
| BLS deserializing 10000 signatures | 628.85 ms/op | 629.83 ms/op | 1.00 |
| BLS deserializing 100000 signatures | 6.2390 s/op | 6.3196 s/op | 0.99 |
| BLS verifyMultipleSignatures - same message - 3 - blst | 769.24 us/op | 799.72 us/op | 0.96 |
| BLS verifyMultipleSignatures - same message - 8 - blst | 911.77 us/op | 939.08 us/op | 0.97 |
| BLS verifyMultipleSignatures - same message - 32 - blst | 1.5965 ms/op | 1.5581 ms/op | 1.02 |
| BLS verifyMultipleSignatures - same message - 64 - blst | 2.4294 ms/op | 2.3823 ms/op | 1.02 |
| BLS verifyMultipleSignatures - same message - 128 - blst | 4.1546 ms/op | 4.0610 ms/op | 1.02 |
| BLS aggregatePubkeys 32 - blst | 17.868 us/op | 17.543 us/op | 1.02 |
| BLS aggregatePubkeys 128 - blst | 63.941 us/op | 62.665 us/op | 1.02 |
| getSlashingsAndExits - default max | 53.326 us/op | 50.951 us/op | 1.05 |
| getSlashingsAndExits - 2k | 343.79 us/op | 402.73 us/op | 0.85 |
| proposeBlockBody type=full, size=empty | 3.6279 ms/op | 660.73 us/op | 5.49 |
| isKnown best case - 1 super set check | 172.00 ns/op | 161.00 ns/op | 1.07 |
| isKnown normal case - 2 super set checks | 163.00 ns/op | 156.00 ns/op | 1.04 |
| isKnown worse case - 16 super set checks | 155.00 ns/op | 157.00 ns/op | 0.99 |
| validate api signedAggregateAndProof - struct | 1.5752 ms/op | 1.5177 ms/op | 1.04 |
| validate gossip signedAggregateAndProof - struct | 1.5617 ms/op | 1.5120 ms/op | 1.03 |
| batch validate gossip attestation - vc 640000 - chunk 32 | 110.64 us/op | 108.01 us/op | 1.02 |
| batch validate gossip attestation - vc 640000 - chunk 64 | 97.507 us/op | 93.534 us/op | 1.04 |
| batch validate gossip attestation - vc 640000 - chunk 128 | 88.437 us/op | 86.773 us/op | 1.02 |
| batch validate gossip attestation - vc 640000 - chunk 256 | 87.097 us/op | 82.744 us/op | 1.05 |
| bytes32 toHexString | 297.00 ns/op | 282.00 ns/op | 1.05 |
| bytes32 Buffer.toString(hex) | 164.00 ns/op | 164.00 ns/op | 1.00 |
| bytes32 Buffer.toString(hex) from Uint8Array | 235.00 ns/op | 233.00 ns/op | 1.01 |
| bytes32 Buffer.toString(hex) + 0x | 163.00 ns/op | 167.00 ns/op | 0.98 |
| Return object 10000 times | 0.21500 ns/op | 0.21190 ns/op | 1.01 |
| Throw Error 10000 times | 3.2785 us/op | 3.2921 us/op | 1.00 |
| toHex | 92.974 ns/op | 96.926 ns/op | 0.96 |
| Buffer.from | 84.221 ns/op | 88.275 ns/op | 0.95 |
| shared Buffer | 57.752 ns/op | 61.849 ns/op | 0.93 |
| fastMsgIdFn sha256 / 200 bytes | 1.4850 us/op | 1.4690 us/op | 1.01 |
| fastMsgIdFn h32 xxhash / 200 bytes | 154.00 ns/op | 150.00 ns/op | 1.03 |
| fastMsgIdFn h64 xxhash / 200 bytes | 203.00 ns/op | 204.00 ns/op | 1.00 |
| fastMsgIdFn sha256 / 1000 bytes | 4.7390 us/op | 4.7310 us/op | 1.00 |
| fastMsgIdFn h32 xxhash / 1000 bytes | 245.00 ns/op | 241.00 ns/op | 1.02 |
| fastMsgIdFn h64 xxhash / 1000 bytes | 250.00 ns/op | 254.00 ns/op | 0.98 |
| fastMsgIdFn sha256 / 10000 bytes | 41.511 us/op | 41.757 us/op | 0.99 |
| fastMsgIdFn h32 xxhash / 10000 bytes | 1.2820 us/op | 1.2830 us/op | 1.00 |
| fastMsgIdFn h64 xxhash / 10000 bytes | 835.00 ns/op | 836.00 ns/op | 1.00 |
| send data - 1000 256B messages | 4.5260 ms/op | 4.2612 ms/op | 1.06 |
| send data - 1000 512B messages | 5.6296 ms/op | 5.0634 ms/op | 1.11 |
| send data - 1000 1024B messages | 6.0001 ms/op | 5.3563 ms/op | 1.12 |
| send data - 1000 1200B messages | 9.5250 ms/op | 5.9433 ms/op | 1.60 |
| send data - 1000 2048B messages | 11.758 ms/op | 9.0019 ms/op | 1.31 |
| send data - 1000 4096B messages | 109.30 ms/op | 87.008 ms/op | 1.26 |
| send data - 1000 16384B messages | 407.49 ms/op | 284.83 ms/op | 1.43 |
| send data - 1000 65536B messages | 1.7598 s/op | 1.2427 s/op | 1.42 |
| enrSubnets - fastDeserialize 64 bits | 785.00 ns/op | 792.00 ns/op | 0.99 |
| enrSubnets - ssz BitVector 64 bits | 272.00 ns/op | 293.00 ns/op | 0.93 |
| enrSubnets - fastDeserialize 4 bits | 106.00 ns/op | 102.00 ns/op | 1.04 |
| enrSubnets - ssz BitVector 4 bits | 280.00 ns/op | 290.00 ns/op | 0.97 |
| prioritizePeers score -10:0 att 32-0.1 sync 2-0 | 207.69 us/op | 202.51 us/op | 1.03 |
| prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 | 237.54 us/op | 230.93 us/op | 1.03 |
| prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 | 354.87 us/op | 334.19 us/op | 1.06 |
| prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 | 606.61 us/op | 590.64 us/op | 1.03 |
| prioritizePeers score 0:0 att 64-1 sync 4-1 | 738.46 us/op | 689.74 us/op | 1.07 |
| array of 16000 items push then shift | 1.3438 us/op | 1.2957 us/op | 1.04 |
| LinkedList of 16000 items push then shift | 9.0640 ns/op | 6.9240 ns/op | 1.31 |
| array of 16000 items push then pop | 78.573 ns/op | 67.525 ns/op | 1.16 |
| LinkedList of 16000 items push then pop | 6.3930 ns/op | 5.9920 ns/op | 1.07 |
| array of 24000 items push then shift | 1.9808 us/op | 1.9146 us/op | 1.03 |
| LinkedList of 24000 items push then shift | 7.1780 ns/op | 6.4270 ns/op | 1.12 |
| array of 24000 items push then pop | 110.83 ns/op | 101.19 ns/op | 1.10 |
| LinkedList of 24000 items push then pop | 6.2630 ns/op | 6.0000 ns/op | 1.04 |
| intersect bitArray bitLen 8 | 4.0230 ns/op | 3.9850 ns/op | 1.01 |
| intersect array and set length 8 | 30.519 ns/op | 30.314 ns/op | 1.01 |
| intersect bitArray bitLen 128 | 24.211 ns/op | 23.385 ns/op | 1.04 |
| intersect array and set length 128 | 518.62 ns/op | 501.52 ns/op | 1.03 |
| bitArray.getTrueBitIndexes() bitLen 128 | 953.00 ns/op | 916.00 ns/op | 1.04 |
| bitArray.getTrueBitIndexes() bitLen 248 | 1.7280 us/op | 1.6530 us/op | 1.05 |
| bitArray.getTrueBitIndexes() bitLen 512 | 3.6400 us/op | 3.4370 us/op | 1.06 |
| Full columns - reconstruct all 6 blobs | 174.54 us/op | 204.74 us/op | 0.85 |
| Full columns - reconstruct half of the blobs out of 6 | 76.449 us/op | 68.763 us/op | 1.11 |
| Full columns - reconstruct single blob out of 6 | 35.888 us/op | 31.105 us/op | 1.15 |
| Half columns - reconstruct all 6 blobs | 415.24 ms/op | 395.96 ms/op | 1.05 |
| Half columns - reconstruct half of the blobs out of 6 | 206.32 ms/op | 197.05 ms/op | 1.05 |
| Half columns - reconstruct single blob out of 6 | 74.521 ms/op | 66.549 ms/op | 1.12 |
| Set add up to 64 items then delete first | 2.1855 us/op | 1.6549 us/op | 1.32 |
| OrderedSet add up to 64 items then delete first | 3.4461 us/op | 2.5030 us/op | 1.38 |
| Set add up to 64 items then delete last | 2.2942 us/op | 1.8736 us/op | 1.22 |
| OrderedSet add up to 64 items then delete last | 3.8393 us/op | 2.7538 us/op | 1.39 |
| Set add up to 64 items then delete middle | 2.1634 us/op | 1.8727 us/op | 1.16 |
| OrderedSet add up to 64 items then delete middle | 4.8871 us/op | 4.2096 us/op | 1.16 |
| Set add up to 128 items then delete first | 4.3788 us/op | 3.7071 us/op | 1.18 |
| OrderedSet add up to 128 items then delete first | 6.6584 us/op | 5.6644 us/op | 1.18 |
| Set add up to 128 items then delete last | 3.9737 us/op | 3.5694 us/op | 1.11 |
| OrderedSet add up to 128 items then delete last | 5.9531 us/op | 5.3098 us/op | 1.12 |
| Set add up to 128 items then delete middle | 3.9942 us/op | 3.7325 us/op | 1.07 |
| OrderedSet add up to 128 items then delete middle | 12.205 us/op | 11.408 us/op | 1.07 |
| Set add up to 256 items then delete first | 8.0619 us/op | 7.2593 us/op | 1.11 |
| OrderedSet add up to 256 items then delete first | 12.761 us/op | 12.096 us/op | 1.05 |
| Set add up to 256 items then delete last | 7.8097 us/op | 6.9856 us/op | 1.12 |
| OrderedSet add up to 256 items then delete last | 11.964 us/op | 11.065 us/op | 1.08 |
| Set add up to 256 items then delete middle | 7.8370 us/op | 7.0234 us/op | 1.12 |
| OrderedSet add up to 256 items then delete middle | 37.154 us/op | 34.499 us/op | 1.08 |
| runFastConfirmationRules vc:100000 bc:96 eq:0 | 5.0620 ms/op | 4.6283 ms/op | 1.09 |
| runFastConfirmationRules vc:600000 bc:96 eq:0 | 36.276 ms/op | 35.516 ms/op | 1.02 |
| runFastConfirmationRules vc:1000000 bc:96 eq:0 | 61.989 ms/op | 56.505 ms/op | 1.10 |
| runFastConfirmationRules vc:600000 bc:320 eq:0 | 36.104 ms/op | 33.927 ms/op | 1.06 |
| runFastConfirmationRules vc:100000 bc:96 eq:1000 | 1.4152 s/op | 1.3122 s/op | 1.08 |
| pass gossip attestations to forkchoice per slot | 2.7021 ms/op | 2.5764 ms/op | 1.05 |
| forkChoice updateHead vc 100000 bc 64 eq 0 | 424.28 us/op | 440.58 us/op | 0.96 |
| forkChoice updateHead vc 600000 bc 64 eq 0 | 2.5210 ms/op | 2.4923 ms/op | 1.01 |
| forkChoice updateHead vc 1000000 bc 64 eq 0 | 4.2277 ms/op | 4.3349 ms/op | 0.98 |
| forkChoice updateHead vc 600000 bc 320 eq 0 | 2.5673 ms/op | 2.6410 ms/op | 0.97 |
| forkChoice updateHead vc 600000 bc 1200 eq 0 | 2.6384 ms/op | 2.6848 ms/op | 0.98 |
| forkChoice updateHead vc 600000 bc 7200 eq 0 | 3.7422 ms/op | 3.0425 ms/op | 1.23 |
| forkChoice updateHead vc 600000 bc 64 eq 1000 | 2.6311 ms/op | 2.6816 ms/op | 0.98 |
| forkChoice updateHead vc 600000 bc 64 eq 10000 | 2.6480 ms/op | 2.7551 ms/op | 0.96 |
| forkChoice updateHead vc 600000 bc 64 eq 300000 | 6.9134 ms/op | 6.7169 ms/op | 1.03 |
| computeDeltas 1400000 validators 0% inactive | 12.678 ms/op | 13.101 ms/op | 0.97 |
| computeDeltas 1400000 validators 10% inactive | 11.959 ms/op | 12.623 ms/op | 0.95 |
| computeDeltas 1400000 validators 20% inactive | 11.272 ms/op | 11.813 ms/op | 0.95 |
| computeDeltas 1400000 validators 50% inactive | 9.1840 ms/op | 9.5150 ms/op | 0.97 |
| computeDeltas 2100000 validators 0% inactive | 19.049 ms/op | 19.618 ms/op | 0.97 |
| computeDeltas 2100000 validators 10% inactive | 18.174 ms/op | 18.990 ms/op | 0.96 |
| computeDeltas 2100000 validators 20% inactive | 17.198 ms/op | 18.049 ms/op | 0.95 |
| computeDeltas 2100000 validators 50% inactive | 11.342 ms/op | 14.344 ms/op | 0.79 |
| altair processAttestation - 250000 vs - 7PWei normalcase | 1.9456 ms/op | 1.6681 ms/op | 1.17 |
| altair processAttestation - 250000 vs - 7PWei worstcase | 3.8045 ms/op | 2.4355 ms/op | 1.56 |
| altair processAttestation - setStatus - 1/6 committees join | 111.56 us/op | 98.990 us/op | 1.13 |
| altair processAttestation - setStatus - 1/3 committees join | 219.91 us/op | 194.79 us/op | 1.13 |
| altair processAttestation - setStatus - 1/2 committees join | 296.54 us/op | 274.50 us/op | 1.08 |
| altair processAttestation - setStatus - 2/3 committees join | 395.20 us/op | 373.84 us/op | 1.06 |
| altair processAttestation - setStatus - 4/5 committees join | 544.97 us/op | 512.13 us/op | 1.06 |
| altair processAttestation - setStatus - 100% committees join | 632.13 us/op | 597.91 us/op | 1.06 |
| altair processBlock - 250000 vs - 7PWei normalcase | 3.8589 ms/op | 3.1148 ms/op | 1.24 |
| altair processBlock - 250000 vs - 7PWei normalcase hashState | 15.144 ms/op | 13.877 ms/op | 1.09 |
| altair processBlock - 250000 vs - 7PWei worstcase | 21.133 ms/op | 21.922 ms/op | 0.96 |
| altair processBlock - 250000 vs - 7PWei worstcase hashState | 47.700 ms/op | 40.926 ms/op | 1.17 |
| phase0 processBlock - 250000 vs - 7PWei normalcase | 1.5565 ms/op | 1.3009 ms/op | 1.20 |
| phase0 processBlock - 250000 vs - 7PWei worstcase | 20.083 ms/op | 16.021 ms/op | 1.25 |
| altair processEth1Data - 250000 vs - 7PWei normalcase | 311.01 us/op | 294.95 us/op | 1.05 |
| getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 | 4.7670 us/op | 3.2200 us/op | 1.48 |
| getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 | 21.393 us/op | 22.594 us/op | 0.95 |
| getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 | 6.3850 us/op | 5.9940 us/op | 1.07 |
| getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 | 4.3830 us/op | 3.9640 us/op | 1.11 |
| getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 | 98.033 us/op | 96.247 us/op | 1.02 |
| getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 | 1.4679 ms/op | 1.3919 ms/op | 1.05 |
| getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 | 1.9128 ms/op | 1.8527 ms/op | 1.03 |
| getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 | 1.9201 ms/op | 1.8429 ms/op | 1.04 |
| getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 | 3.9534 ms/op | 3.6709 ms/op | 1.08 |
| getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 | 2.3506 ms/op | 2.1789 ms/op | 1.08 |
| getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 | 7.2234 ms/op | 3.9853 ms/op | 1.81 |
| Tree 40 250000 create | 376.07 ms/op | 339.59 ms/op | 1.11 |
| Tree 40 250000 get(125000) | 94.990 ns/op | 100.02 ns/op | 0.95 |
| Tree 40 250000 set(125000) | 1.1095 us/op | 1.0243 us/op | 1.08 |
| Tree 40 250000 toArray() | 20.275 ms/op | 18.460 ms/op | 1.10 |
| Tree 40 250000 iterate all - toArray() + loop | 19.294 ms/op | 16.536 ms/op | 1.17 |
| Tree 40 250000 iterate all - get(i) | 42.760 ms/op | 38.086 ms/op | 1.12 |
| Array 250000 create | 2.7461 ms/op | 2.2881 ms/op | 1.20 |
| Array 250000 clone - spread | 849.53 us/op | 691.31 us/op | 1.23 |
| Array 250000 get(125000) | 0.30600 ns/op | 0.29400 ns/op | 1.04 |
| Array 250000 set(125000) | 0.31300 ns/op | 0.29400 ns/op | 1.06 |
| Array 250000 iterate all - loop | 59.990 us/op | 56.902 us/op | 1.05 |
| phase0 afterProcessEpoch - 250000 vs - 7PWei | 42.064 ms/op | 48.353 ms/op | 0.87 |
| Array.fill - length 1000000 | 2.2875 ms/op | 2.2771 ms/op | 1.00 |
| Array push - length 1000000 | 10.807 ms/op | 7.7597 ms/op | 1.39 |
| Array.get | 0.21360 ns/op | 0.20352 ns/op | 1.05 |
| Uint8Array.get | 0.23986 ns/op | 0.24689 ns/op | 0.97 |
| phase0 beforeProcessEpoch - 250000 vs - 7PWei | 18.720 ms/op | 11.621 ms/op | 1.61 |
| altair processEpoch - mainnet_e81889 | 356.99 ms/op | 240.57 ms/op | 1.48 |
| mainnet_e81889 - altair beforeProcessEpoch | 37.278 ms/op | 18.834 ms/op | 1.98 |
| mainnet_e81889 - altair processJustificationAndFinalization | 8.3760 us/op | 5.0840 us/op | 1.65 |
| mainnet_e81889 - altair processInactivityUpdates | 5.9119 ms/op | 3.7210 ms/op | 1.59 |
| mainnet_e81889 - altair processRewardsAndPenalties | 20.337 ms/op | 17.087 ms/op | 1.19 |
| mainnet_e81889 - altair processRegistryUpdates | 611.00 ns/op | 556.00 ns/op | 1.10 |
| mainnet_e81889 - altair processSlashings | 145.00 ns/op | 137.00 ns/op | 1.06 |
| mainnet_e81889 - altair processEth1DataReset | 145.00 ns/op | 137.00 ns/op | 1.06 |
| mainnet_e81889 - altair processEffectiveBalanceUpdates | 2.0374 ms/op | 1.1684 ms/op | 1.74 |
| mainnet_e81889 - altair processSlashingsReset | 724.00 ns/op | 693.00 ns/op | 1.04 |
| mainnet_e81889 - altair processRandaoMixesReset | 1.4170 us/op | 1.3420 us/op | 1.06 |
| mainnet_e81889 - altair processHistoricalRootsUpdate | 144.00 ns/op | 141.00 ns/op | 1.02 |
| mainnet_e81889 - altair processParticipationFlagUpdates | 485.00 ns/op | 447.00 ns/op | 1.09 |
| mainnet_e81889 - altair processSyncCommitteeUpdates | 119.00 ns/op | 118.00 ns/op | 1.01 |
| mainnet_e81889 - altair afterProcessEpoch | 43.524 ms/op | 42.228 ms/op | 1.03 |
| capella processEpoch - mainnet_e217614 | 1.0069 s/op | 716.08 ms/op | 1.41 |
| mainnet_e217614 - capella beforeProcessEpoch | 63.731 ms/op | 56.941 ms/op | 1.12 |
| mainnet_e217614 - capella processJustificationAndFinalization | 8.1700 us/op | 5.1850 us/op | 1.58 |
| mainnet_e217614 - capella processInactivityUpdates | 13.345 ms/op | 11.762 ms/op | 1.13 |
| mainnet_e217614 - capella processRewardsAndPenalties | 106.50 ms/op | 93.083 ms/op | 1.14 |
| mainnet_e217614 - capella processRegistryUpdates | 4.7370 us/op | 4.5220 us/op | 1.05 |
| mainnet_e217614 - capella processSlashings | 146.00 ns/op | 141.00 ns/op | 1.04 |
| mainnet_e217614 - capella processEth1DataReset | 142.00 ns/op | 135.00 ns/op | 1.05 |
| mainnet_e217614 - capella processEffectiveBalanceUpdates | 8.2315 ms/op | 5.2193 ms/op | 1.58 |
| mainnet_e217614 - capella processSlashingsReset | 704.00 ns/op | 677.00 ns/op | 1.04 |
| mainnet_e217614 - capella processRandaoMixesReset | 1.4410 us/op | 1.0840 us/op | 1.33 |
| mainnet_e217614 - capella processHistoricalRootsUpdate | 149.00 ns/op | 136.00 ns/op | 1.10 |
| mainnet_e217614 - capella processParticipationFlagUpdates | 471.00 ns/op | 445.00 ns/op | 1.06 |
| mainnet_e217614 - capella afterProcessEpoch | 111.28 ms/op | 109.14 ms/op | 1.02 |
| phase0 processEpoch - mainnet_e58758 | 389.91 ms/op | 251.76 ms/op | 1.55 |
| mainnet_e58758 - phase0 beforeProcessEpoch | 72.578 ms/op | 45.365 ms/op | 1.60 |
| mainnet_e58758 - phase0 processJustificationAndFinalization | 7.0850 us/op | 4.7920 us/op | 1.48 |
| mainnet_e58758 - phase0 processRewardsAndPenalties | 16.997 ms/op | 15.323 ms/op | 1.11 |
| mainnet_e58758 - phase0 processRegistryUpdates | 2.4120 us/op | 2.2450 us/op | 1.07 |
| mainnet_e58758 - phase0 processSlashings | 157.00 ns/op | 136.00 ns/op | 1.15 |
| mainnet_e58758 - phase0 processEth1DataReset | 155.00 ns/op | 135.00 ns/op | 1.15 |
| mainnet_e58758 - phase0 processEffectiveBalanceUpdates | 849.07 us/op | 1.0109 ms/op | 0.84 |
| mainnet_e58758 - phase0 processSlashingsReset | 918.00 ns/op | 807.00 ns/op | 1.14 |
| mainnet_e58758 - phase0 processRandaoMixesReset | 1.5010 us/op | 1.0070 us/op | 1.49 |
| mainnet_e58758 - phase0 processHistoricalRootsUpdate | 262.00 ns/op | 136.00 ns/op | 1.93 |
| mainnet_e58758 - phase0 processParticipationRecordUpdates | 1.3180 us/op | 974.00 ns/op | 1.35 |
| mainnet_e58758 - phase0 afterProcessEpoch | 35.270 ms/op | 32.956 ms/op | 1.07 |
| phase0 processEffectiveBalanceUpdates - 250000 normalcase | 1.0852 ms/op | 1.0056 ms/op | 1.08 |
| phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 | 1.6135 ms/op | 1.6175 ms/op | 1.00 |
| altair processInactivityUpdates - 250000 normalcase | 11.230 ms/op | 10.363 ms/op | 1.08 |
| altair processInactivityUpdates - 250000 worstcase | 11.810 ms/op | 10.327 ms/op | 1.14 |
| phase0 processRegistryUpdates - 250000 normalcase | 2.3720 us/op | 2.3800 us/op | 1.00 |
| phase0 processRegistryUpdates - 250000 badcase_full_deposits | 144.05 us/op | 146.84 us/op | 0.98 |
| phase0 processRegistryUpdates - 250000 worstcase 0.5 | 74.882 ms/op | 58.651 ms/op | 1.28 |
| altair processRewardsAndPenalties - 250000 normalcase | 16.036 ms/op | 14.345 ms/op | 1.12 |
| altair processRewardsAndPenalties - 250000 worstcase | 15.974 ms/op | 14.060 ms/op | 1.14 |
| phase0 getAttestationDeltas - 250000 normalcase | 5.7164 ms/op | 5.4425 ms/op | 1.05 |
| phase0 getAttestationDeltas - 250000 worstcase | 5.8020 ms/op | 5.4401 ms/op | 1.07 |
| phase0 processSlashings - 250000 worstcase | 63.661 us/op | 62.018 us/op | 1.03 |
| altair processSyncCommitteeUpdates - 250000 | 12.813 ms/op | 9.9483 ms/op | 1.29 |
| BeaconState.hashTreeRoot - No change | 185.00 ns/op | 168.00 ns/op | 1.10 |
| BeaconState.hashTreeRoot - 1 full validator | 81.829 us/op | 59.262 us/op | 1.38 |
| BeaconState.hashTreeRoot - 32 full validator | 1.0340 ms/op | 668.50 us/op | 1.55 |
| BeaconState.hashTreeRoot - 512 full validator | 7.4813 ms/op | 6.4278 ms/op | 1.16 |
| BeaconState.hashTreeRoot - 1 validator.effectiveBalance | 98.095 us/op | 73.867 us/op | 1.33 |
| BeaconState.hashTreeRoot - 32 validator.effectiveBalance | 1.5437 ms/op | 1.0805 ms/op | 1.43 |
| BeaconState.hashTreeRoot - 512 validator.effectiveBalance | 22.779 ms/op | 13.309 ms/op | 1.71 |
| BeaconState.hashTreeRoot - 1 balances | 82.111 us/op | 59.609 us/op | 1.38 |
| BeaconState.hashTreeRoot - 32 balances | 822.70 us/op | 572.60 us/op | 1.44 |
| BeaconState.hashTreeRoot - 512 balances | 6.1958 ms/op | 4.9639 ms/op | 1.25 |
| BeaconState.hashTreeRoot - 250000 balances | 146.63 ms/op | 115.59 ms/op | 1.27 |
| aggregationBits - 2048 els - zipIndexesInBitList | 19.255 us/op | 19.925 us/op | 0.97 |
| regular array get 100000 times | 22.669 us/op | 22.919 us/op | 0.99 |
| wrappedArray get 100000 times | 22.712 us/op | 22.812 us/op | 1.00 |
| arrayWithProxy get 100000 times | 9.3460 ms/op | 10.801 ms/op | 0.87 |
| ssz.Root.equals | 21.254 ns/op | 21.462 ns/op | 0.99 |
| byteArrayEquals | 21.029 ns/op | 21.268 ns/op | 0.99 |
| Buffer.compare | 8.7520 ns/op | 8.8890 ns/op | 0.98 |
| processSlot - 1 slots | 9.3940 us/op | 7.9580 us/op | 1.18 |
| processSlot - 32 slots | 2.3751 ms/op | 1.5512 ms/op | 1.53 |
| getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei | 4.7245 ms/op | 2.7305 ms/op | 1.73 |
| getCommitteeAssignments - req 1 vs - 250000 vc | 1.7241 ms/op | 1.6610 ms/op | 1.04 |
| getCommitteeAssignments - req 100 vs - 250000 vc | 3.5340 ms/op | 3.3854 ms/op | 1.04 |
| getCommitteeAssignments - req 1000 vs - 250000 vc | 3.7827 ms/op | 3.6297 ms/op | 1.04 |
| findModifiedValidators - 10000 modified validators | 893.85 ms/op | 755.06 ms/op | 1.18 |
| findModifiedValidators - 1000 modified validators | 519.91 ms/op | 534.45 ms/op | 0.97 |
| findModifiedValidators - 100 modified validators | 268.46 ms/op | 273.28 ms/op | 0.98 |
| findModifiedValidators - 10 modified validators | 164.10 ms/op | 249.96 ms/op | 0.66 |
| findModifiedValidators - 1 modified validators | 177.05 ms/op | 172.70 ms/op | 1.03 |
| findModifiedValidators - no difference | 145.72 ms/op | 146.58 ms/op | 0.99 |
| migrate state 1500000 validators, 3400 modified, 2000 new | 4.1250 s/op | 2.6970 s/op | 1.53 |
| RootCache.getBlockRootAtSlot - 250000 vs - 7PWei | 3.5600 ns/op | 3.7400 ns/op | 0.95 |
| state getBlockRootAtSlot - 250000 vs - 7PWei | 437.79 ns/op | 281.27 ns/op | 1.56 |
| computeProposerIndex 100000 validators | 1.3718 ms/op | 1.3382 ms/op | 1.03 |
| getNextSyncCommitteeIndices 1000 validators | 2.9025 ms/op | 2.8411 ms/op | 1.02 |
| getNextSyncCommitteeIndices 10000 validators | 26.205 ms/op | 25.280 ms/op | 1.04 |
| getNextSyncCommitteeIndices 100000 validators | 95.982 ms/op | 84.642 ms/op | 1.13 |
| computeProposers - vc 250000 | 582.59 us/op | 552.15 us/op | 1.06 |
| computeEpochShuffling - vc 250000 | 41.548 ms/op | 40.934 ms/op | 1.01 |
| getNextSyncCommittee - vc 250000 | 11.466 ms/op | 9.4627 ms/op | 1.21 |
| nodejs block root to RootHex using toHex | 99.958 ns/op | 99.706 ns/op | 1.00 |
| nodejs block root to RootHex using toRootHex | 64.137 ns/op | 65.398 ns/op | 0.98 |
| nodejs fromHex(blob) | 962.53 us/op | 729.74 us/op | 1.32 |
| nodejs fromHexInto(blob) | 651.49 us/op | 655.74 us/op | 0.99 |
| nodejs block root to RootHex using the deprecated toHexString | 494.75 ns/op | 486.80 ns/op | 1.02 |
| nodejs byteArrayEquals 32 bytes (block root) | 26.835 ns/op | 25.861 ns/op | 1.04 |
| nodejs byteArrayEquals 48 bytes (pubkey) | 38.781 ns/op | 37.351 ns/op | 1.04 |
| nodejs byteArrayEquals 96 bytes (signature) | 36.883 ns/op | 39.596 ns/op | 0.93 |
| nodejs byteArrayEquals 1024 bytes | 45.008 ns/op | 43.597 ns/op | 1.03 |
| nodejs byteArrayEquals 131072 bytes (blob) | 1.8161 us/op | 1.7686 us/op | 1.03 |
| browser block root to RootHex using toHex | 148.73 ns/op | 145.00 ns/op | 1.03 |
| browser block root to RootHex using toRootHex | 135.02 ns/op | 130.83 ns/op | 1.03 |
| browser fromHex(blob) | 1.8865 ms/op | 1.5697 ms/op | 1.20 |
| browser fromHexInto(blob) | 650.81 us/op | 661.21 us/op | 0.98 |
| browser block root to RootHex using the deprecated toHexString | 342.35 ns/op | 347.84 ns/op | 0.98 |
| browser byteArrayEquals 32 bytes (block root) | 28.879 ns/op | 27.653 ns/op | 1.04 |
| browser byteArrayEquals 48 bytes (pubkey) | 40.607 ns/op | 39.042 ns/op | 1.04 |
| browser byteArrayEquals 96 bytes (signature) | 75.886 ns/op | 72.868 ns/op | 1.04 |
| browser byteArrayEquals 1024 bytes | 772.12 ns/op | 745.96 ns/op | 1.04 |
| browser byteArrayEquals 131072 bytes (blob) | 97.261 us/op | 93.823 us/op | 1.04 |
by benchmarkbot/action
| store.bestValidUpdates.set(updateSignaturePeriod, { | ||
| store: ILightClientStore | ||
| ): ILightClientStore { | ||
| for (const [period, bestValidUpdate] of store.bestValidUpdates) { |
There was a problem hiding this comment.
so this change is because we cache up to MAX_SYNC_PERIODS_CACHE (= 2) of bestValidUpdate. The concern is if the store is upgraded but an old cached update remains in a pre-gloas state, causing the headers to have inconsistent formats
There was a problem hiding this comment.
Yes, that matches my read. Since bestValidUpdates can keep the previous sync periods across a store upgrade, normalizing the cached updates during upgradeLightClientStore() is the right place to prevent mixed pre-Gloas/Gloas header shapes from leaking into later summaries.
|
@lodekeeper review |
lodekeeper
left a comment
There was a problem hiding this comment.
Reviewed the current head. I found one issue in the sync spec runner's fork handling.
| @@ -166,52 +205,64 @@ export const sync: TestRunnerFn<SyncTestCase, void> = (fork) => { | |||
| }, | |||
| sszTypes: { | |||
| bootstrap: isForkPostAltair(fork) ? sszTypesFor(fork).LightClientBootstrap : ssz.altair.LightClientBootstrap, | |||
There was a problem hiding this comment.
The runner still deserializes bootstrap.ssz_snappy with the suite/path fork before testFunction can inspect meta.bootstrap_fork_digest. The sync test format makes bootstrap_fork_digest the fork context for the bootstrap SSZ type, and then the bootstrap can be upgraded to store_fork_version. With this shape, a Gloas sync vector whose trusted bootstrap is pre-Gloas can fail during fixture loading, or make the vector depend on Lodestar's directory fork instead of the metadata.
Can we handle bootstrap like updates here: load it as raw bytes, resolve bootstrapFork from meta.bootstrap_fork_digest, deserialize with sszTypesFor(bootstrapFork).LightClientBootstrap, then upgrade to storeFork if needed?
There was a problem hiding this comment.
Confirmed on current head. 24ae721c changes bootstrap to load as raw bytes, resolves bootstrapFork from meta.bootstrap_fork_digest, deserializes with sszTypesFor(bootstrapFork).LightClientBootstrap, and only then upgrades to storeFork when needed. Thanks, this addresses my comment.
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## unstable #9687 +/- ##
============================================
+ Coverage 52.59% 52.60% +0.01%
============================================
Files 848 848
Lines 60075 60058 -17
Branches 4426 4426
============================================
- Hits 31594 31593 -1
+ Misses 28422 28406 -16
Partials 59 59 🚀 New features to boost your workflow:
|
Motivation
Implements the gloas light client spec
Supports:
Description
signedExecutionPayloadBid.message.parentBlockHash.getBlockBodyExecutionBlockHashProof)getLcExecutionRootfor comparing execution data across pre-Gloas and Gloas header representations. Note that we used to do this in-line before but here we introducegetLcExecutionRootso that we can reuse in sync tests too + there's some added complexity to it.original SSZ format when a pre-Gloas header is signed after the Gloas boundary.
For tests,
AI Assistance Disclosure
Planning, implementation and testing done with gpt-sol-5.6 medium