Skip to content

fix: gate loadOtherState validators/balances preload behind opt-in#9245

Merged
nflaig merged 2 commits intounstablefrom
te/fix_loadOtherState
Apr 21, 2026
Merged

fix: gate loadOtherState validators/balances preload behind opt-in#9245
nflaig merged 2 commits intounstablefrom
te/fix_loadOtherState

Conversation

@twoeths
Copy link
Copy Markdown
Contributor

@twoeths twoeths commented Apr 21, 2026

Motivation

Main-thread memory spike on serveHistoricalState: true nodes after #8857

#8857 rerouted the API path through getState() in
packages/beacon-node/src/api/impl/beacon/state/index.ts from the lightweight
loadState(...) to BeaconStateView.loadOtherState(), which eagerly materializes
all validators and balances. That is only needed for persistentCheckpointsCache.

Description

AI Assistance Disclosure

This PR was developed with AI assistance (Claude Code).

Copy link
Copy Markdown
Contributor

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

Choose a reason for hiding this comment

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

Code Review

This pull request introduces an optional preloadValidatorsAndBalances flag to the loadOtherState method, enabling eager materialization of validator and balance arrays only when necessary, such as during block replay. This change helps avoid significant memory overhead in API paths that only require specific fields. The update includes modifications to the BeaconStateView implementation, its interface, and the persistent checkpoint cache, along with a new unit test. A review comment suggests refactoring the inline options type into a named interface to improve code maintainability.

Comment thread packages/state-transition/src/stateView/interface.ts
@twoeths twoeths force-pushed the te/fix_loadOtherState branch from 914f164 to 74b3a11 Compare April 21, 2026 14:14
@twoeths twoeths force-pushed the te/fix_loadOtherState branch from 74b3a11 to af04829 Compare April 21, 2026 14:23
@twoeths twoeths marked this pull request as ready for review April 21, 2026 14:27
@twoeths twoeths requested a review from a team as a code owner April 21, 2026 14:27
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 21, 2026

Performance Report

✔️ no performance regression detected

Full benchmark results
Benchmark suite Current: f372a63 Previous: 9fa9f08 Ratio
getPubkeys - index2pubkey - req 1000 vs - 250000 vc 910.98 us/op 929.91 us/op 0.98
getPubkeys - validatorsArr - req 1000 vs - 250000 vc 37.911 us/op 41.543 us/op 0.91
BLS verify - blst 638.09 us/op 751.11 us/op 0.85
BLS verifyMultipleSignatures 3 - blst 1.2956 ms/op 1.3677 ms/op 0.95
BLS verifyMultipleSignatures 8 - blst 2.0359 ms/op 2.2017 ms/op 0.92
BLS verifyMultipleSignatures 32 - blst 6.5024 ms/op 7.0473 ms/op 0.92
BLS verifyMultipleSignatures 64 - blst 12.744 ms/op 13.393 ms/op 0.95
BLS verifyMultipleSignatures 128 - blst 25.145 ms/op 25.969 ms/op 0.97
BLS deserializing 10000 signatures 627.05 ms/op 650.84 ms/op 0.96
BLS deserializing 100000 signatures 6.3334 s/op 6.3681 s/op 0.99
BLS verifyMultipleSignatures - same message - 3 - blst 770.43 us/op 829.19 us/op 0.93
BLS verifyMultipleSignatures - same message - 8 - blst 913.54 us/op 957.32 us/op 0.95
BLS verifyMultipleSignatures - same message - 32 - blst 1.4888 ms/op 1.5721 ms/op 0.95
BLS verifyMultipleSignatures - same message - 64 - blst 2.3197 ms/op 2.3808 ms/op 0.97
BLS verifyMultipleSignatures - same message - 128 - blst 3.9187 ms/op 4.0055 ms/op 0.98
BLS aggregatePubkeys 32 - blst 17.356 us/op 17.903 us/op 0.97
BLS aggregatePubkeys 128 - blst 62.066 us/op 63.824 us/op 0.97
getSlashingsAndExits - default max 45.879 us/op 46.958 us/op 0.98
getSlashingsAndExits - 2k 330.94 us/op 329.96 us/op 1.00
proposeBlockBody type=full, size=empty 770.84 us/op 698.68 us/op 1.10
isKnown best case - 1 super set check 163.00 ns/op 170.00 ns/op 0.96
isKnown normal case - 2 super set checks 156.00 ns/op 164.00 ns/op 0.95
isKnown worse case - 16 super set checks 162.00 ns/op 166.00 ns/op 0.98
validate api signedAggregateAndProof - struct 1.4139 ms/op 1.5420 ms/op 0.92
validate gossip signedAggregateAndProof - struct 1.4139 ms/op 1.5316 ms/op 0.92
batch validate gossip attestation - vc 640000 - chunk 32 102.10 us/op 105.73 us/op 0.97
batch validate gossip attestation - vc 640000 - chunk 64 89.050 us/op 92.963 us/op 0.96
batch validate gossip attestation - vc 640000 - chunk 128 82.977 us/op 86.012 us/op 0.96
batch validate gossip attestation - vc 640000 - chunk 256 80.338 us/op 82.120 us/op 0.98
bytes32 toHexString 280.00 ns/op 308.00 ns/op 0.91
bytes32 Buffer.toString(hex) 174.00 ns/op 174.00 ns/op 1.00
bytes32 Buffer.toString(hex) from Uint8Array 233.00 ns/op 240.00 ns/op 0.97
bytes32 Buffer.toString(hex) + 0x 175.00 ns/op 173.00 ns/op 1.01
Return object 10000 times 0.20520 ns/op 0.21010 ns/op 0.98
Throw Error 10000 times 3.1249 us/op 3.2741 us/op 0.95
toHex 99.295 ns/op 97.173 ns/op 1.02
Buffer.from 82.635 ns/op 88.814 ns/op 0.93
shared Buffer 53.278 ns/op 60.318 ns/op 0.88
fastMsgIdFn sha256 / 200 bytes 1.3970 us/op 1.5290 us/op 0.91
fastMsgIdFn h32 xxhash / 200 bytes 154.00 ns/op 152.00 ns/op 1.01
fastMsgIdFn h64 xxhash / 200 bytes 205.00 ns/op 205.00 ns/op 1.00
fastMsgIdFn sha256 / 1000 bytes 4.5030 us/op 5.0310 us/op 0.90
fastMsgIdFn h32 xxhash / 1000 bytes 239.00 ns/op 242.00 ns/op 0.99
fastMsgIdFn h64 xxhash / 1000 bytes 248.00 ns/op 251.00 ns/op 0.99
fastMsgIdFn sha256 / 10000 bytes 40.160 us/op 44.907 us/op 0.89
fastMsgIdFn h32 xxhash / 10000 bytes 1.2730 us/op 1.2620 us/op 1.01
fastMsgIdFn h64 xxhash / 10000 bytes 816.00 ns/op 816.00 ns/op 1.00
send data - 1000 256B messages 4.1315 ms/op 3.9097 ms/op 1.06
send data - 1000 512B messages 4.2556 ms/op 3.9822 ms/op 1.07
send data - 1000 1024B messages 4.4483 ms/op 4.1178 ms/op 1.08
send data - 1000 1200B messages 5.0286 ms/op 4.3661 ms/op 1.15
send data - 1000 2048B messages 4.8844 ms/op 4.6082 ms/op 1.06
send data - 1000 4096B messages 5.5805 ms/op 5.2712 ms/op 1.06
send data - 1000 16384B messages 26.121 ms/op 12.759 ms/op 2.05
send data - 1000 65536B messages 173.65 ms/op 241.95 ms/op 0.72
enrSubnets - fastDeserialize 64 bits 746.00 ns/op 744.00 ns/op 1.00
enrSubnets - ssz BitVector 64 bits 266.00 ns/op 261.00 ns/op 1.02
enrSubnets - fastDeserialize 4 bits 101.00 ns/op 97.000 ns/op 1.04
enrSubnets - ssz BitVector 4 bits 260.00 ns/op 266.00 ns/op 0.98
prioritizePeers score -10:0 att 32-0.1 sync 2-0 198.62 us/op 206.73 us/op 0.96
prioritizePeers score 0:0 att 32-0.25 sync 2-0.25 240.71 us/op 246.62 us/op 0.98
prioritizePeers score 0:0 att 32-0.5 sync 2-0.5 338.68 us/op 343.29 us/op 0.99
prioritizePeers score 0:0 att 64-0.75 sync 4-0.75 590.50 us/op 603.17 us/op 0.98
prioritizePeers score 0:0 att 64-1 sync 4-1 692.41 us/op 700.08 us/op 0.99
array of 16000 items push then shift 1.2138 us/op 1.2808 us/op 0.95
LinkedList of 16000 items push then shift 7.5080 ns/op 6.6570 ns/op 1.13
array of 16000 items push then pop 67.374 ns/op 63.167 ns/op 1.07
LinkedList of 16000 items push then pop 5.7990 ns/op 5.9480 ns/op 0.97
array of 24000 items push then shift 1.7929 us/op 1.8974 us/op 0.94
LinkedList of 24000 items push then shift 7.2090 ns/op 6.3350 ns/op 1.14
array of 24000 items push then pop 94.258 ns/op 90.224 ns/op 1.04
LinkedList of 24000 items push then pop 5.9370 ns/op 5.9460 ns/op 1.00
intersect bitArray bitLen 8 4.5980 ns/op 4.7200 ns/op 0.97
intersect array and set length 8 28.305 ns/op 31.745 ns/op 0.89
intersect bitArray bitLen 128 23.287 ns/op 24.707 ns/op 0.94
intersect array and set length 128 481.39 ns/op 514.42 ns/op 0.94
bitArray.getTrueBitIndexes() bitLen 128 1.0950 us/op 1.0150 us/op 1.08
bitArray.getTrueBitIndexes() bitLen 248 1.9160 us/op 1.7150 us/op 1.12
bitArray.getTrueBitIndexes() bitLen 512 3.9360 us/op 3.4940 us/op 1.13
Full columns - reconstruct all 6 blobs 120.53 us/op 115.81 us/op 1.04
Full columns - reconstruct half of the blobs out of 6 89.575 us/op 69.165 us/op 1.30
Full columns - reconstruct single blob out of 6 32.640 us/op 34.355 us/op 0.95
Half columns - reconstruct all 6 blobs 374.17 ms/op 397.59 ms/op 0.94
Half columns - reconstruct half of the blobs out of 6 188.53 ms/op 200.23 ms/op 0.94
Half columns - reconstruct single blob out of 6 66.905 ms/op 71.367 ms/op 0.94
Full columns - reconstruct all 10 blobs 443.20 us/op 191.74 us/op 2.31
Full columns - reconstruct half of the blobs out of 10 103.61 us/op 148.38 us/op 0.70
Full columns - reconstruct single blob out of 10 32.476 us/op 31.647 us/op 1.03
Half columns - reconstruct all 10 blobs 619.20 ms/op 643.80 ms/op 0.96
Half columns - reconstruct half of the blobs out of 10 321.19 ms/op 320.68 ms/op 1.00
Half columns - reconstruct single blob out of 10 68.110 ms/op 66.557 ms/op 1.02
Full columns - reconstruct all 20 blobs 553.47 us/op 517.30 us/op 1.07
Full columns - reconstruct half of the blobs out of 20 223.18 us/op 267.44 us/op 0.83
Full columns - reconstruct single blob out of 20 30.956 us/op 31.022 us/op 1.00
Half columns - reconstruct all 20 blobs 1.2986 s/op 1.2639 s/op 1.03
Half columns - reconstruct half of the blobs out of 20 644.70 ms/op 644.43 ms/op 1.00
Half columns - reconstruct single blob out of 20 68.908 ms/op 70.039 ms/op 0.98
Set add up to 64 items then delete first 2.0709 us/op 2.1325 us/op 0.97
OrderedSet add up to 64 items then delete first 3.2483 us/op 3.3934 us/op 0.96
Set add up to 64 items then delete last 2.2179 us/op 2.2001 us/op 1.01
OrderedSet add up to 64 items then delete last 3.2011 us/op 3.5039 us/op 0.91
Set add up to 64 items then delete middle 2.0634 us/op 2.2285 us/op 0.93
OrderedSet add up to 64 items then delete middle 4.7140 us/op 4.9760 us/op 0.95
Set add up to 128 items then delete first 4.1191 us/op 4.3431 us/op 0.95
OrderedSet add up to 128 items then delete first 6.5167 us/op 6.4957 us/op 1.00
Set add up to 128 items then delete last 3.8291 us/op 4.1522 us/op 0.92
OrderedSet add up to 128 items then delete last 5.7205 us/op 6.2591 us/op 0.91
Set add up to 128 items then delete middle 3.7931 us/op 4.0712 us/op 0.93
OrderedSet add up to 128 items then delete middle 11.567 us/op 12.147 us/op 0.95
Set add up to 256 items then delete first 7.8285 us/op 8.0633 us/op 0.97
OrderedSet add up to 256 items then delete first 12.242 us/op 12.032 us/op 1.02
Set add up to 256 items then delete last 7.5692 us/op 7.9750 us/op 0.95
OrderedSet add up to 256 items then delete last 11.735 us/op 12.320 us/op 0.95
Set add up to 256 items then delete middle 7.5206 us/op 7.9579 us/op 0.95
OrderedSet add up to 256 items then delete middle 35.755 us/op 35.906 us/op 1.00
pass gossip attestations to forkchoice per slot 2.4991 ms/op 2.5087 ms/op 1.00
forkChoice updateHead vc 100000 bc 64 eq 0 380.80 us/op 473.57 us/op 0.80
forkChoice updateHead vc 600000 bc 64 eq 0 2.2831 ms/op 2.9030 ms/op 0.79
forkChoice updateHead vc 1000000 bc 64 eq 0 3.7741 ms/op 4.7755 ms/op 0.79
forkChoice updateHead vc 600000 bc 320 eq 0 2.2860 ms/op 2.8800 ms/op 0.79
forkChoice updateHead vc 600000 bc 1200 eq 0 2.2911 ms/op 2.9014 ms/op 0.79
forkChoice updateHead vc 600000 bc 7200 eq 0 2.7943 ms/op 3.2128 ms/op 0.87
forkChoice updateHead vc 600000 bc 64 eq 1000 2.8355 ms/op 3.5144 ms/op 0.81
forkChoice updateHead vc 600000 bc 64 eq 10000 2.9288 ms/op 3.5836 ms/op 0.82
forkChoice updateHead vc 600000 bc 64 eq 300000 6.8120 ms/op 8.2167 ms/op 0.83
computeDeltas 1400000 validators 0% inactive 12.300 ms/op 11.374 ms/op 1.08
computeDeltas 1400000 validators 10% inactive 11.525 ms/op 15.494 ms/op 0.74
computeDeltas 1400000 validators 20% inactive 10.463 ms/op 9.3260 ms/op 1.12
computeDeltas 1400000 validators 50% inactive 8.0787 ms/op 7.1456 ms/op 1.13
computeDeltas 2100000 validators 0% inactive 18.595 ms/op 17.150 ms/op 1.08
computeDeltas 2100000 validators 10% inactive 17.258 ms/op 16.002 ms/op 1.08
computeDeltas 2100000 validators 20% inactive 15.817 ms/op 14.353 ms/op 1.10
computeDeltas 2100000 validators 50% inactive 9.2458 ms/op 10.629 ms/op 0.87
altair processAttestation - 250000 vs - 7PWei normalcase 2.0164 ms/op 1.7701 ms/op 1.14
altair processAttestation - 250000 vs - 7PWei worstcase 2.8688 ms/op 2.4469 ms/op 1.17
altair processAttestation - setStatus - 1/6 committees join 104.67 us/op 112.56 us/op 0.93
altair processAttestation - setStatus - 1/3 committees join 203.67 us/op 201.24 us/op 1.01
altair processAttestation - setStatus - 1/2 committees join 279.22 us/op 283.59 us/op 0.98
altair processAttestation - setStatus - 2/3 committees join 358.58 us/op 370.77 us/op 0.97
altair processAttestation - setStatus - 4/5 committees join 500.23 us/op 525.84 us/op 0.95
altair processAttestation - setStatus - 100% committees join 601.64 us/op 622.09 us/op 0.97
altair processBlock - 250000 vs - 7PWei normalcase 3.9907 ms/op 3.4119 ms/op 1.17
altair processBlock - 250000 vs - 7PWei normalcase hashState 16.326 ms/op 13.664 ms/op 1.19
altair processBlock - 250000 vs - 7PWei worstcase 22.142 ms/op 21.119 ms/op 1.05
altair processBlock - 250000 vs - 7PWei worstcase hashState 44.895 ms/op 41.071 ms/op 1.09
phase0 processBlock - 250000 vs - 7PWei normalcase 1.3397 ms/op 1.3355 ms/op 1.00
phase0 processBlock - 250000 vs - 7PWei worstcase 16.667 ms/op 18.175 ms/op 0.92
altair processEth1Data - 250000 vs - 7PWei normalcase 286.90 us/op 298.13 us/op 0.96
getExpectedWithdrawals 250000 eb:1,eth1:1,we:0,wn:0,smpl:16 4.0760 us/op 7.1520 us/op 0.57
getExpectedWithdrawals 250000 eb:0.95,eth1:0.1,we:0.05,wn:0,smpl:220 21.317 us/op 21.198 us/op 1.01
getExpectedWithdrawals 250000 eb:0.95,eth1:0.3,we:0.05,wn:0,smpl:43 6.1070 us/op 5.8930 us/op 1.04
getExpectedWithdrawals 250000 eb:0.95,eth1:0.7,we:0.05,wn:0,smpl:19 4.0340 us/op 3.8670 us/op 1.04
getExpectedWithdrawals 250000 eb:0.1,eth1:0.1,we:0,wn:0,smpl:1021 92.038 us/op 95.635 us/op 0.96
getExpectedWithdrawals 250000 eb:0.03,eth1:0.03,we:0,wn:0,smpl:11778 1.3274 ms/op 1.4312 ms/op 0.93
getExpectedWithdrawals 250000 eb:0.01,eth1:0.01,we:0,wn:0,smpl:16384 1.7464 ms/op 1.8603 ms/op 0.94
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,smpl:16384 1.7397 ms/op 1.8854 ms/op 0.92
getExpectedWithdrawals 250000 eb:0,eth1:0,we:0,wn:0,nocache,smpl:16384 3.6215 ms/op 3.7379 ms/op 0.97
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,smpl:16384 1.9882 ms/op 2.1337 ms/op 0.93
getExpectedWithdrawals 250000 eb:0,eth1:1,we:0,wn:0,nocache,smpl:16384 3.7823 ms/op 3.9999 ms/op 0.95
Tree 40 250000 create 339.31 ms/op 335.35 ms/op 1.01
Tree 40 250000 get(125000) 88.489 ns/op 103.92 ns/op 0.85
Tree 40 250000 set(125000) 962.54 ns/op 1.0568 us/op 0.91
Tree 40 250000 toArray() 15.286 ms/op 15.904 ms/op 0.96
Tree 40 250000 iterate all - toArray() + loop 16.031 ms/op 15.630 ms/op 1.03
Tree 40 250000 iterate all - get(i) 39.094 ms/op 44.220 ms/op 0.88
Array 250000 create 2.1906 ms/op 2.3790 ms/op 0.92
Array 250000 clone - spread 665.48 us/op 689.20 us/op 0.97
Array 250000 get(125000) 0.28700 ns/op 0.29700 ns/op 0.97
Array 250000 set(125000) 0.29200 ns/op 0.30000 ns/op 0.97
Array 250000 iterate all - loop 57.128 us/op 57.257 us/op 1.00
phase0 afterProcessEpoch - 250000 vs - 7PWei 61.847 ms/op 40.325 ms/op 1.53
Array.fill - length 1000000 2.0763 ms/op 2.1306 ms/op 0.97
Array push - length 1000000 8.7860 ms/op 9.6101 ms/op 0.91
Array.get 0.20040 ns/op 0.21007 ns/op 0.95
Uint8Array.get 0.22817 ns/op 0.24712 ns/op 0.92
phase0 beforeProcessEpoch - 250000 vs - 7PWei 16.187 ms/op 17.669 ms/op 0.92
altair processEpoch - mainnet_e81889 252.94 ms/op 264.54 ms/op 0.96
mainnet_e81889 - altair beforeProcessEpoch 17.836 ms/op 15.838 ms/op 1.13
mainnet_e81889 - altair processJustificationAndFinalization 5.8110 us/op 6.0950 us/op 0.95
mainnet_e81889 - altair processInactivityUpdates 3.3228 ms/op 3.5498 ms/op 0.94
mainnet_e81889 - altair processRewardsAndPenalties 19.186 ms/op 20.016 ms/op 0.96
mainnet_e81889 - altair processRegistryUpdates 521.00 ns/op 526.00 ns/op 0.99
mainnet_e81889 - altair processSlashings 135.00 ns/op 135.00 ns/op 1.00
mainnet_e81889 - altair processEth1DataReset 140.00 ns/op 128.00 ns/op 1.09
mainnet_e81889 - altair processEffectiveBalanceUpdates 1.5281 ms/op 1.8687 ms/op 0.82
mainnet_e81889 - altair processSlashingsReset 655.00 ns/op 698.00 ns/op 0.94
mainnet_e81889 - altair processRandaoMixesReset 1.1090 us/op 1.3010 us/op 0.85
mainnet_e81889 - altair processHistoricalRootsUpdate 140.00 ns/op 134.00 ns/op 1.04
mainnet_e81889 - altair processParticipationFlagUpdates 420.00 ns/op 457.00 ns/op 0.92
mainnet_e81889 - altair processSyncCommitteeUpdates 113.00 ns/op 105.00 ns/op 1.08
mainnet_e81889 - altair afterProcessEpoch 41.191 ms/op 42.739 ms/op 0.96
capella processEpoch - mainnet_e217614 769.75 ms/op 815.96 ms/op 0.94
mainnet_e217614 - capella beforeProcessEpoch 60.655 ms/op 57.873 ms/op 1.05
mainnet_e217614 - capella processJustificationAndFinalization 5.9140 us/op 5.8650 us/op 1.01
mainnet_e217614 - capella processInactivityUpdates 14.341 ms/op 13.070 ms/op 1.10
mainnet_e217614 - capella processRewardsAndPenalties 83.968 ms/op 92.656 ms/op 0.91
mainnet_e217614 - capella processRegistryUpdates 4.1830 us/op 4.4850 us/op 0.93
mainnet_e217614 - capella processSlashings 137.00 ns/op 134.00 ns/op 1.02
mainnet_e217614 - capella processEth1DataReset 140.00 ns/op 129.00 ns/op 1.09
mainnet_e217614 - capella processEffectiveBalanceUpdates 12.702 ms/op 6.2391 ms/op 2.04
mainnet_e217614 - capella processSlashingsReset 658.00 ns/op 742.00 ns/op 0.89
mainnet_e217614 - capella processRandaoMixesReset 1.1210 us/op 1.0140 us/op 1.11
mainnet_e217614 - capella processHistoricalRootsUpdate 134.00 ns/op 132.00 ns/op 1.02
mainnet_e217614 - capella processParticipationFlagUpdates 412.00 ns/op 415.00 ns/op 0.99
mainnet_e217614 - capella afterProcessEpoch 109.82 ms/op 108.86 ms/op 1.01
phase0 processEpoch - mainnet_e58758 288.38 ms/op 281.98 ms/op 1.02
mainnet_e58758 - phase0 beforeProcessEpoch 56.937 ms/op 57.968 ms/op 0.98
mainnet_e58758 - phase0 processJustificationAndFinalization 5.7850 us/op 5.7420 us/op 1.01
mainnet_e58758 - phase0 processRewardsAndPenalties 15.469 ms/op 15.886 ms/op 0.97
mainnet_e58758 - phase0 processRegistryUpdates 2.1850 us/op 2.2380 us/op 0.98
mainnet_e58758 - phase0 processSlashings 128.00 ns/op 132.00 ns/op 0.97
mainnet_e58758 - phase0 processEth1DataReset 127.00 ns/op 133.00 ns/op 0.95
mainnet_e58758 - phase0 processEffectiveBalanceUpdates 795.07 us/op 884.51 us/op 0.90
mainnet_e58758 - phase0 processSlashingsReset 848.00 ns/op 1.0760 us/op 0.79
mainnet_e58758 - phase0 processRandaoMixesReset 1.0770 us/op 1.0200 us/op 1.06
mainnet_e58758 - phase0 processHistoricalRootsUpdate 141.00 ns/op 139.00 ns/op 1.01
mainnet_e58758 - phase0 processParticipationRecordUpdates 1.0230 us/op 1.0090 us/op 1.01
mainnet_e58758 - phase0 afterProcessEpoch 32.606 ms/op 33.373 ms/op 0.98
phase0 processEffectiveBalanceUpdates - 250000 normalcase 934.96 us/op 1.0308 ms/op 0.91
phase0 processEffectiveBalanceUpdates - 250000 worstcase 0.5 1.4552 ms/op 1.6587 ms/op 0.88
altair processInactivityUpdates - 250000 normalcase 12.600 ms/op 10.900 ms/op 1.16
altair processInactivityUpdates - 250000 worstcase 14.935 ms/op 10.713 ms/op 1.39
phase0 processRegistryUpdates - 250000 normalcase 3.0000 us/op 2.2160 us/op 1.35
phase0 processRegistryUpdates - 250000 badcase_full_deposits 145.76 us/op 145.64 us/op 1.00
phase0 processRegistryUpdates - 250000 worstcase 0.5 64.002 ms/op 65.609 ms/op 0.98
altair processRewardsAndPenalties - 250000 normalcase 15.882 ms/op 17.015 ms/op 0.93
altair processRewardsAndPenalties - 250000 worstcase 15.196 ms/op 17.465 ms/op 0.87
phase0 getAttestationDeltas - 250000 normalcase 5.0397 ms/op 5.4637 ms/op 0.92
phase0 getAttestationDeltas - 250000 worstcase 5.1972 ms/op 5.6055 ms/op 0.93
phase0 processSlashings - 250000 worstcase 56.617 us/op 63.695 us/op 0.89
altair processSyncCommitteeUpdates - 250000 9.9309 ms/op 11.153 ms/op 0.89
BeaconState.hashTreeRoot - No change 174.00 ns/op 169.00 ns/op 1.03
BeaconState.hashTreeRoot - 1 full validator 73.278 us/op 86.246 us/op 0.85
BeaconState.hashTreeRoot - 32 full validator 787.82 us/op 859.95 us/op 0.92
BeaconState.hashTreeRoot - 512 full validator 5.9381 ms/op 9.2630 ms/op 0.64
BeaconState.hashTreeRoot - 1 validator.effectiveBalance 80.594 us/op 105.84 us/op 0.76
BeaconState.hashTreeRoot - 32 validator.effectiveBalance 1.2807 ms/op 1.5675 ms/op 0.82
BeaconState.hashTreeRoot - 512 validator.effectiveBalance 13.874 ms/op 21.836 ms/op 0.64
BeaconState.hashTreeRoot - 1 balances 69.194 us/op 86.204 us/op 0.80
BeaconState.hashTreeRoot - 32 balances 663.78 us/op 729.32 us/op 0.91
BeaconState.hashTreeRoot - 512 balances 5.3888 ms/op 7.7510 ms/op 0.70
BeaconState.hashTreeRoot - 250000 balances 124.91 ms/op 143.32 ms/op 0.87
aggregationBits - 2048 els - zipIndexesInBitList 18.944 us/op 20.726 us/op 0.91
regular array get 100000 times 22.125 us/op 23.123 us/op 0.96
wrappedArray get 100000 times 22.231 us/op 23.116 us/op 0.96
arrayWithProxy get 100000 times 9.2001 ms/op 10.170 ms/op 0.90
ssz.Root.equals 20.683 ns/op 21.596 ns/op 0.96
byteArrayEquals 20.562 ns/op 21.305 ns/op 0.97
Buffer.compare 8.9560 ns/op 8.9230 ns/op 1.00
processSlot - 1 slots 8.1120 us/op 10.839 us/op 0.75
processSlot - 32 slots 2.0105 ms/op 2.0690 ms/op 0.97
getEffectiveBalanceIncrementsZeroInactive - 250000 vs - 7PWei 6.2425 ms/op 5.6813 ms/op 1.10
getCommitteeAssignments - req 1 vs - 250000 vc 1.6080 ms/op 1.6713 ms/op 0.96
getCommitteeAssignments - req 100 vs - 250000 vc 3.2850 ms/op 3.4331 ms/op 0.96
getCommitteeAssignments - req 1000 vs - 250000 vc 3.5333 ms/op 3.7270 ms/op 0.95
findModifiedValidators - 10000 modified validators 736.84 ms/op 834.89 ms/op 0.88
findModifiedValidators - 1000 modified validators 478.49 ms/op 583.13 ms/op 0.82
findModifiedValidators - 100 modified validators 294.13 ms/op 338.96 ms/op 0.87
findModifiedValidators - 10 modified validators 210.36 ms/op 275.74 ms/op 0.76
findModifiedValidators - 1 modified validators 146.46 ms/op 230.09 ms/op 0.64
findModifiedValidators - no difference 133.92 ms/op 205.33 ms/op 0.65
migrate state 1500000 validators, 3400 modified, 2000 new 3.3802 s/op 3.4932 s/op 0.97
RootCache.getBlockRootAtSlot - 250000 vs - 7PWei 3.6600 ns/op 3.7400 ns/op 0.98
state getBlockRootAtSlot - 250000 vs - 7PWei 435.33 ns/op 420.84 ns/op 1.03
computeProposerIndex 100000 validators 1.3596 ms/op 1.3857 ms/op 0.98
getNextSyncCommitteeIndices 1000 validators 2.9039 ms/op 2.9188 ms/op 0.99
getNextSyncCommitteeIndices 10000 validators 25.426 ms/op 25.545 ms/op 1.00
getNextSyncCommitteeIndices 100000 validators 88.932 ms/op 89.577 ms/op 0.99
computeProposers - vc 250000 664.51 us/op 575.95 us/op 1.15
computeEpochShuffling - vc 250000 39.972 ms/op 40.208 ms/op 0.99
getNextSyncCommittee - vc 250000 11.045 ms/op 9.6300 ms/op 1.15
nodejs block root to RootHex using toHex 92.482 ns/op 102.08 ns/op 0.91
nodejs block root to RootHex using toRootHex 56.739 ns/op 61.734 ns/op 0.92
nodejs fromHex(blob) 843.66 us/op 851.41 us/op 0.99
nodejs fromHexInto(blob) 615.07 us/op 674.23 us/op 0.91
nodejs block root to RootHex using the deprecated toHexString 463.73 ns/op 485.59 ns/op 0.95
nodejs byteArrayEquals 32 bytes (block root) 25.596 ns/op 25.994 ns/op 0.98
nodejs byteArrayEquals 48 bytes (pubkey) 37.103 ns/op 37.502 ns/op 0.99
nodejs byteArrayEquals 96 bytes (signature) 33.762 ns/op 36.336 ns/op 0.93
nodejs byteArrayEquals 1024 bytes 39.765 ns/op 44.895 ns/op 0.89
nodejs byteArrayEquals 131072 bytes (blob) 1.7673 us/op 1.7596 us/op 1.00
browser block root to RootHex using toHex 143.80 ns/op 145.61 ns/op 0.99
browser block root to RootHex using toRootHex 129.81 ns/op 131.42 ns/op 0.99
browser fromHex(blob) 1.5497 ms/op 1.8491 ms/op 0.84
browser fromHexInto(blob) 625.32 us/op 663.96 us/op 0.94
browser block root to RootHex using the deprecated toHexString 326.86 ns/op 344.39 ns/op 0.95
browser byteArrayEquals 32 bytes (block root) 28.093 ns/op 27.960 ns/op 1.00
browser byteArrayEquals 48 bytes (pubkey) 39.769 ns/op 39.453 ns/op 1.01
browser byteArrayEquals 96 bytes (signature) 73.660 ns/op 74.193 ns/op 0.99
browser byteArrayEquals 1024 bytes 738.75 ns/op 758.95 ns/op 0.97
browser byteArrayEquals 131072 bytes (blob) 95.438 us/op 95.788 us/op 1.00

by benchmarkbot/action

@lodekeeper
Copy link
Copy Markdown
Contributor

Cross-linking from #9246: this PR addresses the API-path memory spike, but there is a separate correctness bug in the same loadState() path that this PR does not cover.

The cache-aliasing bug in loadState()

The default clone() at loadState.ts:114 and :191 transfers the seed sub-view's cache to the migrated sub-view — they share the same internal nodes[] / caches[] arrays. A subsequent migratedState.commit() writes modified validator / inactivity-score nodes into those shared arrays, silently corrupting the seed state's cache snapshot at the modified indices. The corruption surfaces on the next seedState.clone({dontTransferCache: false}) (the verifyBlock / getPreState path) as a Withdrawal mismatch at index=0 divergence.

Why the preload gating in this PR does not fix it

The poisoning happens inside migratedState.commit() at loadState.ts:64, which runs before the preload branch that this PR gates. Timeline:

  1. migratedState.validators = seedState.validators.clone() → migrated's nodes is aliased to H.caches[validatorsIndex].nodes.
  2. migrated.validators.set(i, newValidator) → staged in viewsChanged.
  3. migratedState.commit()arrayComposite.js executes this.nodes[index] = node → writes into the shared array. H's cache is already poisoned here.
  4. This PR's change: skip getAllReadonlyValues(). But step 3 already corrupted the cache — the preload's populateAllNodes() actually rebinds migrated.nodes to a fresh array, which if anything hides the issue in the loadOtherState path. Skipping it does not un-poison H's arrays.

So this PR correctly fixes the memory spike but leaves the correctness bug untouched. Also relevant: persistentCheckpointsCache exercises loadState() with the same problematic clone() and doesn't touch loadOtherState at all.

Fix

#9246 (two-line clone(true)) addresses the correctness bug. Both fixes are independent and both needed.

Copy link
Copy Markdown
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.

LGTM. Surgical fix: gates the eager getAllReadonlyValues() / getAll() preload behind an opt-in flag and only persistentCheckpointsCache (which always consumes the state immediately via block replay) sets it.

Correctness: Interface + impl + single call-site change are consistent. All six other loadOtherState() callers (api/impl/beacon/state, api/impl/validator, api/impl/proof, api/impl/lodestar × 2, plus the unit test) now revert to lazy materialization — restores pre-#8857 semantics on API paths. Optional arg keeps the signature backwards-compatible.

Risk: Low. This just narrows an existing optimization to where it's actually needed.

Independence from #9246: Confirmed — this PR doesn't touch loadState() where the cache-aliasing bug lives, so the two fixes address distinct v1.42.0 regressions (memory spike vs. withdrawal mismatch). Both are needed for the public-node redeploy.

Nice minimal fix 👍

@nflaig nflaig merged commit abc719d into unstable Apr 21, 2026
19 of 20 checks passed
@nflaig nflaig deleted the te/fix_loadOtherState branch April 21, 2026 18:14
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 21, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 52.53%. Comparing base (9fa9f08) to head (aedc3db).
⚠️ Report is 3 commits behind head on unstable.

Additional details and impacted files
@@            Coverage Diff            @@
##           unstable    #9245   +/-   ##
=========================================
  Coverage     52.53%   52.53%           
=========================================
  Files           848      848           
  Lines         61405    61405           
  Branches       4525     4525           
=========================================
  Hits          32259    32259           
  Misses        29081    29081           
  Partials         65       65           
🚀 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.

3 participants