Skip to content

feat: model BeaconStateView#8773

Merged
nflaig merged 17 commits intounstablefrom
te/beacon_state_view_2
Feb 23, 2026
Merged

feat: model BeaconStateView#8773
nflaig merged 17 commits intounstablefrom
te/beacon_state_view_2

Conversation

@twoeths
Copy link
Contributor

@twoeths twoeths commented Jan 22, 2026

Motivation

Description

  • implement IBeaconStateView interface
  • implement the ts BeaconStateView

blocked by #8728 to build successfully

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @twoeths, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a significant architectural improvement by implementing a BeaconStateView and its corresponding interface. This new abstraction layer encapsulates the complex BeaconState object, offering a simplified, read-only perspective that enhances code clarity and maintainability. By migrating existing state-accessing logic to this view, the PR aims to enforce better data encapsulation and prepare the codebase for future evolutions, particularly in areas like light client synchronization and state proof generation.

Highlights

  • Introduced BeaconStateView: A new class BeaconStateView and its interface IBeaconStateView have been added to provide a read-only, abstracted layer over the CachedBeaconStateAllForks. This promotes immutability and a cleaner API for state access.
  • Refactored State Access: Existing modules like processAttestationsAltair.ts, upgradeStateToAltair.ts, rootCache.ts, and shufflingDecisionRoot.ts have been updated to utilize the new IBeaconStateView interface, centralizing state interaction.
  • Light Client Proof Utilities: New files lightClient/proofs.ts and lightClient/types.ts were introduced to facilitate the generation of sync committee witnesses and proofs for various state components, crucial for light client functionality.
  • Type Enhancements: The capella/types.ts file now exports HistoricalSummaries, and validatorStatus.ts includes a new GeneralValidatorStatus type and a utility function mapToGeneralStatus for broader validator status categorization.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
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 introduces the BeaconStateView interface and its implementation, providing a read-only facade over the CachedBeaconStateAllForks. This is a positive step towards encapsulating state access logic and improving maintainability. The changes also include refactoring RootCache and proposerShufflingDecisionRoot to utilize this new view, ensuring consistency. New types and utility functions for light client proofs and validator status mapping have also been added. Overall, the changes are well-structured and align with the goal of abstracting state access. There are a few areas identified for potential improvement regarding robustness and efficiency.

Comment on lines +21 to +25
const n2 = n1.left;
const n5 = n2.right;
const n10 = n5.left;
const n21 = n10.right;
const n43 = n21.right;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The direct access to n1.left, n2.right, etc., in getSyncCommitteesWitness relies heavily on the internal structure of the Tree and node properties. This makes the code brittle to future changes in the SSZ tree structure. If the SSZ structure is modified, these direct accesses could break silently or produce incorrect proofs, which is a critical correctness issue for light client proofs. It would be more robust to use explicit GINDEXes or a more abstract way to navigate the tree if available, or at least add comments explaining the gindex for each node access to make it easier to maintain.

Comment on lines +331 to +340
getValidatorsByStatus(statuses: Set<string>, currentEpoch: Epoch): phase0.Validator[] {
const validators: phase0.Validator[] = [];
const validatorsArr = this.cachedState.validators.getAllReadonlyValues();

for (const validator of validatorsArr) {
const validatorStatus = getValidatorStatus(validator, currentEpoch);
if (statuses.has(validatorStatus) || statuses.has(mapToGeneralStatus(validatorStatus))) {
validators.push(validator);
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The getValidatorsByStatus method creates a new array validatorsArr by calling this.cachedState.validators.getAllReadonlyValues() on every invocation. If this method is called frequently and the validator list is large, this could lead to unnecessary memory allocations and performance overhead. Consider iterating directly over the cachedState.validators view or caching the getAllReadonlyValues() result if appropriate to improve efficiency.

@twoeths twoeths force-pushed the te/beacon_state_view_2 branch from 9bf8e0f to 6884d19 Compare January 26, 2026 07:14
@github-actions
Copy link
Contributor

github-actions bot commented Jan 26, 2026

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: caad545 Previous: 49f680d Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 1.2091 ms/op 1.3041 ms/op 0.93
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 38.982 us/op 40.024 us/op 0.97
BLS verify - blst 1.4371 ms/op 920.93 us/op 1.56
BLS verifyMultipleSignatures 3 - blst 1.3658 ms/op 1.2784 ms/op 1.07
BLS verifyMultipleSignatures 8 - blst 1.9202 ms/op 2.4736 ms/op 0.78
BLS verifyMultipleSignatures 32 - blst 7.2741 ms/op 5.1241 ms/op 1.42
BLS verifyMultipleSignatures 64 - blst 10.912 ms/op 9.4430 ms/op 1.16
BLS verifyMultipleSignatures 128 - blst 17.363 ms/op 17.910 ms/op 0.97
BLS deserializing 10000 signatures 689.77 ms/op 719.93 ms/op 0.96
BLS deserializing 100000 signatures 6.8936 s/op 6.9256 s/op 1.00
BLS verifyMultipleSignatures - same message - 3 - blst 863.48 us/op 819.48 us/op 1.05
BLS verifyMultipleSignatures - same message - 8 - blst 1.0026 ms/op 983.80 us/op 1.02
BLS verifyMultipleSignatures - same message - 32 - blst 1.6552 ms/op 1.6788 ms/op 0.99
BLS verifyMultipleSignatures - same message - 64 - blst 2.5398 ms/op 2.5440 ms/op 1.00
BLS verifyMultipleSignatures - same message - 128 - blst 4.3679 ms/op 4.2728 ms/op 1.02
BLS aggregatePubkeys 32 - blst 19.376 us/op 19.134 us/op 1.01
BLS aggregatePubkeys 128 - blst 69.213 us/op 68.324 us/op 1.01
getSlashingsAndExits - default max 74.230 us/op 66.371 us/op 1.12
getSlashingsAndExits - 2k 334.68 us/op 306.36 us/op 1.09
isKnown best case - 1 super set check 203.00 ns/op 193.00 ns/op 1.05
isKnown normal case - 2 super set checks 196.00 ns/op 186.00 ns/op 1.05
isKnown worse case - 16 super set checks 206.00 ns/op 193.00 ns/op 1.07
validate api signedAggregateAndProof - struct 1.4787 ms/op 1.3414 ms/op 1.10
validate gossip signedAggregateAndProof - struct 1.4753 ms/op 1.3436 ms/op 1.10
batch validate gossip attestation - vc 640000 - chunk 32 115.97 us/op 118.23 us/op 0.98
batch validate gossip attestation - vc 640000 - chunk 64 103.06 us/op 104.60 us/op 0.99
batch validate gossip attestation - vc 640000 - chunk 128 105.88 us/op 93.527 us/op 1.13
batch validate gossip attestation - vc 640000 - chunk 256 94.522 us/op 93.642 us/op 1.01
bytes32 toHexString 412.00 ns/op 356.00 ns/op 1.16
bytes32 Buffer.toString(hex) 249.00 ns/op 235.00 ns/op 1.06
bytes32 Buffer.toString(hex) from Uint8Array 343.00 ns/op 315.00 ns/op 1.09
bytes32 Buffer.toString(hex) + 0x 251.00 ns/op 238.00 ns/op 1.05
Return object 10000 times 0.23620 ns/op 0.23050 ns/op 1.02
Throw Error 10000 times 4.3684 us/op 3.9949 us/op 1.09
toHex 152.68 ns/op 129.80 ns/op 1.18
Buffer.from 142.71 ns/op 122.28 ns/op 1.17
shared Buffer 96.084 ns/op 77.561 ns/op 1.24
fastMsgIdFn sha256 / 200 bytes 1.8990 us/op 1.8210 us/op 1.04
fastMsgIdFn h32 xxhash / 200 bytes 195.00 ns/op 186.00 ns/op 1.05
fastMsgIdFn h64 xxhash / 200 bytes 262.00 ns/op 259.00 ns/op 1.01
fastMsgIdFn sha256 / 1000 bytes 6.1210 us/op 5.7780 us/op 1.06
fastMsgIdFn h32 xxhash / 1000 bytes 287.00 ns/op 277.00 ns/op 1.04
fastMsgIdFn h64 xxhash / 1000 bytes 318.00 ns/op 317.00 ns/op 1.00
fastMsgIdFn sha256 / 10000 bytes 53.244 us/op 52.390 us/op 1.02
fastMsgIdFn h32 xxhash / 10000 bytes 1.4160 us/op 1.4060 us/op 1.01
fastMsgIdFn h64 xxhash / 10000 bytes 1.0160 us/op 941.00 ns/op 1.08
send data - 1000 256B messages 14.759 ms/op 13.163 ms/op 1.12
send data - 1000 512B messages 16.145 ms/op 16.292 ms/op 0.99
send data - 1000 1024B messages 23.120 ms/op 22.145 ms/op 1.04
send data - 1000 1200B messages 24.142 ms/op 22.720 ms/op 1.06
send data - 1000 2048B messages 25.559 ms/op 21.205 ms/op 1.21
send data - 1000 4096B messages 29.072 ms/op 24.148 ms/op 1.20
send data - 1000 16384B messages 142.25 ms/op 118.94 ms/op 1.20
send data - 1000 65536B messages 318.19 ms/op 323.72 ms/op 0.98
enrSubnets - fastDeserialize 64 bits 892.00 ns/op 2.1300 us/op 0.42
enrSubnets - ssz BitVector 64 bits 363.00 ns/op 340.00 ns/op 1.07
enrSubnets - fastDeserialize 4 bits 146.00 ns/op 126.00 ns/op 1.16
enrSubnets - ssz BitVector 4 bits 354.00 ns/op 339.00 ns/op 1.04
prioritizePeers score -10:0 att 32-0.1 sync 2-0 261.07 us/op 232.77 us/op 1.12
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 291.48 us/op 255.83 us/op 1.14
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 392.63 us/op 371.16 us/op 1.06
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 780.07 us/op 700.09 us/op 1.11
prioritizePeers score 0:0 att 64-1 sync 4-1 952.51 us/op 868.56 us/op 1.10
array of 16000 items push then shift 1.6479 us/op 1.5596 us/op 1.06
LinkedList of 16000 items push then shift 7.6140 ns/op 7.1540 ns/op 1.06
array of 16000 items push then pop 77.013 ns/op 74.542 ns/op 1.03
LinkedList of 16000 items push then pop 7.2810 ns/op 6.9830 ns/op 1.04
array of 24000 items push then shift 2.3872 us/op 2.2779 us/op 1.05
LinkedList of 24000 items push then shift 7.6270 ns/op 7.2220 ns/op 1.06
array of 24000 items push then pop 108.25 ns/op 101.34 ns/op 1.07
LinkedList of 24000 items push then pop 7.5370 ns/op 6.9430 ns/op 1.09
intersect bitArray bitLen 8 5.7790 ns/op 5.4810 ns/op 1.05
intersect array and set length 8 33.848 ns/op 33.383 ns/op 1.01
intersect bitArray bitLen 128 28.937 ns/op 25.991 ns/op 1.11
intersect array and set length 128 553.97 ns/op 527.49 ns/op 1.05
bitArray.getTrueBitIndexes() bitLen 128 1.2590 us/op 1.0080 us/op 1.25
bitArray.getTrueBitIndexes() bitLen 248 1.9060 us/op 1.7940 us/op 1.06
bitArray.getTrueBitIndexes() bitLen 512 3.9510 us/op 3.6430 us/op 1.08
Full columns - reconstruct all 6 blobs 312.63 us/op 271.00 us/op 1.15
Full columns - reconstruct half of the blobs out of 6 125.15 us/op 100.78 us/op 1.24
Full columns - reconstruct single blob out of 6 33.690 us/op 29.514 us/op 1.14
Half columns - reconstruct all 6 blobs 333.45 ms/op 261.63 ms/op 1.27
Half columns - reconstruct half of the blobs out of 6 138.80 ms/op 131.87 ms/op 1.05
Half columns - reconstruct single blob out of 6 52.221 ms/op 47.708 ms/op 1.09
Full columns - reconstruct all 10 blobs 449.53 us/op 316.87 us/op 1.42
Full columns - reconstruct half of the blobs out of 10 189.55 us/op 158.24 us/op 1.20
Full columns - reconstruct single blob out of 10 51.847 us/op 31.429 us/op 1.65
Half columns - reconstruct all 10 blobs 475.07 ms/op 429.80 ms/op 1.11
Half columns - reconstruct half of the blobs out of 10 245.35 ms/op 219.44 ms/op 1.12
Half columns - reconstruct single blob out of 10 52.042 ms/op 47.897 ms/op 1.09
Full columns - reconstruct all 20 blobs 755.65 us/op 565.17 us/op 1.34
Full columns - reconstruct half of the blobs out of 20 366.72 us/op 282.81 us/op 1.30
Full columns - reconstruct single blob out of 20 31.209 us/op 30.388 us/op 1.03
Half columns - reconstruct all 20 blobs 929.27 ms/op 860.20 ms/op 1.08
Half columns - reconstruct half of the blobs out of 20 461.73 ms/op 433.23 ms/op 1.07
Half columns - reconstruct single blob out of 20 54.476 ms/op 47.531 ms/op 1.15
Set add up to 64 items then delete first 2.0931 us/op 2.0416 us/op 1.03
OrderedSet add up to 64 items then delete first 3.0992 us/op 2.9384 us/op 1.05
Set add up to 64 items then delete last 2.3696 us/op 2.2098 us/op 1.07
OrderedSet add up to 64 items then delete last 3.4812 us/op 3.2664 us/op 1.07
Set add up to 64 items then delete middle 2.3646 us/op 3.3509 us/op 0.71
OrderedSet add up to 64 items then delete middle 5.0221 us/op 4.8956 us/op 1.03
Set add up to 128 items then delete first 4.8142 us/op 4.8219 us/op 1.00
OrderedSet add up to 128 items then delete first 7.1172 us/op 7.1175 us/op 1.00
Set add up to 128 items then delete last 4.8149 us/op 4.6578 us/op 1.03
OrderedSet add up to 128 items then delete last 6.9422 us/op 6.6677 us/op 1.04
Set add up to 128 items then delete middle 4.6156 us/op 4.5184 us/op 1.02
OrderedSet add up to 128 items then delete middle 14.011 us/op 13.069 us/op 1.07
Set add up to 256 items then delete first 10.690 us/op 10.004 us/op 1.07
OrderedSet add up to 256 items then delete first 15.033 us/op 15.703 us/op 0.96
Set add up to 256 items then delete last 9.8543 us/op 9.0561 us/op 1.09
OrderedSet add up to 256 items then delete last 15.517 us/op 13.745 us/op 1.13
Set add up to 256 items then delete middle 9.6651 us/op 9.1476 us/op 1.06
OrderedSet add up to 256 items then delete middle 42.058 us/op 41.312 us/op 1.02
pass gossip attestations to forkchoice per slot 2.7495 ms/op 2.5528 ms/op 1.08
forkChoice updateHead vc 100000 bc 64 eq 0 512.51 us/op 505.56 us/op 1.01
forkChoice updateHead vc 600000 bc 64 eq 0 3.1547 ms/op 3.0097 ms/op 1.05
forkChoice updateHead vc 1000000 bc 64 eq 0 5.2682 ms/op 4.9889 ms/op 1.06
forkChoice updateHead vc 600000 bc 320 eq 0 3.1471 ms/op 2.9886 ms/op 1.05
forkChoice updateHead vc 600000 bc 1200 eq 0 3.1198 ms/op 3.0184 ms/op 1.03
forkChoice updateHead vc 600000 bc 7200 eq 0 3.6230 ms/op 3.2785 ms/op 1.11
forkChoice updateHead vc 600000 bc 64 eq 1000 3.4946 ms/op 3.3331 ms/op 1.05
forkChoice updateHead vc 600000 bc 64 eq 10000 3.7415 ms/op 3.4805 ms/op 1.07
forkChoice updateHead vc 600000 bc 64 eq 300000 9.6861 ms/op 8.9988 ms/op 1.08
computeDeltas 1400000 validators 0% inactive 15.342 ms/op 14.327 ms/op 1.07
computeDeltas 1400000 validators 10% inactive 14.201 ms/op 13.427 ms/op 1.06
computeDeltas 1400000 validators 20% inactive 13.165 ms/op 12.551 ms/op 1.05
computeDeltas 1400000 validators 50% inactive 11.256 ms/op 9.8506 ms/op 1.14
computeDeltas 2100000 validators 0% inactive 26.427 ms/op 21.540 ms/op 1.23
computeDeltas 2100000 validators 10% inactive 21.519 ms/op 20.183 ms/op 1.07
computeDeltas 2100000 validators 20% inactive 20.068 ms/op 18.834 ms/op 1.07
computeDeltas 2100000 validators 50% inactive 15.962 ms/op 14.788 ms/op 1.08
altair processAttestation - 250000 vs - 7PWei normalcase 1.9109 ms/op 1.8888 ms/op 1.01
altair processAttestation - 250000 vs - 7PWei worstcase 2.9984 ms/op 2.7720 ms/op 1.08
altair processAttestation - setStatus - 1/6 committees join 116.78 us/op 119.85 us/op 0.97
altair processAttestation - setStatus - 1/3 committees join 225.94 us/op 234.28 us/op 0.96
altair processAttestation - setStatus - 1/2 committees join 323.41 us/op 326.94 us/op 0.99
altair processAttestation - setStatus - 2/3 committees join 415.25 us/op 415.56 us/op 1.00
altair processAttestation - setStatus - 4/5 committees join 591.51 us/op 580.42 us/op 1.02
altair processAttestation - setStatus - 100% committees join 706.23 us/op 678.71 us/op 1.04
altair processBlock - 250000 vs - 7PWei normalcase 5.0463 ms/op 3.5147 ms/op 1.44
altair processBlock - 250000 vs - 7PWei normalcase hashState 23.585 ms/op 16.078 ms/op 1.47
altair processBlock - 250000 vs - 7PWei worstcase 31.068 ms/op 22.522 ms/op 1.38
altair processBlock - 250000 vs - 7PWei worstcase hashState 66.625 ms/op 53.856 ms/op 1.24
phase0 processBlock - 250000 vs - 7PWei normalcase 1.6863 ms/op 1.7618 ms/op 0.96
phase0 processBlock - 250000 vs - 7PWei worstcase 24.068 ms/op 18.662 ms/op 1.29
altair processEth1Data - 250000 vs - 7PWei normalcase 418.10 us/op 359.10 us/op 1.16
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 8.9700 us/op 5.7130 us/op 1.57
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 59.301 us/op 38.433 us/op 1.54
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 18.822 us/op 10.823 us/op 1.74
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 14.665 us/op 7.0870 us/op 2.07
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 246.74 us/op 149.91 us/op 1.65
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.9753 ms/op 1.8831 ms/op 1.05
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 2.6518 ms/op 2.2902 ms/op 1.16
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 2.4044 ms/op 2.3157 ms/op 1.04
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 5.1701 ms/op 4.7091 ms/op 1.10
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 2.9467 ms/op 2.6525 ms/op 1.11
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 6.0752 ms/op 4.8228 ms/op 1.26
Tree 40 250000 create 397.51 ms/op 355.16 ms/op 1.12
Tree 40 250000 get(125000) 133.23 ns/op 126.29 ns/op 1.05
Tree 40 250000 set(125000) 1.3220 us/op 1.2423 us/op 1.06
Tree 40 250000 toArray() 17.071 ms/op 12.935 ms/op 1.32
Tree 40 250000 iterate all - toArray() + loop 17.497 ms/op 17.873 ms/op 0.98
Tree 40 250000 iterate all - get(i) 52.237 ms/op 44.297 ms/op 1.18
Array 250000 create 2.5220 ms/op 2.5483 ms/op 0.99
Array 250000 clone - spread 861.38 us/op 863.46 us/op 1.00
Array 250000 get(125000) 0.38100 ns/op 0.35400 ns/op 1.08
Array 250000 set(125000) 0.37800 ns/op 0.35600 ns/op 1.06
Array 250000 iterate all - loop 61.737 us/op 62.613 us/op 0.99
phase0 afterProcessEpoch - 250000 vs - 7PWei 43.577 ms/op 41.210 ms/op 1.06
Array.fill - length 1000000 3.0111 ms/op 3.0579 ms/op 0.98
Array push - length 1000000 11.750 ms/op 10.104 ms/op 1.16
Array.get 0.22224 ns/op 0.21846 ns/op 1.02
Uint8Array.get 0.22658 ns/op 0.22217 ns/op 1.02
phase0 beforeProcessEpoch - 250000 vs - 7PWei 14.312 ms/op 14.309 ms/op 1.00
altair processEpoch - mainnet_e81889 268.08 ms/op 256.93 ms/op 1.04
mainnet_e81889 - altair beforeProcessEpoch 18.055 ms/op 17.329 ms/op 1.04
mainnet_e81889 - altair processJustificationAndFinalization 5.7470 us/op 5.6270 us/op 1.02
mainnet_e81889 - altair processInactivityUpdates 3.8063 ms/op 3.7621 ms/op 1.01
mainnet_e81889 - altair processRewardsAndPenalties 19.234 ms/op 18.667 ms/op 1.03
mainnet_e81889 - altair processRegistryUpdates 639.00 ns/op 624.00 ns/op 1.02
mainnet_e81889 - altair processSlashings 216.00 ns/op 166.00 ns/op 1.30
mainnet_e81889 - altair processEth1DataReset 167.00 ns/op 164.00 ns/op 1.02
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.5157 ms/op 3.2080 ms/op 0.47
mainnet_e81889 - altair processSlashingsReset 793.00 ns/op 815.00 ns/op 0.97
mainnet_e81889 - altair processRandaoMixesReset 1.0400 us/op 1.0600 us/op 0.98
mainnet_e81889 - altair processHistoricalRootsUpdate 190.00 ns/op 164.00 ns/op 1.16
mainnet_e81889 - altair processParticipationFlagUpdates 536.00 ns/op 493.00 ns/op 1.09
mainnet_e81889 - altair processSyncCommitteeUpdates 144.00 ns/op 125.00 ns/op 1.15
mainnet_e81889 - altair afterProcessEpoch 46.232 ms/op 43.513 ms/op 1.06
capella processEpoch - mainnet_e217614 874.51 ms/op 763.22 ms/op 1.15
mainnet_e217614 - capella beforeProcessEpoch 85.901 ms/op 65.508 ms/op 1.31
mainnet_e217614 - capella processJustificationAndFinalization 6.0380 us/op 5.6320 us/op 1.07
mainnet_e217614 - capella processInactivityUpdates 17.712 ms/op 18.246 ms/op 0.97
mainnet_e217614 - capella processRewardsAndPenalties 118.31 ms/op 96.802 ms/op 1.22
mainnet_e217614 - capella processRegistryUpdates 5.8860 us/op 5.7060 us/op 1.03
mainnet_e217614 - capella processSlashings 193.00 ns/op 162.00 ns/op 1.19
mainnet_e217614 - capella processEth1DataReset 249.00 ns/op 160.00 ns/op 1.56
mainnet_e217614 - capella processEffectiveBalanceUpdates 25.408 ms/op 13.201 ms/op 1.92
mainnet_e217614 - capella processSlashingsReset 939.00 ns/op 789.00 ns/op 1.19
mainnet_e217614 - capella processRandaoMixesReset 1.3170 us/op 1.0680 us/op 1.23
mainnet_e217614 - capella processHistoricalRootsUpdate 184.00 ns/op 164.00 ns/op 1.12
mainnet_e217614 - capella processParticipationFlagUpdates 642.00 ns/op 494.00 ns/op 1.30
mainnet_e217614 - capella afterProcessEpoch 119.37 ms/op 110.42 ms/op 1.08
phase0 processEpoch - mainnet_e58758 257.61 ms/op 237.13 ms/op 1.09
mainnet_e58758 - phase0 beforeProcessEpoch 62.137 ms/op 45.767 ms/op 1.36
mainnet_e58758 - phase0 processJustificationAndFinalization 6.7580 us/op 5.5190 us/op 1.22
mainnet_e58758 - phase0 processRewardsAndPenalties 20.950 ms/op 18.684 ms/op 1.12
mainnet_e58758 - phase0 processRegistryUpdates 2.8820 us/op 2.7480 us/op 1.05
mainnet_e58758 - phase0 processSlashings 178.00 ns/op 152.00 ns/op 1.17
mainnet_e58758 - phase0 processEth1DataReset 199.00 ns/op 166.00 ns/op 1.20
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 2.1855 ms/op 937.73 us/op 2.33
mainnet_e58758 - phase0 processSlashingsReset 986.00 ns/op 897.00 ns/op 1.10
mainnet_e58758 - phase0 processRandaoMixesReset 1.3490 us/op 1.0850 us/op 1.24
mainnet_e58758 - phase0 processHistoricalRootsUpdate 279.00 ns/op 168.00 ns/op 1.66
mainnet_e58758 - phase0 processParticipationRecordUpdates 931.00 ns/op 830.00 ns/op 1.12
mainnet_e58758 - phase0 afterProcessEpoch 38.850 ms/op 35.770 ms/op 1.09
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.5982 ms/op 1.3267 ms/op 1.20
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 2.6564 ms/op 2.1035 ms/op 1.26
altair processInactivityUpdates - 250000 normalcase 17.285 ms/op 13.785 ms/op 1.25
altair processInactivityUpdates - 250000 worstcase 16.324 ms/op 12.688 ms/op 1.29
phase0 processRegistryUpdates - 250000 normalcase 9.8230 us/op 4.7580 us/op 2.06
phase0 processRegistryUpdates - 250000 badcase_full_deposits 294.12 us/op 224.27 us/op 1.31
phase0 processRegistryUpdates - 250000 worstcase 0.5 70.776 ms/op 70.744 ms/op 1.00
altair processRewardsAndPenalties - 250000 normalcase 18.932 ms/op 18.150 ms/op 1.04
altair processRewardsAndPenalties - 250000 worstcase 22.410 ms/op 18.076 ms/op 1.24
phase0 getAttestationDeltas - 250000 normalcase 7.5369 ms/op 6.9407 ms/op 1.09
phase0 getAttestationDeltas - 250000 worstcase 7.4459 ms/op 6.9400 ms/op 1.07
phase0 processSlashings - 250000 worstcase 128.34 us/op 89.090 us/op 1.44
altair processSyncCommitteeUpdates - 250000 14.435 ms/op 11.031 ms/op 1.31
BeaconState.hashTreeRoot - No change 210.00 ns/op 198.00 ns/op 1.06
BeaconState.hashTreeRoot - 1 full validator 107.34 us/op 81.193 us/op 1.32
BeaconState.hashTreeRoot - 32 full validator 1.1950 ms/op 1.3769 ms/op 0.87
BeaconState.hashTreeRoot - 512 full validator 11.121 ms/op 8.6907 ms/op 1.28
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 142.49 us/op 101.93 us/op 1.40
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.8606 ms/op 1.8804 ms/op 0.99
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 24.787 ms/op 20.189 ms/op 1.23
BeaconState.hashTreeRoot - 1 balances 96.013 us/op 96.312 us/op 1.00
BeaconState.hashTreeRoot - 32 balances 1.0216 ms/op 1.2304 ms/op 0.83
BeaconState.hashTreeRoot - 512 balances 8.3025 ms/op 7.0094 ms/op 1.18
BeaconState.hashTreeRoot - 250000 balances 210.13 ms/op 150.52 ms/op 1.40
aggregationBits - 2048 els - zipIndexesInBitList 25.050 us/op 21.982 us/op 1.14
regular array get 100000 times 26.377 us/op 25.236 us/op 1.05
wrappedArray get 100000 times 27.151 us/op 25.103 us/op 1.08
arrayWithProxy get 100000 times 14.740 ms/op 14.391 ms/op 1.02
ssz.Root.equals 26.653 ns/op 23.785 ns/op 1.12
byteArrayEquals 26.182 ns/op 23.309 ns/op 1.12
Buffer.compare 11.040 ns/op 10.031 ns/op 1.10
processSlot - 1 slots 15.928 us/op 10.370 us/op 1.54
processSlot - 32 slots 2.9876 ms/op 2.2472 ms/op 1.33
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 5.7852 ms/op 3.6809 ms/op 1.57
getCommitteeAssignments - req 1 vs - 250000 vc 1.9696 ms/op 1.8970 ms/op 1.04
getCommitteeAssignments - req 100 vs - 250000 vc 3.8608 ms/op 3.7022 ms/op 1.04
getCommitteeAssignments - req 1000 vs - 250000 vc 4.1256 ms/op 3.9384 ms/op 1.05
findModifiedValidators - 10000 modified validators 734.57 ms/op 577.25 ms/op 1.27
findModifiedValidators - 1000 modified validators 422.86 ms/op 505.91 ms/op 0.84
findModifiedValidators - 100 modified validators 321.19 ms/op 301.85 ms/op 1.06
findModifiedValidators - 10 modified validators 167.07 ms/op 250.12 ms/op 0.67
findModifiedValidators - 1 modified validators 141.44 ms/op 170.46 ms/op 0.83
findModifiedValidators - no difference 161.90 ms/op 166.48 ms/op 0.97
migrate state 1500000 validators, 3400 modified, 2000 new 1.0062 s/op 925.29 ms/op 1.09
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 4.7000 ns/op 4.1900 ns/op 1.12
state getBlockRootAtSlot - 250000 vs - 7PWei 608.43 ns/op 588.68 ns/op 1.03
computeProposerIndex 100000 validators 1.6335 ms/op 1.5541 ms/op 1.05
getNextSyncCommitteeIndices 1000 validators 135.72 ms/op 124.63 ms/op 1.09
getNextSyncCommitteeIndices 10000 validators 136.60 ms/op 122.87 ms/op 1.11
getNextSyncCommitteeIndices 100000 validators 138.83 ms/op 122.19 ms/op 1.14
computeProposers - vc 250000 723.16 us/op 667.68 us/op 1.08
computeEpochShuffling - vc 250000 44.889 ms/op 41.560 ms/op 1.08
getNextSyncCommittee - vc 250000 12.733 ms/op 10.439 ms/op 1.22
nodejs block root to RootHex using toHex 142.85 ns/op 136.63 ns/op 1.05
nodejs block root to RootHex using toRootHex 92.197 ns/op 88.472 ns/op 1.04
nodejs fromHex(blob) 347.68 us/op 230.36 us/op 1.51
nodejs fromHexInto(blob) 739.41 us/op 706.05 us/op 1.05
nodejs block root to RootHex using the deprecated toHexString 591.63 ns/op 577.14 ns/op 1.03
nodejs byteArrayEquals 32 bytes (block root) 29.950 ns/op 28.461 ns/op 1.05
nodejs byteArrayEquals 48 bytes (pubkey) 42.883 ns/op 40.864 ns/op 1.05
nodejs byteArrayEquals 96 bytes (signature) 41.722 ns/op 40.968 ns/op 1.02
nodejs byteArrayEquals 1024 bytes 47.722 ns/op 46.634 ns/op 1.02
nodejs byteArrayEquals 131072 bytes (blob) 1.9428 us/op 1.8664 us/op 1.04
browser block root to RootHex using toHex 260.87 ns/op 261.52 ns/op 1.00
browser block root to RootHex using toRootHex 160.08 ns/op 153.86 ns/op 1.04
browser fromHex(blob) 1.2212 ms/op 1.2323 ms/op 0.99
browser fromHexInto(blob) 724.41 us/op 714.83 us/op 1.01
browser block root to RootHex using the deprecated toHexString 424.08 ns/op 571.60 ns/op 0.74
browser byteArrayEquals 32 bytes (block root) 31.724 ns/op 31.423 ns/op 1.01
browser byteArrayEquals 48 bytes (pubkey) 44.679 ns/op 43.761 ns/op 1.02
browser byteArrayEquals 96 bytes (signature) 86.788 ns/op 86.591 ns/op 1.00
browser byteArrayEquals 1024 bytes 831.62 ns/op 812.12 ns/op 1.02
browser byteArrayEquals 131072 bytes (blob) 103.37 us/op 103.79 us/op 1.00

by benchmarkbot/action

import {BeaconStateAllForks, CachedBeaconStateAllForks} from "../types.js";
import {SyncCommitteeWitness} from "./types.js";

export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllForks): SyncCommitteeWitness {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this was moved from

export function getSyncCommitteesWitness(fork: ForkName, state: BeaconStateAllForks): SyncCommitteeWitness {

although it does not look like it belongs to state-transition, it couples with BeaconStateAllForks and this is the only place we can leave it

@twoeths twoeths marked this pull request as ready for review January 26, 2026 11:17
@twoeths twoeths requested a review from a team as a code owner January 26, 2026 11:17
@twoeths twoeths force-pushed the te/beacon_state_view_2 branch from 1844dcf to e1d30db Compare January 27, 2026 09:14
/**
* A read-only view of the BeaconState.
*/
export interface IBeaconStateView {
Copy link
Member

@wemeetagain wemeetagain Jan 29, 2026

Choose a reason for hiding this comment

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

We should be consistent with when we're using getters and when we're using functions/methods. Currently, I'm not sure when each is used. There's a few things here that are methods that seem like they should be getters.

Also should be organized very well. For example, latestExecutionPayloadHeader should probably go next to latest BlockHeader? proposersPrevEpoch next to proposers or proposersNextEpoch? Also add spaces / comment lines between "groupings" of properties. In any case, having a nice ordering here will be useful to keep the bindings aligned.

Copy link
Member

Choose a reason for hiding this comment

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

We should be consistent with when we're using getters and when we're using functions/methods. Currently, I'm not sure when each is used

my rule is to use getters/fields when they're mandatory properties of the view, use functions if they're one-time use
it's easier for consumers to think that way. In terms of implementation/performance, they are the same

@twoeths
Copy link
Contributor Author

twoeths commented Feb 2, 2026

waiting for #8810 to be merged into this PR

@twoeths twoeths force-pushed the te/beacon_state_view_2 branch from da644c7 to 782e1b1 Compare February 5, 2026 07:54
Copy link
Contributor

@lodekeeper lodekeeper left a comment

Choose a reason for hiding this comment

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

Review Summary

Overall: Approve ✅

This PR introduces a clean abstraction layer (IBeaconStateView) for state access, enabling future decoupling from the TypeScript CachedBeaconState implementation. Code quality is good.

Verified

  • ✅ Lazy caching pattern is consistent across all fork-specific getters
  • ✅ Fork guards use correct ForkSeq comparisons
  • ✅ Bug fix in weakSubjectivity.ts is correct (using passed config parameter instead of state.config)
  • ✅ Light client proof extraction is a clean code move with no logic changes
  • ✅ Type exports (HistoricalSummaries, ExecutionPayloadBid, GeneralValidatorStatus) are correct

Minor observations (not blocking)

  1. IBeaconStateView JSDoc says "read-only view" but includes stateTransition/processSlots - these return new views (immutable pattern), so it's correct but comment could clarify
  2. createBeaconStateViewForHistoricalRegen intentionally passes empty index2pubkey: [] - documented appropriately
  3. getExpectedWithdrawals relies on underlying function's fork guard - acceptable

Good architectural foundation for the lodestar-z migration path. 🚀

Reviewed with sub-agent assistance (codex-reviewer, gpt-advisor)

Copy link
Contributor

@lodekeeper lodekeeper left a comment

Choose a reason for hiding this comment

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

Additional Findings from Sub-Agent Review

After deeper analysis, a few potential bugs were identified:

🔴 Critical

1. Missing Altair guard in getSyncCommitteesWitness
packages/state-transition/src/lightClient/proofs.ts - The function doesn't guard against pre-Altair forks. If called on a Phase0 state, it will crash accessing non-existent sync committee nodes.

Suggested fix:

if (!isForkPostAltair(fork)) {
  throw new Error("Sync committees are not available before Altair");
}

2. loadOtherState doesn't sync new validators' pubkeys
packages/state-transition/src/stateView/beaconStateView.ts:631-650 - Uses skipSyncPubkeys: true but doesn't manually sync new validators. If the loaded state has new validators not in the seed state, they won't be in pubkey caches.

Compare with loadCachedBeaconState in stateCache.ts which properly syncs new validators.

3. createBeaconStateViewForHistoricalRegen has empty index2pubkey
Passes index2pubkey: [] but methods like computeSyncCommitteeRewards() use it, which will fail.

🟡 Medium

4. Interface/Implementation mismatch - getExpectedWithdrawals interface is missing processedBuildersSweepCount from return type.


These may be acceptable depending on intended usage patterns, but worth flagging for review. Happy to discuss!

nflaig
nflaig previously approved these changes Feb 5, 2026
Copy link
Member

@nflaig nflaig left a comment

Choose a reason for hiding this comment

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

LGTM

@twoeths
Copy link
Contributor Author

twoeths commented Feb 7, 2026

waiting for the final review from @wemeetagain

Copy link
Member

@wemeetagain wemeetagain left a comment

Choose a reason for hiding this comment

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

Looks good!

@nflaig nflaig merged commit aeb5a21 into unstable Feb 23, 2026
24 of 26 checks passed
@nflaig nflaig deleted the te/beacon_state_view_2 branch February 23, 2026 14:03
@codecov
Copy link

codecov bot commented Feb 23, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.35%. Comparing base (49f680d) to head (da4fe5e).
⚠️ Report is 1 commits behind head on unstable.

Additional details and impacted files
@@             Coverage Diff              @@
##           unstable    #8773      +/-   ##
============================================
+ Coverage     52.33%   52.35%   +0.01%     
============================================
  Files           848      848              
  Lines         63363    63341      -22     
  Branches       4696     4696              
============================================
- Hits          33163    33161       -2     
+ Misses        30131    30111      -20     
  Partials         69       69              
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants