Skip to content

feat: model BeaconStateView#8773

Open
twoeths wants to merge 15 commits intounstablefrom
te/beacon_state_view_2
Open

feat: model BeaconStateView#8773
twoeths wants to merge 15 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 +478
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: 3b08b6a Previous: 6cfc0a8 Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 1.3906 ms/op 877.72 us/op 1.58
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 45.470 us/op 35.363 us/op 1.29
BLS verify - blst 1.0912 ms/op 877.36 us/op 1.24
BLS verifyMultipleSignatures 3 - blst 1.6658 ms/op 1.2944 ms/op 1.29
BLS verifyMultipleSignatures 8 - blst 2.2598 ms/op 2.0603 ms/op 1.10
BLS verifyMultipleSignatures 32 - blst 6.4185 ms/op 4.3667 ms/op 1.47
BLS verifyMultipleSignatures 64 - blst 12.370 ms/op 8.1114 ms/op 1.53
BLS verifyMultipleSignatures 128 - blst 20.408 ms/op 15.600 ms/op 1.31
BLS deserializing 10000 signatures 773.55 ms/op 615.37 ms/op 1.26
BLS deserializing 100000 signatures 7.5220 s/op 6.0617 s/op 1.24
BLS verifyMultipleSignatures - same message - 3 - blst 1.0749 ms/op 889.31 us/op 1.21
BLS verifyMultipleSignatures - same message - 8 - blst 1.1950 ms/op 1.0684 ms/op 1.12
BLS verifyMultipleSignatures - same message - 32 - blst 2.2030 ms/op 1.6246 ms/op 1.36
BLS verifyMultipleSignatures - same message - 64 - blst 2.8746 ms/op 2.3993 ms/op 1.20
BLS verifyMultipleSignatures - same message - 128 - blst 4.9155 ms/op 3.9232 ms/op 1.25
BLS aggregatePubkeys 32 - blst 22.220 us/op 16.620 us/op 1.34
BLS aggregatePubkeys 128 - blst 78.404 us/op 58.519 us/op 1.34
getSlashingsAndExits - default max 85.941 us/op 36.870 us/op 2.33
getSlashingsAndExits - 2k 345.45 us/op 344.74 us/op 1.00
isKnown best case - 1 super set check 214.00 ns/op 388.00 ns/op 0.55
isKnown normal case - 2 super set checks 200.00 ns/op 379.00 ns/op 0.53
isKnown worse case - 16 super set checks 199.00 ns/op 378.00 ns/op 0.53
validate api signedAggregateAndProof - struct 1.5177 ms/op 1.4130 ms/op 1.07
validate gossip signedAggregateAndProof - struct 2.5657 ms/op 1.8907 ms/op 1.36
batch validate gossip attestation - vc 640000 - chunk 32 122.67 us/op 105.75 us/op 1.16
batch validate gossip attestation - vc 640000 - chunk 64 104.46 us/op 90.050 us/op 1.16
batch validate gossip attestation - vc 640000 - chunk 128 96.424 us/op 82.662 us/op 1.17
batch validate gossip attestation - vc 640000 - chunk 256 94.200 us/op 79.346 us/op 1.19
bytes32 toHexString 382.00 ns/op 484.00 ns/op 0.79
bytes32 Buffer.toString(hex) 236.00 ns/op 396.00 ns/op 0.60
bytes32 Buffer.toString(hex) from Uint8Array 312.00 ns/op 464.00 ns/op 0.67
bytes32 Buffer.toString(hex) + 0x 284.00 ns/op 391.00 ns/op 0.73
Return object 10000 times 0.24200 ns/op 0.22290 ns/op 1.09
Throw Error 10000 times 4.3287 us/op 3.0910 us/op 1.40
toHex 158.99 ns/op 90.671 ns/op 1.75
Buffer.from 146.45 ns/op 84.457 ns/op 1.73
shared Buffer 87.773 ns/op 58.070 ns/op 1.51
fastMsgIdFn sha256 / 200 bytes 2.0120 us/op 1.6310 us/op 1.23
fastMsgIdFn h32 xxhash / 200 bytes 207.00 ns/op 369.00 ns/op 0.56
fastMsgIdFn h64 xxhash / 200 bytes 276.00 ns/op 415.00 ns/op 0.67
fastMsgIdFn sha256 / 1000 bytes 5.9870 us/op 4.7780 us/op 1.25
fastMsgIdFn h32 xxhash / 1000 bytes 284.00 ns/op 452.00 ns/op 0.63
fastMsgIdFn h64 xxhash / 1000 bytes 300.00 ns/op 462.00 ns/op 0.65
fastMsgIdFn sha256 / 10000 bytes 53.918 us/op 40.147 us/op 1.34
fastMsgIdFn h32 xxhash / 10000 bytes 1.4060 us/op 1.4700 us/op 0.96
fastMsgIdFn h64 xxhash / 10000 bytes 982.00 ns/op 1.0340 us/op 0.95
send data - 1000 256B messages 13.214 ms/op 10.313 ms/op 1.28
send data - 1000 512B messages 16.335 ms/op 12.030 ms/op 1.36
send data - 1000 1024B messages 21.549 ms/op 17.293 ms/op 1.25
send data - 1000 1200B messages 23.668 ms/op 15.947 ms/op 1.48
send data - 1000 2048B messages 23.179 ms/op 16.341 ms/op 1.42
send data - 1000 4096B messages 31.308 ms/op 16.626 ms/op 1.88
send data - 1000 16384B messages 103.88 ms/op 90.512 ms/op 1.15
send data - 1000 65536B messages 285.80 ms/op 202.13 ms/op 1.41
enrSubnets - fastDeserialize 64 bits 1.1700 us/op 957.00 ns/op 1.22
enrSubnets - ssz BitVector 64 bits 433.00 ns/op 490.00 ns/op 0.88
enrSubnets - fastDeserialize 4 bits 162.00 ns/op 308.00 ns/op 0.53
enrSubnets - ssz BitVector 4 bits 418.00 ns/op 496.00 ns/op 0.84
prioritizePeers score -10:0 att 32-0.1 sync 2-0 353.71 us/op 245.66 us/op 1.44
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 266.39 us/op 224.72 us/op 1.19
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 389.35 us/op 396.67 us/op 0.98
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 718.53 us/op 566.70 us/op 1.27
prioritizePeers score 0:0 att 64-1 sync 4-1 1.0807 ms/op 661.21 us/op 1.63
array of 16000 items push then shift 1.6044 us/op 1.1724 us/op 1.37
LinkedList of 16000 items push then shift 7.3060 ns/op 7.3710 ns/op 0.99
array of 16000 items push then pop 74.280 ns/op 62.101 ns/op 1.20
LinkedList of 16000 items push then pop 7.1000 ns/op 6.2930 ns/op 1.13
array of 24000 items push then shift 2.3890 us/op 1.8681 us/op 1.28
LinkedList of 24000 items push then shift 7.4910 ns/op 6.5380 ns/op 1.15
array of 24000 items push then pop 106.81 ns/op 87.867 ns/op 1.22
LinkedList of 24000 items push then pop 7.1350 ns/op 6.0660 ns/op 1.18
intersect bitArray bitLen 8 5.7760 ns/op 4.7580 ns/op 1.21
intersect array and set length 8 33.596 ns/op 30.460 ns/op 1.10
intersect bitArray bitLen 128 28.613 ns/op 25.561 ns/op 1.12
intersect array and set length 128 551.64 ns/op 495.40 ns/op 1.11
bitArray.getTrueBitIndexes() bitLen 128 1.0200 us/op 1.1720 us/op 0.87
bitArray.getTrueBitIndexes() bitLen 248 1.8090 us/op 1.9070 us/op 0.95
bitArray.getTrueBitIndexes() bitLen 512 3.7590 us/op 3.7460 us/op 1.00
Full columns - reconstruct all 6 blobs 281.27 us/op 316.77 us/op 0.89
Full columns - reconstruct half of the blobs out of 6 105.25 us/op 101.46 us/op 1.04
Full columns - reconstruct single blob out of 6 30.618 us/op 30.103 us/op 1.02
Half columns - reconstruct all 6 blobs 270.44 ms/op 228.60 ms/op 1.18
Half columns - reconstruct half of the blobs out of 6 135.89 ms/op 116.89 ms/op 1.16
Half columns - reconstruct single blob out of 6 50.127 ms/op 45.407 ms/op 1.10
Full columns - reconstruct all 10 blobs 625.10 us/op 266.80 us/op 2.34
Full columns - reconstruct half of the blobs out of 10 303.59 us/op 135.32 us/op 2.24
Full columns - reconstruct single blob out of 10 41.408 us/op 38.731 us/op 1.07
Half columns - reconstruct all 10 blobs 446.98 ms/op 381.93 ms/op 1.17
Half columns - reconstruct half of the blobs out of 10 237.26 ms/op 191.76 ms/op 1.24
Half columns - reconstruct single blob out of 10 51.682 ms/op 43.417 ms/op 1.19
Full columns - reconstruct all 20 blobs 1.2643 ms/op 672.81 us/op 1.88
Full columns - reconstruct half of the blobs out of 20 417.72 us/op 342.79 us/op 1.22
Full columns - reconstruct single blob out of 20 30.537 us/op 28.135 us/op 1.09
Half columns - reconstruct all 20 blobs 910.63 ms/op 754.49 ms/op 1.21
Half columns - reconstruct half of the blobs out of 20 453.29 ms/op 380.81 ms/op 1.19
Half columns - reconstruct single blob out of 20 53.770 ms/op 43.077 ms/op 1.25
Set add up to 64 items then delete first 2.1055 us/op 1.6431 us/op 1.28
OrderedSet add up to 64 items then delete first 3.1721 us/op 2.4840 us/op 1.28
Set add up to 64 items then delete last 2.4722 us/op 1.6784 us/op 1.47
OrderedSet add up to 64 items then delete last 3.6851 us/op 2.7755 us/op 1.33
Set add up to 64 items then delete middle 2.4888 us/op 1.8872 us/op 1.32
OrderedSet add up to 64 items then delete middle 5.3576 us/op 4.2380 us/op 1.26
Set add up to 128 items then delete first 4.9457 us/op 3.7254 us/op 1.33
OrderedSet add up to 128 items then delete first 7.0731 us/op 5.6461 us/op 1.25
Set add up to 128 items then delete last 4.8995 us/op 3.6053 us/op 1.36
OrderedSet add up to 128 items then delete last 7.2187 us/op 5.3681 us/op 1.34
Set add up to 128 items then delete middle 4.8088 us/op 3.5891 us/op 1.34
OrderedSet add up to 128 items then delete middle 14.234 us/op 11.012 us/op 1.29
Set add up to 256 items then delete first 10.354 us/op 7.5173 us/op 1.38
OrderedSet add up to 256 items then delete first 15.223 us/op 11.455 us/op 1.33
Set add up to 256 items then delete last 10.216 us/op 7.2596 us/op 1.41
OrderedSet add up to 256 items then delete last 16.013 us/op 10.909 us/op 1.47
Set add up to 256 items then delete middle 10.404 us/op 7.2008 us/op 1.44
OrderedSet add up to 256 items then delete middle 44.649 us/op 34.332 us/op 1.30
pass gossip attestations to forkchoice per slot 2.6626 ms/op 1.9062 ms/op 1.40
forkChoice updateHead vc 100000 bc 64 eq 0 523.66 us/op 346.28 us/op 1.51
forkChoice updateHead vc 600000 bc 64 eq 0 3.0878 ms/op 2.0889 ms/op 1.48
forkChoice updateHead vc 1000000 bc 64 eq 0 5.1131 ms/op 4.6073 ms/op 1.11
forkChoice updateHead vc 600000 bc 320 eq 0 3.1151 ms/op 2.0843 ms/op 1.49
forkChoice updateHead vc 600000 bc 1200 eq 0 3.1636 ms/op 2.1103 ms/op 1.50
forkChoice updateHead vc 600000 bc 7200 eq 0 3.4724 ms/op 2.2943 ms/op 1.51
forkChoice updateHead vc 600000 bc 64 eq 1000 3.5381 ms/op 2.5950 ms/op 1.36
forkChoice updateHead vc 600000 bc 64 eq 10000 3.6745 ms/op 2.6850 ms/op 1.37
forkChoice updateHead vc 600000 bc 64 eq 300000 9.5590 ms/op 6.6290 ms/op 1.44
computeDeltas 1400000 validators 0% inactive 14.918 ms/op 11.215 ms/op 1.33
computeDeltas 1400000 validators 10% inactive 13.908 ms/op 10.460 ms/op 1.33
computeDeltas 1400000 validators 20% inactive 13.356 ms/op 9.8564 ms/op 1.36
computeDeltas 1400000 validators 50% inactive 10.330 ms/op 7.3564 ms/op 1.40
computeDeltas 2100000 validators 0% inactive 22.680 ms/op 16.813 ms/op 1.35
computeDeltas 2100000 validators 10% inactive 21.139 ms/op 15.709 ms/op 1.35
computeDeltas 2100000 validators 20% inactive 19.425 ms/op 14.304 ms/op 1.36
computeDeltas 2100000 validators 50% inactive 15.139 ms/op 11.203 ms/op 1.35
altair processAttestation - 250000 vs - 7PWei normalcase 2.0485 ms/op 1.5775 ms/op 1.30
altair processAttestation - 250000 vs - 7PWei worstcase 2.9735 ms/op 2.1072 ms/op 1.41
altair processAttestation - setStatus - 1/6 committees join 118.92 us/op 85.452 us/op 1.39
altair processAttestation - setStatus - 1/3 committees join 239.44 us/op 167.59 us/op 1.43
altair processAttestation - setStatus - 1/2 committees join 337.38 us/op 224.27 us/op 1.50
altair processAttestation - setStatus - 2/3 committees join 428.93 us/op 330.75 us/op 1.30
altair processAttestation - setStatus - 4/5 committees join 597.42 us/op 464.97 us/op 1.28
altair processAttestation - setStatus - 100% committees join 703.48 us/op 558.68 us/op 1.26
altair processBlock - 250000 vs - 7PWei normalcase 3.6672 ms/op 2.7432 ms/op 1.34
altair processBlock - 250000 vs - 7PWei normalcase hashState 18.529 ms/op 18.644 ms/op 0.99
altair processBlock - 250000 vs - 7PWei worstcase 24.731 ms/op 22.573 ms/op 1.10
altair processBlock - 250000 vs - 7PWei worstcase hashState 60.060 ms/op 56.223 ms/op 1.07
phase0 processBlock - 250000 vs - 7PWei normalcase 1.9037 ms/op 1.4992 ms/op 1.27
phase0 processBlock - 250000 vs - 7PWei worstcase 22.482 ms/op 21.910 ms/op 1.03
altair processEth1Data - 250000 vs - 7PWei normalcase 375.85 us/op 299.38 us/op 1.26
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 6.2350 us/op 3.3350 us/op 1.87
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 42.228 us/op 33.683 us/op 1.25
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 11.390 us/op 5.8150 us/op 1.96
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 7.5460 us/op 8.6290 us/op 0.87
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 176.49 us/op 155.60 us/op 1.13
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 2.3390 ms/op 1.3867 ms/op 1.69
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 2.5577 ms/op 1.6194 ms/op 1.58
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 2.5499 ms/op 1.7951 ms/op 1.42
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 4.9299 ms/op 3.4775 ms/op 1.42
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 3.1739 ms/op 1.7975 ms/op 1.77
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 5.4079 ms/op 4.0609 ms/op 1.33
Tree 40 250000 create 387.11 ms/op 332.84 ms/op 1.16
Tree 40 250000 get(125000) 134.84 ns/op 102.77 ns/op 1.31
Tree 40 250000 set(125000) 1.3181 us/op 1.0564 us/op 1.25
Tree 40 250000 toArray() 17.268 ms/op 9.1787 ms/op 1.88
Tree 40 250000 iterate all - toArray() + loop 17.337 ms/op 9.3090 ms/op 1.86
Tree 40 250000 iterate all - get(i) 46.612 ms/op 32.931 ms/op 1.42
Array 250000 create 2.6138 ms/op 2.0096 ms/op 1.30
Array 250000 clone - spread 1.2558 ms/op 605.01 us/op 2.08
Array 250000 get(125000) 0.41000 ns/op 0.48600 ns/op 0.84
Array 250000 set(125000) 0.37500 ns/op 0.51500 ns/op 0.73
Array 250000 iterate all - loop 65.785 us/op 55.573 us/op 1.18
phase0 afterProcessEpoch - 250000 vs - 7PWei 44.527 ms/op 37.375 ms/op 1.19
Array.fill - length 1000000 3.0641 ms/op 2.0018 ms/op 1.53
Array push - length 1000000 11.548 ms/op 6.7417 ms/op 1.71
Array.get 0.23347 ns/op 0.18201 ns/op 1.28
Uint8Array.get 0.25200 ns/op 0.19250 ns/op 1.31
phase0 beforeProcessEpoch - 250000 vs - 7PWei 17.608 ms/op 13.096 ms/op 1.34
altair processEpoch - mainnet_e81889 270.69 ms/op 242.32 ms/op 1.12
mainnet_e81889 - altair beforeProcessEpoch 19.808 ms/op 13.208 ms/op 1.50
mainnet_e81889 - altair processJustificationAndFinalization 6.7840 us/op 5.9960 us/op 1.13
mainnet_e81889 - altair processInactivityUpdates 4.0672 ms/op 2.9809 ms/op 1.36
mainnet_e81889 - altair processRewardsAndPenalties 20.534 ms/op 17.168 ms/op 1.20
mainnet_e81889 - altair processRegistryUpdates 688.00 ns/op 782.00 ns/op 0.88
mainnet_e81889 - altair processSlashings 212.00 ns/op 394.00 ns/op 0.54
mainnet_e81889 - altair processEth1DataReset 209.00 ns/op 445.00 ns/op 0.47
mainnet_e81889 - altair processEffectiveBalanceUpdates 3.9379 ms/op 1.2365 ms/op 3.18
mainnet_e81889 - altair processSlashingsReset 914.00 ns/op 948.00 ns/op 0.96
mainnet_e81889 - altair processRandaoMixesReset 1.2450 us/op 1.1870 us/op 1.05
mainnet_e81889 - altair processHistoricalRootsUpdate 187.00 ns/op 374.00 ns/op 0.50
mainnet_e81889 - altair processParticipationFlagUpdates 561.00 ns/op 683.00 ns/op 0.82
mainnet_e81889 - altair processSyncCommitteeUpdates 151.00 ns/op 334.00 ns/op 0.45
mainnet_e81889 - altair afterProcessEpoch 49.995 ms/op 36.589 ms/op 1.37
capella processEpoch - mainnet_e217614 946.22 ms/op 758.32 ms/op 1.25
mainnet_e217614 - capella beforeProcessEpoch 102.76 ms/op 59.486 ms/op 1.73
mainnet_e217614 - capella processJustificationAndFinalization 6.5590 us/op 4.8450 us/op 1.35
mainnet_e217614 - capella processInactivityUpdates 18.913 ms/op 11.101 ms/op 1.70
mainnet_e217614 - capella processRewardsAndPenalties 111.33 ms/op 99.136 ms/op 1.12
mainnet_e217614 - capella processRegistryUpdates 6.5750 us/op 4.7980 us/op 1.37
mainnet_e217614 - capella processSlashings 187.00 ns/op 365.00 ns/op 0.51
mainnet_e217614 - capella processEth1DataReset 217.00 ns/op 372.00 ns/op 0.58
mainnet_e217614 - capella processEffectiveBalanceUpdates 19.895 ms/op 5.9421 ms/op 3.35
mainnet_e217614 - capella processSlashingsReset 921.00 ns/op 1.1340 us/op 0.81
mainnet_e217614 - capella processRandaoMixesReset 1.3280 us/op 1.1910 us/op 1.12
mainnet_e217614 - capella processHistoricalRootsUpdate 191.00 ns/op 354.00 ns/op 0.54
mainnet_e217614 - capella processParticipationFlagUpdates 615.00 ns/op 678.00 ns/op 0.91
mainnet_e217614 - capella afterProcessEpoch 128.54 ms/op 100.84 ms/op 1.27
phase0 processEpoch - mainnet_e58758 251.14 ms/op 229.97 ms/op 1.09
mainnet_e58758 - phase0 beforeProcessEpoch 66.044 ms/op 44.423 ms/op 1.49
mainnet_e58758 - phase0 processJustificationAndFinalization 7.0660 us/op 4.4100 us/op 1.60
mainnet_e58758 - phase0 processRewardsAndPenalties 23.949 ms/op 16.326 ms/op 1.47
mainnet_e58758 - phase0 processRegistryUpdates 3.2900 us/op 2.3890 us/op 1.38
mainnet_e58758 - phase0 processSlashings 214.00 ns/op 372.00 ns/op 0.58
mainnet_e58758 - phase0 processEth1DataReset 191.00 ns/op 367.00 ns/op 0.52
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 1.8253 ms/op 1.1642 ms/op 1.57
mainnet_e58758 - phase0 processSlashingsReset 1.0010 us/op 1.0940 us/op 0.91
mainnet_e58758 - phase0 processRandaoMixesReset 1.2540 us/op 1.2620 us/op 0.99
mainnet_e58758 - phase0 processHistoricalRootsUpdate 220.00 ns/op 371.00 ns/op 0.59
mainnet_e58758 - phase0 processParticipationRecordUpdates 992.00 ns/op 1.2680 us/op 0.78
mainnet_e58758 - phase0 afterProcessEpoch 40.546 ms/op 30.236 ms/op 1.34
phase0 processEffectiveBalanceUpdates - 250000 normalcase 1.9380 ms/op 985.77 us/op 1.97
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 2.9601 ms/op 1.8852 ms/op 1.57
altair processInactivityUpdates - 250000 normalcase 16.958 ms/op 9.1459 ms/op 1.85
altair processInactivityUpdates - 250000 worstcase 18.164 ms/op 9.9853 ms/op 1.82
phase0 processRegistryUpdates - 250000 normalcase 9.0430 us/op 2.4250 us/op 3.73
phase0 processRegistryUpdates - 250000 badcase_full_deposits 297.52 us/op 254.54 us/op 1.17
phase0 processRegistryUpdates - 250000 worstcase 0.5 76.148 ms/op 46.843 ms/op 1.63
altair processRewardsAndPenalties - 250000 normalcase 20.659 ms/op 13.443 ms/op 1.54
altair processRewardsAndPenalties - 250000 worstcase 20.331 ms/op 15.348 ms/op 1.32
phase0 getAttestationDeltas - 250000 normalcase 7.8552 ms/op 4.8352 ms/op 1.62
phase0 getAttestationDeltas - 250000 worstcase 7.5388 ms/op 4.8711 ms/op 1.55
phase0 processSlashings - 250000 worstcase 99.073 us/op 93.794 us/op 1.06
altair processSyncCommitteeUpdates - 250000 14.448 ms/op 8.8383 ms/op 1.63
BeaconState.hashTreeRoot - No change 283.00 ns/op 385.00 ns/op 0.74
BeaconState.hashTreeRoot - 1 full validator 96.044 us/op 88.388 us/op 1.09
BeaconState.hashTreeRoot - 32 full validator 1.0938 ms/op 950.24 us/op 1.15
BeaconState.hashTreeRoot - 512 full validator 9.5199 ms/op 6.2028 ms/op 1.53
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 128.67 us/op 75.834 us/op 1.70
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.9219 ms/op 1.4439 ms/op 1.33
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 19.985 ms/op 14.756 ms/op 1.35
BeaconState.hashTreeRoot - 1 balances 92.033 us/op 78.172 us/op 1.18
BeaconState.hashTreeRoot - 32 balances 987.83 us/op 822.82 us/op 1.20
BeaconState.hashTreeRoot - 512 balances 7.5142 ms/op 4.8859 ms/op 1.54
BeaconState.hashTreeRoot - 250000 balances 147.22 ms/op 150.54 ms/op 0.98
aggregationBits - 2048 els - zipIndexesInBitList 24.185 us/op 18.361 us/op 1.32
bigIntToBytes LE 0 291.00 ns/op 408.00 ns/op 0.71
bigIntToBytesInto LE 0 270.00 ns/op 374.00 ns/op 0.72
bigIntToBytes LE 255 287.00 ns/op 403.00 ns/op 0.71
bigIntToBytesInto LE 255 252.00 ns/op 389.00 ns/op 0.65
bigIntToBytes LE 2^15 291.00 ns/op 523.00 ns/op 0.56
bigIntToBytesInto LE 2^15 264.00 ns/op 381.00 ns/op 0.69
bigIntToBytes LE 2^23 354.00 ns/op 413.00 ns/op 0.86
bigIntToBytesInto LE 2^23 264.00 ns/op 391.00 ns/op 0.68
bigIntToBytes LE 2^31 325.00 ns/op 439.00 ns/op 0.74
bigIntToBytesInto LE 2^31 261.00 ns/op 394.00 ns/op 0.66
bigIntToBytes LE 2^63 290.00 ns/op 467.00 ns/op 0.62
bigIntToBytesInto LE 2^63 263.00 ns/op 385.00 ns/op 0.68
bigIntToBytes LE 2^127 332.00 ns/op 449.00 ns/op 0.74
bigIntToBytesInto LE 2^127 271.00 ns/op 445.00 ns/op 0.61
bigIntToBytes BE 32 bytes (allocating) 346.00 ns/op 470.00 ns/op 0.74
bigIntToBytesInto BE 32 bytes (pre-allocated) 257.00 ns/op 387.00 ns/op 0.66
batch 100x bigIntToBytes (allocating) 12.001 us/op 8.5030 us/op 1.41
batch 100x bigIntToBytesInto (reusing buffer) 6.8670 us/op 5.3680 us/op 1.28
regular array get 100000 times 28.037 us/op 22.509 us/op 1.25
wrappedArray get 100000 times 27.971 us/op 22.502 us/op 1.24
arrayWithProxy get 100000 times 16.612 ms/op 14.170 ms/op 1.17
ssz.Root.equals 25.427 ns/op 21.472 ns/op 1.18
byteArrayEquals 24.711 ns/op 20.945 ns/op 1.18
Buffer.compare 11.158 ns/op 9.5080 ns/op 1.17
processSlot - 1 slots 11.699 us/op 10.034 us/op 1.17
processSlot - 32 slots 2.6740 ms/op 2.3111 ms/op 1.16
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 3.9173 ms/op 4.2267 ms/op 0.93
getCommitteeAssignments - req 1 vs - 250000 vc 2.1167 ms/op 1.5488 ms/op 1.37
getCommitteeAssignments - req 100 vs - 250000 vc 3.9867 ms/op 3.0996 ms/op 1.29
getCommitteeAssignments - req 1000 vs - 250000 vc 4.3115 ms/op 3.3306 ms/op 1.29
findModifiedValidators - 10000 modified validators 625.44 ms/op 397.86 ms/op 1.57
findModifiedValidators - 1000 modified validators 358.08 ms/op 331.60 ms/op 1.08
findModifiedValidators - 100 modified validators 219.55 ms/op 268.54 ms/op 0.82
findModifiedValidators - 10 modified validators 255.85 ms/op 138.21 ms/op 1.85
findModifiedValidators - 1 modified validators 239.55 ms/op 124.30 ms/op 1.93
findModifiedValidators - no difference 181.60 ms/op 143.03 ms/op 1.27
migrate state 1500000 validators, 3400 modified, 2000 new 1.0289 s/op 939.32 ms/op 1.10
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 4.7700 ns/op 5.8200 ns/op 0.82
state getBlockRootAtSlot - 250000 vs - 7PWei 597.44 ns/op 536.92 ns/op 1.11
computeProposerIndex 100000 validators 1.7791 ms/op 1.2121 ms/op 1.47
getNextSyncCommitteeIndices 1000 validators 136.80 ms/op 91.034 ms/op 1.50
getNextSyncCommitteeIndices 10000 validators 138.00 ms/op 89.858 ms/op 1.54
getNextSyncCommitteeIndices 100000 validators 143.94 ms/op 90.198 ms/op 1.60
computeProposers - vc 250000 756.37 us/op 568.54 us/op 1.33
computeEpochShuffling - vc 250000 47.933 ms/op 37.256 ms/op 1.29
getNextSyncCommittee - vc 250000 12.313 ms/op 9.1867 ms/op 1.34
nodejs block root to RootHex using toHex 170.45 ns/op 115.18 ns/op 1.48
nodejs block root to RootHex using toRootHex 115.77 ns/op 75.732 ns/op 1.53
nodejs fromHex(blob) 470.76 us/op 319.71 us/op 1.47
nodejs fromHexInto(blob) 786.89 us/op 650.82 us/op 1.21
nodejs block root to RootHex using the deprecated toHexString 593.51 ns/op 367.71 ns/op 1.61
browser block root to RootHex using toHex 292.18 ns/op 244.33 ns/op 1.20
browser block root to RootHex using toRootHex 166.48 ns/op 127.92 ns/op 1.30
browser fromHex(blob) 1.4682 ms/op 1.0856 ms/op 1.35
browser fromHexInto(blob) 795.04 us/op 566.63 us/op 1.40
browser block root to RootHex using the deprecated toHexString 428.59 ns/op 448.20 ns/op 0.96

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!

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

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