@lodestar/state-transition 1.45.0-dev.1588281853 → 1.45.0-dev.17a48050e6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/lib/block/processOperations.d.ts.map +1 -1
  2. package/lib/block/processOperations.js +17 -1
  3. package/lib/block/processOperations.js.map +1 -1
  4. package/lib/block/processParentExecutionPayload.d.ts.map +1 -1
  5. package/lib/block/processParentExecutionPayload.js +15 -1
  6. package/lib/block/processParentExecutionPayload.js.map +1 -1
  7. package/lib/block/processWithdrawals.js +1 -1
  8. package/lib/block/processWithdrawals.js.map +1 -1
  9. package/lib/cache/stateCache.d.ts.map +1 -1
  10. package/lib/cache/stateCache.js +2 -4
  11. package/lib/cache/stateCache.js.map +1 -1
  12. package/lib/epoch/processParticipationFlagUpdates.d.ts +2 -2
  13. package/lib/epoch/processParticipationFlagUpdates.d.ts.map +1 -1
  14. package/lib/epoch/processParticipationFlagUpdates.js +13 -0
  15. package/lib/epoch/processParticipationFlagUpdates.js.map +1 -1
  16. package/lib/epoch/processRewardsAndPenalties.d.ts.map +1 -1
  17. package/lib/epoch/processRewardsAndPenalties.js +1 -2
  18. package/lib/epoch/processRewardsAndPenalties.js.map +1 -1
  19. package/lib/lightClient/proofs.d.ts.map +1 -1
  20. package/lib/lightClient/proofs.js +28 -2
  21. package/lib/lightClient/proofs.js.map +1 -1
  22. package/lib/lightClient/spec/index.d.ts.map +1 -1
  23. package/lib/lightClient/spec/index.js +7 -4
  24. package/lib/lightClient/spec/index.js.map +1 -1
  25. package/lib/lightClient/spec/utils.d.ts +7 -1
  26. package/lib/lightClient/spec/utils.d.ts.map +1 -1
  27. package/lib/lightClient/spec/utils.js +123 -14
  28. package/lib/lightClient/spec/utils.js.map +1 -1
  29. package/lib/lightClient/spec/validateLightClientBootstrap.d.ts.map +1 -1
  30. package/lib/lightClient/spec/validateLightClientBootstrap.js +2 -7
  31. package/lib/lightClient/spec/validateLightClientBootstrap.js.map +1 -1
  32. package/lib/lightClient/spec/validateLightClientUpdate.d.ts.map +1 -1
  33. package/lib/lightClient/spec/validateLightClientUpdate.js +6 -5
  34. package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -1
  35. package/lib/lightClient/types.d.ts +3 -1
  36. package/lib/lightClient/types.d.ts.map +1 -1
  37. package/lib/metrics.js +2 -4
  38. package/lib/metrics.js.map +1 -1
  39. package/lib/slot/upgradeStateToGloas.d.ts.map +1 -1
  40. package/lib/slot/upgradeStateToGloas.js +48 -9
  41. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  42. package/lib/stateView/beaconStateView.d.ts +1 -1
  43. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  44. package/lib/stateView/beaconStateView.js +6 -8
  45. package/lib/stateView/beaconStateView.js.map +1 -1
  46. package/lib/stateView/interface.d.ts +1 -1
  47. package/lib/stateView/interface.d.ts.map +1 -1
  48. package/lib/stateView/nativeBeaconStateView.d.ts +2 -2
  49. package/lib/stateView/nativeBeaconStateView.d.ts.map +1 -1
  50. package/lib/stateView/nativeBeaconStateView.js +7 -9
  51. package/lib/stateView/nativeBeaconStateView.js.map +1 -1
  52. package/lib/util/execution.d.ts.map +1 -1
  53. package/lib/util/execution.js +7 -2
  54. package/lib/util/execution.js.map +1 -1
  55. package/lib/util/index.d.ts +1 -0
  56. package/lib/util/index.d.ts.map +1 -1
  57. package/lib/util/index.js +1 -0
  58. package/lib/util/index.js.map +1 -1
  59. package/lib/util/ssz.d.ts +9 -0
  60. package/lib/util/ssz.d.ts.map +1 -0
  61. package/lib/util/ssz.js +39 -0
  62. package/lib/util/ssz.js.map +1 -0
  63. package/package.json +10 -10
  64. package/src/block/processOperations.ts +28 -1
  65. package/src/block/processParentExecutionPayload.ts +26 -1
  66. package/src/block/processWithdrawals.ts +1 -1
  67. package/src/cache/stateCache.ts +2 -4
  68. package/src/epoch/processParticipationFlagUpdates.ts +20 -2
  69. package/src/epoch/processRewardsAndPenalties.ts +1 -2
  70. package/src/lightClient/proofs.ts +36 -1
  71. package/src/lightClient/spec/index.ts +8 -4
  72. package/src/lightClient/spec/utils.ts +193 -25
  73. package/src/lightClient/spec/validateLightClientBootstrap.ts +3 -10
  74. package/src/lightClient/spec/validateLightClientUpdate.ts +11 -21
  75. package/src/lightClient/types.ts +3 -1
  76. package/src/metrics.ts +2 -4
  77. package/src/slot/upgradeStateToGloas.ts +84 -9
  78. package/src/stateView/beaconStateView.ts +8 -8
  79. package/src/stateView/interface.ts +1 -1
  80. package/src/stateView/nativeBeaconStateView.ts +8 -10
  81. package/src/util/execution.ts +9 -4
  82. package/src/util/index.ts +1 -0
  83. package/src/util/ssz.ts +42 -0
package/src/metrics.ts CHANGED
@@ -159,11 +159,9 @@ export function onPostStateMetrics(postState: CachedBeaconStateAllForks, metrics
159
159
  // This cache is populated during epoch transition, and should be preserved for performance.
160
160
  // If the cache is missing too often, means that our clone strategy is not working well.
161
161
  function isValidatorsNodesPopulated(state: CachedBeaconStateAllForks): boolean {
162
- // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute
163
- return state.validators["nodesPopulated"] === true;
162
+ return (state.validators as unknown as {nodesPopulated?: boolean}).nodesPopulated === true;
164
163
  }
165
164
 
166
165
  function isBalancesNodesPopulated(state: CachedBeaconStateAllForks): boolean {
167
- // biome-ignore lint/complexity/useLiteralKeys: It is a private attribute
168
- return state.balances["nodesPopulated"] === true;
166
+ return (state.balances as unknown as {nodesPopulated?: boolean}).nodesPopulated === true;
169
167
  }
@@ -1,3 +1,15 @@
1
+ import {getNodesAtDepth} from "@chainsafe/persistent-merkle-tree";
2
+ import {
3
+ BasicType,
4
+ CompositeType,
5
+ CompositeView,
6
+ CompositeViewDU,
7
+ ListBasicTreeViewDU,
8
+ ListCompositeTreeViewDU,
9
+ ProgressiveListBasicType,
10
+ ProgressiveListCompositeType,
11
+ ValueOf,
12
+ } from "@chainsafe/ssz";
1
13
  import {PAYLOAD_BUILDER_VERSION, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
2
14
  import {ssz} from "@lodestar/types";
3
15
  import {toPubkeyHex} from "@lodestar/utils";
@@ -7,6 +19,7 @@ import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
7
19
  import {addBuilderToRegistry, initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js";
8
20
  import {isValidatorKnown} from "../util/index.js";
9
21
  import {PendingDepositsLookup} from "../util/pendingDepositsLookup.js";
22
+ import {progressiveListRootNode} from "../util/ssz.js";
10
23
 
11
24
  /**
12
25
  * Upgrade a state from Fulu to Gloas.
@@ -34,17 +47,26 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
34
47
  stateGloasView.eth1Data = stateGloasCloned.eth1Data;
35
48
  stateGloasView.eth1DataVotes = stateGloasCloned.eth1DataVotes;
36
49
  stateGloasView.eth1DepositIndex = stateGloasCloned.eth1DepositIndex;
37
- stateGloasView.validators = stateGloasCloned.validators;
38
- stateGloasView.balances = stateGloasCloned.balances;
50
+ stateGloasView.validators = migrateCompositeListToGloas(stateGloasCloned.validators, ssz.gloas.Validators);
51
+ stateGloasView.balances = migrateBasicListToGloas(stateGloasCloned.balances, ssz.gloas.Balances);
39
52
  stateGloasView.randaoMixes = stateGloasCloned.randaoMixes;
40
53
  stateGloasView.slashings = stateGloasCloned.slashings;
41
- stateGloasView.previousEpochParticipation = stateGloasCloned.previousEpochParticipation;
42
- stateGloasView.currentEpochParticipation = stateGloasCloned.currentEpochParticipation;
54
+ stateGloasView.previousEpochParticipation = migrateBasicListToGloas(
55
+ stateGloasCloned.previousEpochParticipation,
56
+ ssz.gloas.EpochParticipation
57
+ );
58
+ stateGloasView.currentEpochParticipation = migrateBasicListToGloas(
59
+ stateGloasCloned.currentEpochParticipation,
60
+ ssz.gloas.EpochParticipation
61
+ );
43
62
  stateGloasView.justificationBits = stateGloasCloned.justificationBits;
44
63
  stateGloasView.previousJustifiedCheckpoint = stateGloasCloned.previousJustifiedCheckpoint;
45
64
  stateGloasView.currentJustifiedCheckpoint = stateGloasCloned.currentJustifiedCheckpoint;
46
65
  stateGloasView.finalizedCheckpoint = stateGloasCloned.finalizedCheckpoint;
47
- stateGloasView.inactivityScores = stateGloasCloned.inactivityScores;
66
+ stateGloasView.inactivityScores = migrateBasicListToGloas(
67
+ stateGloasCloned.inactivityScores,
68
+ ssz.gloas.InactivityScores
69
+ );
48
70
  stateGloasView.currentSyncCommittee = stateGloasCloned.currentSyncCommittee;
49
71
  stateGloasView.nextSyncCommittee = stateGloasCloned.nextSyncCommittee;
50
72
  stateGloasView.latestExecutionPayloadBid.blockHash = stateFulu.latestExecutionPayloadHeader.blockHash;
@@ -61,9 +83,18 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
61
83
  stateGloasView.earliestExitEpoch = stateGloasCloned.earliestExitEpoch;
62
84
  stateGloasView.consolidationBalanceToConsume = stateGloasCloned.consolidationBalanceToConsume;
63
85
  stateGloasView.earliestConsolidationEpoch = stateGloasCloned.earliestConsolidationEpoch;
64
- stateGloasView.pendingDeposits = stateGloasCloned.pendingDeposits;
65
- stateGloasView.pendingPartialWithdrawals = stateGloasCloned.pendingPartialWithdrawals;
66
- stateGloasView.pendingConsolidations = stateGloasCloned.pendingConsolidations;
86
+ stateGloasView.pendingDeposits = migrateCompositeListToGloas(
87
+ stateGloasCloned.pendingDeposits,
88
+ ssz.gloas.PendingDeposits
89
+ );
90
+ stateGloasView.pendingPartialWithdrawals = migrateCompositeListToGloas(
91
+ stateGloasCloned.pendingPartialWithdrawals,
92
+ ssz.gloas.PendingPartialWithdrawals
93
+ );
94
+ stateGloasView.pendingConsolidations = migrateCompositeListToGloas(
95
+ stateGloasCloned.pendingConsolidations,
96
+ ssz.gloas.PendingConsolidations
97
+ );
67
98
  stateGloasView.proposerLookahead = stateGloasCloned.proposerLookahead;
68
99
  stateGloasView.ptcWindow = ssz.gloas.PtcWindow.toViewDU(initializePtcWindow(stateFulu));
69
100
 
@@ -85,6 +116,50 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
85
116
  return stateGloas;
86
117
  }
87
118
 
119
+ /**
120
+ * Migrate a composite list from fulu to its gloas progressive-list equivalent by reusing the fulu
121
+ * list's element nodes.
122
+ *
123
+ * Works whenever the element type is identical across the fork (e.g. validators use ValidatorNodeStruct,
124
+ * the pending* queues use the same electra element types). Each element's cached subtree root is then
125
+ * valid under gloas, so only the progressive list superstructure is rebuilt and a subsequent
126
+ * hashTreeRoot() skips re-hashing every element — the dominant cost for large lists like validators.
127
+ * Much cheaper than `gloasType.toViewDU(fuluList.getAllReadonlyValues())`, which decodes every element
128
+ * to a value and forces a full re-hash.
129
+ *
130
+ * The chunk nodes of a composite list ARE the element root nodes, so they are extracted directly
131
+ * with getNodesAtDepth instead of allocating a temporary ViewDU wrapper per element (getAllReadonly).
132
+ * Requires the fulu view to be committed (done at the top of upgradeStateToGloas).
133
+ */
134
+ function migrateCompositeListToGloas<
135
+ ElementType extends CompositeType<ValueOf<ElementType>, CompositeView<ElementType>, CompositeViewDU<ElementType>>,
136
+ >(fuluList: ListCompositeTreeViewDU<ElementType>, gloasType: ProgressiveListCompositeType<ElementType>) {
137
+ const {length, type} = fuluList;
138
+ const elementNodes = getNodesAtDepth(fuluList.node.left, type.chunkDepth, 0, length);
139
+ return gloasType.getViewDU(progressiveListRootNode(elementNodes, length));
140
+ }
141
+
142
+ /**
143
+ * Migrate a basic list from fulu to its gloas progressive-list equivalent by reusing the fulu
144
+ * list's packed chunk leaf nodes.
145
+ *
146
+ * Packed leaf chunks are bit-identical between List[T, N] and ProgressiveList[T] (same 32-byte
147
+ * LE packing, zero-padded final chunk); only the superstructure above the leaves differs. Reusing
148
+ * the leaves avoids materializing the value array (getAll), re-serializing it, and allocating
149
+ * fresh LeafNodes — the gloas tree shares the leaf nodes with the fulu tree.
150
+ * Requires the fulu view to be committed (done at the top of upgradeStateToGloas).
151
+ */
152
+ function migrateBasicListToGloas<ElementType extends BasicType<unknown>>(
153
+ fuluList: ListBasicTreeViewDU<ElementType>,
154
+ gloasType: ProgressiveListBasicType<ElementType>
155
+ ) {
156
+ const {length, type} = fuluList;
157
+ const chunkCount = Math.ceil(length / type.itemsPerChunk);
158
+ // List root = BranchNode(chunksNode, lengthNode) → chunks tree is the left child
159
+ const chunkLeafNodes = getNodesAtDepth(fuluList.node.left, type.chunkDepth, 0, chunkCount);
160
+ return gloasType.getViewDU(progressiveListRootNode(chunkLeafNodes, length));
161
+ }
162
+
88
163
  /**
89
164
  * Applies any pending deposits for builders to onboard builders during the fork transition
90
165
  * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.8/specs/gloas/fork.md#new-onboard_builders_from_pending_deposits
@@ -94,7 +169,7 @@ function onboardBuildersFromPendingDeposits(state: CachedBeaconStateGloas): void
94
169
  // at the fork, so every builder pubkey here is one added in an earlier iteration.
95
170
  const builderPubkeys = new Set<string>();
96
171
 
97
- const pendingDeposits = ssz.electra.PendingDeposits.defaultViewDU();
172
+ const pendingDeposits = ssz.gloas.PendingDeposits.defaultViewDU();
98
173
  const pendingDepositsLookup = PendingDepositsLookup.buildEmpty();
99
174
 
100
175
  for (let i = 0; i < state.pendingDeposits.length; i++) {
@@ -398,6 +398,14 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
398
398
  return (this.cachedState as CachedBeaconStateGloas).builders.getReadonly(index);
399
399
  }
400
400
 
401
+ getBuildersLength(): number {
402
+ if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
403
+ throw new Error("Builders are not supported before Gloas");
404
+ }
405
+
406
+ return (this.cachedState as CachedBeaconStateGloas).builders.length;
407
+ }
408
+
401
409
  canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean {
402
410
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
403
411
  throw new Error("Builders are not supported before Gloas");
@@ -507,14 +515,6 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
507
515
  return this.cachedState.epochCtx.getBeaconProposer(slot);
508
516
  }
509
517
 
510
- getBeaconProposerOrNull(slot: Slot): ValidatorIndex | null {
511
- try {
512
- return this.cachedState.epochCtx.getBeaconProposer(slot);
513
- } catch {
514
- return null;
515
- }
516
- }
517
-
518
518
  computeAnchorCheckpoint(): {checkpoint: phase0.Checkpoint; blockHeader: phase0.BeaconBlockHeader} {
519
519
  return computeAnchorCheckpoint(this.config, this.cachedState);
520
520
  }
@@ -92,7 +92,6 @@ export interface IBeaconStateView {
92
92
  currentProposers: ValidatorIndex[];
93
93
  nextProposers: ValidatorIndex[];
94
94
  getBeaconProposer(slot: Slot): ValidatorIndex;
95
- getBeaconProposerOrNull(slot: Slot): ValidatorIndex | null;
96
95
 
97
96
  // Validators and balances
98
97
  effectiveBalanceIncrements: EffectiveBalanceIncrements;
@@ -255,6 +254,7 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
255
254
  latestExecutionPayloadBid: ExecutionPayloadBid;
256
255
  payloadExpectedWithdrawals: capella.Withdrawal[];
257
256
  getBuilder(index: BuilderIndex): gloas.Builder;
257
+ getBuildersLength(): number;
258
258
  canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean;
259
259
  getEpochPTCs(epoch: Epoch): Uint32Array[];
260
260
  getIndicesInPayloadTimelinessCommittee(validatorIndex: ValidatorIndex, slot: Slot): number[];
@@ -128,8 +128,6 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
128
128
  private readonly _getBeaconCommitteeCountPerSlot = new Map<Epoch, number>();
129
129
  private readonly _getShufflingDecisionRoot = new Map<Epoch, RootHex>();
130
130
  private readonly _getBeaconProposer = new Map<Slot, ValidatorIndex>();
131
- // getBeaconProposerOrNull can return null, so use .has() to distinguish "not cached" from "cached null"
132
- private readonly _getBeaconProposerOrNull = new Map<Slot, ValidatorIndex | null>();
133
131
  private readonly _getValidator = new Map<ValidatorIndex, phase0.Validator>();
134
132
  private readonly _getBalance = new Map<number, number>();
135
133
  private readonly _getIndexedSyncCommitteeAtEpoch = new Map<Epoch, SyncCommitteeCache>();
@@ -144,6 +142,7 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
144
142
  private _getNextShuffling: EpochShuffling | null = null;
145
143
  private _getEffectiveBalanceIncrementsZeroInactive: EffectiveBalanceIncrements | null = null;
146
144
  private _getAllValidators: phase0.Validator[] | null = null;
145
+ private _getBuildersLength: number | null = null;
147
146
  private _getAllBalances: number[] | null = null;
148
147
  private _getLatestWeakSubjectivityCheckpointEpoch: Epoch | null = null;
149
148
  private _getFinalizedRootProof: Uint8Array[] | null = null;
@@ -410,14 +409,6 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
410
409
  return cached;
411
410
  }
412
411
 
413
- getBeaconProposerOrNull(slot: Slot): ValidatorIndex | null {
414
- if (!this._getBeaconProposerOrNull.has(slot)) {
415
- this._getBeaconProposerOrNull.set(slot, this.binding.getBeaconProposerOrNull(slot));
416
- }
417
- // biome-ignore lint/style/noNonNullAssertion: has() check guarantees a value
418
- return this._getBeaconProposerOrNull.get(slot)!;
419
- }
420
-
421
412
  // Validators and balances
422
413
 
423
414
  get effectiveBalanceIncrements(): EffectiveBalanceIncrements {
@@ -888,6 +879,13 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
888
879
  return cached;
889
880
  }
890
881
 
882
+ getBuildersLength(): number {
883
+ if (this._getBuildersLength === null) {
884
+ this._getBuildersLength = this.binding.getBuildersLength();
885
+ }
886
+ return this._getBuildersLength;
887
+ }
888
+
891
889
  canBuilderCoverBid(builderIndex: BuilderIndex, bidAmount: number): boolean {
892
890
  return this.binding.canBuilderCoverBid(builderIndex, bidAmount);
893
891
  }
@@ -9,6 +9,7 @@ import {
9
9
  bellatrix,
10
10
  capella,
11
11
  deneb,
12
+ gloas,
12
13
  isBlindedBeaconBlockBody,
13
14
  isExecutionPayload,
14
15
  ssz,
@@ -127,7 +128,10 @@ export function isCapellaPayloadHeader(
127
128
  }
128
129
 
129
130
  export function executionPayloadToPayloadHeader(fork: ForkSeq, payload: ExecutionPayload): ExecutionPayloadHeader {
130
- const transactionsRoot = ssz.bellatrix.Transactions.hashTreeRoot(payload.transactions);
131
+ const transactionsRoot =
132
+ fork >= ForkSeq.gloas
133
+ ? ssz.gloas.Transactions.hashTreeRoot((payload as gloas.ExecutionPayload).transactions)
134
+ : ssz.bellatrix.Transactions.hashTreeRoot(payload.transactions);
131
135
 
132
136
  const bellatrixPayloadFields: ExecutionPayloadHeader = {
133
137
  parentHash: payload.parentHash,
@@ -147,9 +151,10 @@ export function executionPayloadToPayloadHeader(fork: ForkSeq, payload: Executio
147
151
  };
148
152
 
149
153
  if (fork >= ForkSeq.capella) {
150
- (bellatrixPayloadFields as capella.ExecutionPayloadHeader).withdrawalsRoot = ssz.capella.Withdrawals.hashTreeRoot(
151
- (payload as capella.ExecutionPayload).withdrawals
152
- );
154
+ (bellatrixPayloadFields as capella.ExecutionPayloadHeader).withdrawalsRoot =
155
+ fork >= ForkSeq.gloas
156
+ ? ssz.gloas.Withdrawals.hashTreeRoot((payload as gloas.ExecutionPayload).withdrawals)
157
+ : ssz.capella.Withdrawals.hashTreeRoot((payload as capella.ExecutionPayload).withdrawals);
153
158
  }
154
159
 
155
160
  if (fork >= ForkSeq.deneb) {
package/src/util/index.ts CHANGED
@@ -25,6 +25,7 @@ export * from "./shuffling.js";
25
25
  export * from "./signatureSets.js";
26
26
  export * from "./signingRoot.js";
27
27
  export * from "./slot.js";
28
+ export * from "./ssz.js";
28
29
  export * from "./syncCommittee.js";
29
30
  export * from "./validator.js";
30
31
  export * from "./weakSubjectivity.js";
@@ -0,0 +1,42 @@
1
+ import {BranchNode, LeafNode, Node, zeroNode} from "@chainsafe/persistent-merkle-tree";
2
+ import {progressiveSubtreeFillToContents} from "@chainsafe/ssz";
3
+
4
+ // TODO: move these utils to @chainsafe/ssz (progressive.ts, next to progressiveSubtreeFillToContents)
5
+
6
+ /** Root node (chunks + length mix-in) of a zero-filled ProgressiveListBasicType of `length` items */
7
+ export function zeroProgressiveListBasicRootNode(itemsPerChunk: number, length: number): Node {
8
+ const chunkCount = Math.ceil(length / itemsPerChunk);
9
+ // mirrors ssz's progressiveSubtreeCount (subtree capacities 1, 4, 16, ...)
10
+ let numSubtrees = 0;
11
+ for (let remaining = chunkCount, subtreeLength = 1; remaining > 0; subtreeLength *= 4) {
12
+ remaining -= Math.min(remaining, subtreeLength);
13
+ numSubtrees++;
14
+ }
15
+ return new BranchNode(zeroProgressiveNode(numSubtrees), LeafNode.fromUint32(length));
16
+ }
17
+
18
+ /**
19
+ * Root node of a progressive list from its chunk/element nodes + length mix-in.
20
+ * `nodes` are packed 32-byte chunk leaves for basic lists, or element root nodes for composite lists.
21
+ */
22
+ export function progressiveListRootNode(nodes: Node[], length: number): Node {
23
+ return new BranchNode(progressiveSubtreeFillToContents(nodes), LeafNode.fromUint32(length));
24
+ }
25
+
26
+ /**
27
+ * Return the chunks node of a zero-filled progressive merkle list spanning `numSubtrees`
28
+ * balanced subtrees (chunk capacities 1, 4, 16, ... = 4^i; depths 0, 2, 4, ... = 2i).
29
+ *
30
+ * Not memoized: the zero data is already fully shared via the cached zeroNode(2i) subtrees;
31
+ * only the O(numSubtrees) spine BranchNodes (~10 at 2M validators) are allocated per call.
32
+ */
33
+ function zeroProgressiveNode(numSubtrees: number): Node {
34
+ // chain(k) = B(zeroNode(0), B(zeroNode(2), ... B(zeroNode(2(k-1)), zeroNode(0))))
35
+ let node: Node = zeroNode(0); // terminator
36
+ for (let i = numSubtrees - 1; i >= 0; i--) {
37
+ // BranchNode(left: balanced subtree i, right: rest of chain) — chain grows to the right,
38
+ // same as ssz progressiveSubtreeFillToContents: `root = new BranchNode(subtreeRoots[i], root)`
39
+ node = new BranchNode(zeroNode(2 * i), node);
40
+ }
41
+ return node;
42
+ }