@lodestar/state-transition 1.48.0-dev.f22c5ce63e → 1.48.0-rc.0

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 (94) hide show
  1. package/lib/block/index.d.ts.map +1 -1
  2. package/lib/block/index.js +44 -13
  3. package/lib/block/index.js.map +1 -1
  4. package/lib/block/processExecutionPayloadBid.d.ts +2 -2
  5. package/lib/block/processExecutionPayloadBid.d.ts.map +1 -1
  6. package/lib/block/processExecutionPayloadBid.js +3 -2
  7. package/lib/block/processExecutionPayloadBid.js.map +1 -1
  8. package/lib/block/processOperations.d.ts.map +1 -1
  9. package/lib/block/processOperations.js +62 -33
  10. package/lib/block/processOperations.js.map +1 -1
  11. package/lib/block/processParentExecutionPayload.d.ts.map +1 -1
  12. package/lib/block/processParentExecutionPayload.js +2 -16
  13. package/lib/block/processParentExecutionPayload.js.map +1 -1
  14. package/lib/block/processPayloadAttestation.d.ts +1 -1
  15. package/lib/block/processPayloadAttestation.d.ts.map +1 -1
  16. package/lib/block/processPayloadAttestation.js +2 -2
  17. package/lib/block/processPayloadAttestation.js.map +1 -1
  18. package/lib/block/processWithdrawals.d.ts.map +1 -1
  19. package/lib/block/processWithdrawals.js +8 -2
  20. package/lib/block/processWithdrawals.js.map +1 -1
  21. package/lib/block/types.d.ts +30 -0
  22. package/lib/block/types.d.ts.map +1 -1
  23. package/lib/block/types.js +32 -0
  24. package/lib/block/types.js.map +1 -1
  25. package/lib/epoch/processInactivityUpdates.d.ts.map +1 -1
  26. package/lib/epoch/processInactivityUpdates.js +7 -1
  27. package/lib/epoch/processInactivityUpdates.js.map +1 -1
  28. package/lib/epoch/processPtcWindow.d.ts.map +1 -1
  29. package/lib/epoch/processPtcWindow.js +12 -9
  30. package/lib/epoch/processPtcWindow.js.map +1 -1
  31. package/lib/index.d.ts +1 -1
  32. package/lib/index.d.ts.map +1 -1
  33. package/lib/index.js +1 -1
  34. package/lib/index.js.map +1 -1
  35. package/lib/lightClient/spec/index.d.ts +1 -1
  36. package/lib/lightClient/spec/index.d.ts.map +1 -1
  37. package/lib/lightClient/spec/index.js +1 -1
  38. package/lib/lightClient/spec/index.js.map +1 -1
  39. package/lib/lightClient/spec/utils.d.ts +13 -3
  40. package/lib/lightClient/spec/utils.d.ts.map +1 -1
  41. package/lib/lightClient/spec/utils.js +108 -34
  42. package/lib/lightClient/spec/utils.js.map +1 -1
  43. package/lib/metrics.d.ts +7 -1
  44. package/lib/metrics.d.ts.map +1 -1
  45. package/lib/metrics.js +12 -1
  46. package/lib/metrics.js.map +1 -1
  47. package/lib/rewards/blockRewards.d.ts.map +1 -1
  48. package/lib/rewards/blockRewards.js +1 -3
  49. package/lib/rewards/blockRewards.js.map +1 -1
  50. package/lib/signatureSets/index.d.ts.map +1 -1
  51. package/lib/signatureSets/index.js +8 -0
  52. package/lib/signatureSets/index.js.map +1 -1
  53. package/lib/slot/upgradeStateToGloas.js +1 -1
  54. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  55. package/lib/stateView/beaconStateView.d.ts +6 -1
  56. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  57. package/lib/stateView/beaconStateView.js +12 -0
  58. package/lib/stateView/beaconStateView.js.map +1 -1
  59. package/lib/stateView/interface.d.ts +3 -1
  60. package/lib/stateView/interface.d.ts.map +1 -1
  61. package/lib/stateView/interface.js.map +1 -1
  62. package/lib/stateView/nativeBeaconStateView.d.ts +4 -1
  63. package/lib/stateView/nativeBeaconStateView.d.ts.map +1 -1
  64. package/lib/stateView/nativeBeaconStateView.js +10 -0
  65. package/lib/stateView/nativeBeaconStateView.js.map +1 -1
  66. package/lib/util/gloas.d.ts.map +1 -1
  67. package/lib/util/gloas.js +1 -1
  68. package/lib/util/gloas.js.map +1 -1
  69. package/lib/util/seed.d.ts +1 -1
  70. package/lib/util/seed.d.ts.map +1 -1
  71. package/lib/util/seed.js +14 -11
  72. package/lib/util/seed.js.map +1 -1
  73. package/package.json +8 -8
  74. package/src/block/index.ts +53 -17
  75. package/src/block/processExecutionPayloadBid.ts +6 -5
  76. package/src/block/processOperations.ts +63 -45
  77. package/src/block/processParentExecutionPayload.ts +2 -27
  78. package/src/block/processPayloadAttestation.ts +3 -2
  79. package/src/block/processWithdrawals.ts +12 -2
  80. package/src/block/types.ts +32 -0
  81. package/src/epoch/processInactivityUpdates.ts +8 -1
  82. package/src/epoch/processPtcWindow.ts +15 -8
  83. package/src/index.ts +1 -1
  84. package/src/lightClient/spec/index.ts +2 -0
  85. package/src/lightClient/spec/utils.ts +142 -48
  86. package/src/metrics.ts +13 -2
  87. package/src/rewards/blockRewards.ts +1 -3
  88. package/src/signatureSets/index.ts +17 -3
  89. package/src/slot/upgradeStateToGloas.ts +1 -1
  90. package/src/stateView/beaconStateView.ts +16 -0
  91. package/src/stateView/interface.ts +3 -0
  92. package/src/stateView/nativeBeaconStateView.ts +13 -1
  93. package/src/util/gloas.ts +1 -0
  94. package/src/util/seed.ts +25 -13
@@ -1,12 +1,4 @@
1
- import {
2
- ForkSeq,
3
- MAX_ATTESTATIONS_ELECTRA,
4
- MAX_ATTESTER_SLASHINGS_ELECTRA,
5
- MAX_BLS_TO_EXECUTION_CHANGES,
6
- MAX_PAYLOAD_ATTESTATIONS,
7
- MAX_PROPOSER_SLASHINGS,
8
- MAX_VOLUNTARY_EXITS,
9
- } from "@lodestar/params";
1
+ import {ForkSeq} from "@lodestar/params";
10
2
  import {BeaconBlockBody, Slot, capella, electra, gloas} from "@lodestar/types";
11
3
  import {BeaconStateTransitionMetrics} from "../metrics.js";
12
4
  import {
@@ -26,7 +18,7 @@ import {processPayloadAttestation} from "./processPayloadAttestation.js";
26
18
  import {processProposerSlashing} from "./processProposerSlashing.js";
27
19
  import {processVoluntaryExit} from "./processVoluntaryExit.js";
28
20
  import {processWithdrawalRequest} from "./processWithdrawalRequest.js";
29
- import {ProcessBlockOpts} from "./types.js";
21
+ import {ProcessBlockOpts, ProcessOperationsStep} from "./types.js";
30
22
 
31
23
  export {
32
24
  processProposerSlashing,
@@ -48,10 +40,6 @@ export function processOperations(
48
40
  opts: ProcessBlockOpts = {verifySignatures: true},
49
41
  metrics?: BeaconStateTransitionMetrics | null
50
42
  ): void {
51
- if (fork >= ForkSeq.gloas) {
52
- assertGloasOperationLimits(body as gloas.BeaconBlockBody);
53
- }
54
-
55
43
  // verify that outstanding deposits are processed up to the maximum number of deposits.
56
44
  // From Fulu the eth1 bridge deposit mechanism was removed, so blocks must not contain any deposits.
57
45
  const maxDeposits = fork >= ForkSeq.fulu ? 0 : getEth1DepositCount(state);
@@ -61,64 +49,94 @@ export function processOperations(
61
49
  );
62
50
  }
63
51
 
64
- for (const proposerSlashing of body.proposerSlashings) {
65
- processProposerSlashing(fork, state, proposerSlashing, opts.verifySignatures);
52
+ {
53
+ const timer = metrics?.processOperationsStepTime.startTimer({step: ProcessOperationsStep.processProposerSlashing});
54
+ for (const proposerSlashing of body.proposerSlashings) {
55
+ processProposerSlashing(fork, state, proposerSlashing, opts.verifySignatures);
56
+ }
57
+ timer?.();
66
58
  }
67
- for (const attesterSlashing of body.attesterSlashings) {
68
- processAttesterSlashing(fork, state, attesterSlashing, opts.verifySignatures);
59
+
60
+ {
61
+ const timer = metrics?.processOperationsStepTime.startTimer({step: ProcessOperationsStep.processAttesterSlashing});
62
+ for (const attesterSlashing of body.attesterSlashings) {
63
+ processAttesterSlashing(fork, state, attesterSlashing, opts.verifySignatures);
64
+ }
65
+ timer?.();
69
66
  }
70
67
 
71
- processAttestations(fork, state, body.attestations, parentSlot, opts.verifySignatures, metrics);
68
+ {
69
+ const timer = metrics?.processOperationsStepTime.startTimer({step: ProcessOperationsStep.processAttestations});
70
+ processAttestations(fork, state, body.attestations, parentSlot, opts.verifySignatures, metrics);
71
+ timer?.();
72
+ }
72
73
 
73
- for (const deposit of body.deposits) {
74
- processDeposit(fork, state, deposit);
74
+ {
75
+ const timer = metrics?.processOperationsStepTime.startTimer({step: ProcessOperationsStep.processDeposit});
76
+ for (const deposit of body.deposits) {
77
+ processDeposit(fork, state, deposit);
78
+ }
79
+ timer?.();
75
80
  }
76
81
 
77
- for (const voluntaryExit of body.voluntaryExits) {
78
- processVoluntaryExit(fork, state, voluntaryExit, opts.verifySignatures);
82
+ {
83
+ const timer = metrics?.processOperationsStepTime.startTimer({step: ProcessOperationsStep.processVoluntaryExit});
84
+ for (const voluntaryExit of body.voluntaryExits) {
85
+ processVoluntaryExit(fork, state, voluntaryExit, opts.verifySignatures);
86
+ }
87
+ timer?.();
79
88
  }
80
89
 
81
90
  if (fork >= ForkSeq.capella) {
91
+ const timer = metrics?.processOperationsStepTime.startTimer({
92
+ step: ProcessOperationsStep.processBlsToExecutionChange,
93
+ });
82
94
  for (const blsToExecutionChange of (body as capella.BeaconBlockBody).blsToExecutionChanges) {
83
95
  processBlsToExecutionChange(state as CachedBeaconStateCapella, blsToExecutionChange);
84
96
  }
97
+ timer?.();
85
98
  }
86
99
 
87
100
  if (fork >= ForkSeq.electra && fork < ForkSeq.gloas) {
88
101
  const stateElectra = state as CachedBeaconStateElectra;
89
102
  const bodyElectra = body as electra.BeaconBlockBody;
90
103
 
91
- for (const depositRequest of bodyElectra.executionRequests.deposits) {
92
- processDepositRequest(fork, stateElectra, depositRequest);
104
+ {
105
+ const timer = metrics?.processOperationsStepTime.startTimer({step: ProcessOperationsStep.processDepositRequest});
106
+ for (const depositRequest of bodyElectra.executionRequests.deposits) {
107
+ processDepositRequest(fork, stateElectra, depositRequest);
108
+ }
109
+ timer?.();
93
110
  }
94
111
 
95
- for (const elWithdrawalRequest of bodyElectra.executionRequests.withdrawals) {
96
- processWithdrawalRequest(fork, stateElectra, elWithdrawalRequest);
112
+ {
113
+ const timer = metrics?.processOperationsStepTime.startTimer({
114
+ step: ProcessOperationsStep.processWithdrawalRequest,
115
+ });
116
+ for (const elWithdrawalRequest of bodyElectra.executionRequests.withdrawals) {
117
+ processWithdrawalRequest(fork, stateElectra, elWithdrawalRequest);
118
+ }
119
+ timer?.();
97
120
  }
98
121
 
99
- for (const elConsolidationRequest of bodyElectra.executionRequests.consolidations) {
100
- processConsolidationRequest(stateElectra, elConsolidationRequest);
122
+ {
123
+ const timer = metrics?.processOperationsStepTime.startTimer({
124
+ step: ProcessOperationsStep.processConsolidationRequest,
125
+ });
126
+ for (const elConsolidationRequest of bodyElectra.executionRequests.consolidations) {
127
+ processConsolidationRequest(stateElectra, elConsolidationRequest);
128
+ }
129
+ timer?.();
101
130
  }
102
131
  }
103
132
 
104
133
  if (fork >= ForkSeq.gloas) {
134
+ const timer = metrics?.processOperationsStepTime.startTimer({
135
+ step: ProcessOperationsStep.processPayloadAttestation,
136
+ });
105
137
  for (const payloadAttestation of (body as gloas.BeaconBlockBody).payloadAttestations) {
106
- processPayloadAttestation(state as CachedBeaconStateGloas, payloadAttestation);
138
+ processPayloadAttestation(state as CachedBeaconStateGloas, payloadAttestation, opts.verifySignatures);
107
139
  }
108
- }
109
- }
110
-
111
- function assertGloasOperationLimits(body: gloas.BeaconBlockBody): void {
112
- assertMaxLength("proposerSlashings", body.proposerSlashings.length, MAX_PROPOSER_SLASHINGS);
113
- assertMaxLength("attesterSlashings", body.attesterSlashings.length, MAX_ATTESTER_SLASHINGS_ELECTRA);
114
- assertMaxLength("attestations", body.attestations.length, MAX_ATTESTATIONS_ELECTRA);
115
- assertMaxLength("voluntaryExits", body.voluntaryExits.length, MAX_VOLUNTARY_EXITS);
116
- assertMaxLength("blsToExecutionChanges", body.blsToExecutionChanges.length, MAX_BLS_TO_EXECUTION_CHANGES);
117
- assertMaxLength("payloadAttestations", body.payloadAttestations.length, MAX_PAYLOAD_ATTESTATIONS);
118
- }
119
-
120
- function assertMaxLength(name: string, length: number, limit: number): void {
121
- if (length > limit) {
122
- throw new Error(`Block contains too many ${name}: count=${length} limit=${limit}`);
140
+ timer?.();
123
141
  }
124
142
  }
@@ -1,12 +1,4 @@
1
- import {
2
- ForkPostGloas,
3
- MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD,
4
- MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD,
5
- MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD,
6
- MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD,
7
- SLOTS_PER_EPOCH,
8
- SLOTS_PER_HISTORICAL_ROOT,
9
- } from "@lodestar/params";
1
+ import {ForkPostGloas, SLOTS_PER_EPOCH, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
10
2
  import {BeaconBlock, gloas, ssz} from "@lodestar/types";
11
3
  import {byteArrayEquals, toRootHex} from "@lodestar/utils";
12
4
  import {CachedBeaconStateGloas} from "../types.js";
@@ -55,11 +47,9 @@ export function processParentExecutionPayload(state: CachedBeaconStateGloas, blo
55
47
  * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.6/specs/gloas/beacon-chain.md#new-apply_parent_execution_payload
56
48
  */
57
49
  export function applyParentExecutionPayload(state: CachedBeaconStateGloas, requests: gloas.ExecutionRequests): void {
58
- assertExecutionRequestsWithinLimits(requests);
59
-
60
50
  const fork = state.config.getForkSeq(state.slot);
61
51
  const parentBid = state.latestExecutionPayloadBid;
62
- const parentSlot = parentBid.slot;
52
+ const parentSlot = state.latestBlockHeader.slot;
63
53
  const parentEpoch = computeEpochAtSlot(parentSlot);
64
54
  const currentEpoch = computeEpochAtSlot(state.slot);
65
55
 
@@ -126,21 +116,6 @@ function settleBuilderPayment(state: CachedBeaconStateGloas, paymentIndex: numbe
126
116
  state.builderPendingPayments.set(paymentIndex, ssz.gloas.BuilderPendingPayment.defaultViewDU());
127
117
  }
128
118
 
129
- function assertExecutionRequestsWithinLimits(requests: gloas.ExecutionRequests): void {
130
- assertMaxLength("withdrawals", requests.withdrawals.length, MAX_WITHDRAWAL_REQUESTS_PER_PAYLOAD);
131
- assertMaxLength("consolidations", requests.consolidations.length, MAX_CONSOLIDATION_REQUESTS_PER_PAYLOAD);
132
- // New in GLOAS:EIP8282
133
- assertMaxLength("builderDeposits", requests.builderDeposits.length, MAX_BUILDER_DEPOSIT_REQUESTS_PER_PAYLOAD);
134
- // New in GLOAS:EIP8282
135
- assertMaxLength("builderExits", requests.builderExits.length, MAX_BUILDER_EXIT_REQUESTS_PER_PAYLOAD);
136
- }
137
-
138
- function assertMaxLength(name: string, length: number, limit: number): void {
139
- if (length > limit) {
140
- throw new Error(`Too many parent execution request ${name} count=${length} limit=${limit}`);
141
- }
142
- }
143
-
144
119
  function assertEmptyExecutionRequests(requests: gloas.ExecutionRequests): void {
145
120
  if (
146
121
  requests.deposits.length !== 0 ||
@@ -5,7 +5,8 @@ import {isValidIndexedPayloadAttestation} from "./isValidIndexedPayloadAttestati
5
5
 
6
6
  export function processPayloadAttestation(
7
7
  state: CachedBeaconStateGloas,
8
- payloadAttestation: gloas.PayloadAttestation
8
+ payloadAttestation: gloas.PayloadAttestation,
9
+ verifySignature = true
9
10
  ): void {
10
11
  const data = payloadAttestation.data;
11
12
 
@@ -19,7 +20,7 @@ export function processPayloadAttestation(
19
20
 
20
21
  const indexedPayloadAttestation = state.epochCtx.getIndexedPayloadAttestation(data.slot, payloadAttestation);
21
22
 
22
- if (!isValidIndexedPayloadAttestation(state, indexedPayloadAttestation, true)) {
23
+ if (!isValidIndexedPayloadAttestation(state, indexedPayloadAttestation, verifySignature)) {
23
24
  throw Error("Invalid payload attestation");
24
25
  }
25
26
  }
@@ -212,7 +212,12 @@ function getBuildersSweepWithdrawals(
212
212
  break;
213
213
  }
214
214
 
215
- // Get next builder in turn
215
+ // Get next builder in turn, the spec does not wrap around an out of bounds start index
216
+ if (n === 0 && state.nextWithdrawalBuilderIndex >= builders.length) {
217
+ throw Error(
218
+ `Next withdrawal builder index ${state.nextWithdrawalBuilderIndex} out of bounds, builders count ${builders.length}`
219
+ );
220
+ }
216
221
  const builderIndex = (state.nextWithdrawalBuilderIndex + n) % builders.length;
217
222
  const builder = builders.getReadonly(builderIndex);
218
223
 
@@ -340,7 +345,12 @@ function getValidatorsSweepWithdrawals(
340
345
  break;
341
346
  }
342
347
 
343
- // Get next validator in turn
348
+ // Get next validator in turn, the spec does not wrap around an out of bounds start index
349
+ if (n === 0 && nextWithdrawalValidatorIndex >= validators.length) {
350
+ throw Error(
351
+ `Next withdrawal validator index ${nextWithdrawalValidatorIndex} out of bounds, validators count ${validators.length}`
352
+ );
353
+ }
344
354
  const validatorIndex = (nextWithdrawalValidatorIndex + n) % validators.length;
345
355
 
346
356
  const validator = validators.getReadonly(validatorIndex);
@@ -7,3 +7,35 @@ export enum ProposerRewardType {
7
7
  syncAggregate = "sync_aggregate",
8
8
  slashing = "slashing",
9
9
  }
10
+
11
+ /**
12
+ * Steps of `processBlock()` tracked in metrics
13
+ */
14
+ export enum BlockProcessStep {
15
+ processParentExecutionPayload = "processParentExecutionPayload",
16
+ processBlockHeader = "processBlockHeader",
17
+ processWithdrawals = "processWithdrawals",
18
+ processExecutionPayload = "processExecutionPayload",
19
+ processExecutionPayloadBid = "processExecutionPayloadBid",
20
+ processRandao = "processRandao",
21
+ processEth1Data = "processEth1Data",
22
+ processOperations = "processOperations",
23
+ processSyncAggregate = "processSyncAggregate",
24
+ processBlobKzgCommitments = "processBlobKzgCommitments",
25
+ }
26
+
27
+ /**
28
+ * Steps of `processOperations()` tracked in metrics
29
+ */
30
+ export enum ProcessOperationsStep {
31
+ processProposerSlashing = "processProposerSlashing",
32
+ processAttesterSlashing = "processAttesterSlashing",
33
+ processAttestations = "processAttestations",
34
+ processDeposit = "processDeposit",
35
+ processVoluntaryExit = "processVoluntaryExit",
36
+ processBlsToExecutionChange = "processBlsToExecutionChange",
37
+ processDepositRequest = "processDepositRequest",
38
+ processWithdrawalRequest = "processWithdrawalRequest",
39
+ processConsolidationRequest = "processConsolidationRequest",
40
+ processPayloadAttestation = "processPayloadAttestation",
41
+ }
@@ -37,6 +37,7 @@ export function processInactivityUpdates(state: CachedBeaconStateAltair, cache:
37
37
 
38
38
  inactivityScoresArr.length = state.validators.length;
39
39
  inactivityScores.getAll(inactivityScoresArr);
40
+ let shouldSetInactivityScores = false;
40
41
 
41
42
  for (let i = 0; i < flags.length; i++) {
42
43
  const flag = flags[i];
@@ -53,8 +54,14 @@ export function processInactivityUpdates(state: CachedBeaconStateAltair, cache:
53
54
  inactivityScore -= Math.min(INACTIVITY_SCORE_RECOVERY_RATE, inactivityScore);
54
55
  }
55
56
  if (inactivityScore !== prevInactivityScore) {
56
- inactivityScores.set(i, inactivityScore);
57
+ inactivityScoresArr[i] = inactivityScore;
58
+ shouldSetInactivityScores = true;
57
59
  }
58
60
  }
59
61
  }
62
+
63
+ if (shouldSetInactivityScores) {
64
+ // Rebuilding the full view is slightly slower than individual sets only when very few scores change.
65
+ state.inactivityScores = state.type.fields.inactivityScores.toViewDU(inactivityScoresArr);
66
+ }
60
67
  }
@@ -1,4 +1,4 @@
1
- import {MIN_SEED_LOOKAHEAD} from "@lodestar/params";
1
+ import {MIN_SEED_LOOKAHEAD, SLOTS_PER_EPOCH} from "@lodestar/params";
2
2
  import {ssz} from "@lodestar/types";
3
3
  import {CachedBeaconStateGloas, EpochTransitionCache} from "../types.js";
4
4
  import {computeEpochShuffling} from "../util/epochShuffling.js";
@@ -22,17 +22,24 @@ export function processPtcWindow(state: CachedBeaconStateGloas, cache: EpochTran
22
22
  state,
23
23
  nextEpoch,
24
24
  nextEpochShuffling.committees,
25
+ nextEpochShuffling.shuffling,
25
26
  state.epochCtx.effectiveBalanceIncrements
26
27
  );
27
28
 
28
29
  // Stash for finalProcessEpoch to shift into epoch cache
29
30
  cache.nextEpochPayloadTimelinessCommittees = newNextPayloadTimelinessCommittees;
30
31
 
31
- // Write shifted window to state: current(N) + next(N+1) + newlyComputed(N+2)
32
- // From the perspective of upcoming epoch N+1, this is previous + current + next
33
- state.ptcWindow = ssz.gloas.PtcWindow.toViewDU([
34
- ...state.epochCtx.payloadTimelinessCommittees,
35
- ...state.epochCtx.nextPayloadTimelinessCommittees,
36
- ...newNextPayloadTimelinessCommittees,
37
- ]);
32
+ // Shift the current and next epochs forward by reusing their SSZ subtrees and cached hashes.
33
+ // Only the newly computed epoch needs new subtrees.
34
+ const ptcWindow = state.ptcWindow;
35
+ const retainedLength = ptcWindow.length - SLOTS_PER_EPOCH;
36
+ for (let i = 0; i < retainedLength; i++) {
37
+ ptcWindow.set(i, ptcWindow.getReadonly(i + SLOTS_PER_EPOCH));
38
+ }
39
+ for (let i = 0; i < SLOTS_PER_EPOCH; i++) {
40
+ ptcWindow.set(
41
+ retainedLength + i,
42
+ ssz.gloas.PayloadTimelinessCommittee.toViewDU(newNextPayloadTimelinessCommittees[i])
43
+ );
44
+ }
38
45
  }
package/src/index.ts CHANGED
@@ -13,7 +13,7 @@ export {
13
13
  } from "./block/processVoluntaryExit.js";
14
14
  // Withdrawals for new blocks
15
15
  export {getExpectedWithdrawals} from "./block/processWithdrawals.js";
16
- export {ProposerRewardType} from "./block/types.js";
16
+ export {BlockProcessStep, ProcessOperationsStep, ProposerRewardType} from "./block/types.js";
17
17
  export {
18
18
  type EffectiveBalanceIncrements,
19
19
  getEffectiveBalanceIncrementsWithLen,
@@ -32,11 +32,13 @@ export {
32
32
  type SyncCommitteeFast,
33
33
  } from "./store.js";
34
34
  export {
35
+ getLightClientExecutionRoot,
35
36
  getSafetyThreshold,
36
37
  isFinalityUpdate,
37
38
  isSyncCommitteeUpdate,
38
39
  isValidLightClientHeader,
39
40
  normalizeMerkleBranch,
41
+ upgradeLightClientBootstrap,
40
42
  upgradeLightClientFinalityUpdate,
41
43
  upgradeLightClientHeader,
42
44
  upgradeLightClientOptimisticUpdate,
@@ -23,18 +23,18 @@ import {
23
23
  } from "@lodestar/params";
24
24
  import {
25
25
  BeaconBlockHeader,
26
+ LightClientBootstrap,
26
27
  LightClientFinalityUpdate,
27
28
  LightClientHeader,
28
29
  LightClientOptimisticUpdate,
29
30
  LightClientUpdate,
30
- Slot,
31
31
  SyncCommittee,
32
32
  isElectraLightClientUpdate,
33
33
  ssz,
34
34
  } from "@lodestar/types";
35
- import {byteArrayEquals, verifyMerkleBranch} from "@lodestar/utils";
36
- import {computeEpochAtSlot, computeSyncPeriodAtSlot} from "../../util/epoch.js";
37
- import type {LightClientStore, SyncCommitteeFast} from "./store.js";
35
+ import {byteArrayEquals, hash, verifyMerkleBranch} from "@lodestar/utils";
36
+ import {computeEpochAtSlot} from "../../util/epoch.js";
37
+ import type {ILightClientStore, SyncCommitteeFast} from "./store.js";
38
38
 
39
39
  export const GENESIS_SLOT = 0;
40
40
  export const ZERO_HASH = new Uint8Array(32);
@@ -201,6 +201,76 @@ function getGindexIndex(gindex: number): number {
201
201
  return gindex - 2 ** getGindexDepth(gindex);
202
202
  }
203
203
 
204
+ /**
205
+ * Returns the execution root represented by a light-client header.
206
+ *
207
+ * Gloas headers removed `execution` so Pre-Gloas headers upgraded to the Gloas format
208
+ * require reconstruction of the original execution payload header root.
209
+ *
210
+ * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.14/specs/gloas/light-client/sync-protocol.md#modified-get_lc_execution_root
211
+ */
212
+ export function getLightClientExecutionRoot(config: ChainForkConfig, header: LightClientHeader): Uint8Array {
213
+ const epoch = computeEpochAtSlot(header.beacon.slot);
214
+
215
+ if (!isGloasLightClientHeader(header)) {
216
+ if (epoch < config.CAPELLA_FORK_EPOCH) {
217
+ return ZERO_HASH;
218
+ }
219
+
220
+ return config
221
+ .getPostBellatrixForkTypes(header.beacon.slot)
222
+ .ExecutionPayloadHeader.hashTreeRoot((header as LightClientHeader<ForkName.capella>).execution);
223
+ }
224
+
225
+ if (epoch >= config.GLOAS_FORK_EPOCH) {
226
+ return header.executionBlockHash;
227
+ }
228
+
229
+ // Reconstruct the pre-Gloas execution payload root for a header upgraded to the Gloas format
230
+ // because the structure is different, `execution` is removed
231
+ const executionPayloadDepth = getGindexDepth(BLOCK_BODY_EXECUTION_PAYLOAD_GINDEX);
232
+ const innerBranch = header.executionBranch.slice(0, -executionPayloadDepth);
233
+
234
+ if (epoch >= config.DENEB_FORK_EPOCH) {
235
+ if (header.beacon.slot === GENESIS_SLOT) {
236
+ return ssz.deneb.ExecutionPayloadHeader.hashTreeRoot(ssz.deneb.ExecutionPayloadHeader.defaultValue());
237
+ }
238
+ return computeBranchRoot(
239
+ header.executionBlockHash,
240
+ innerBranch,
241
+ ssz.deneb.ExecutionPayloadHeader.getPathInfo(["blockHash"]).gindex
242
+ );
243
+ }
244
+
245
+ if (epoch >= config.CAPELLA_FORK_EPOCH) {
246
+ if (header.beacon.slot === GENESIS_SLOT) {
247
+ return ssz.capella.ExecutionPayloadHeader.hashTreeRoot(ssz.capella.ExecutionPayloadHeader.defaultValue());
248
+ }
249
+ return computeBranchRoot(
250
+ header.executionBlockHash,
251
+ innerBranch,
252
+ ssz.capella.ExecutionPayloadHeader.getPathInfo(["blockHash"]).gindex
253
+ );
254
+ }
255
+
256
+ return ZERO_HASH;
257
+ }
258
+
259
+ export function upgradeLightClientBootstrap(
260
+ config: ChainForkConfig,
261
+ targetFork: ForkName,
262
+ bootstrap: LightClientBootstrap
263
+ ): LightClientBootstrap {
264
+ return {
265
+ ...bootstrap,
266
+ header: upgradeLightClientHeader(config, targetFork, bootstrap.header),
267
+ currentSyncCommitteeBranch: normalizeMerkleBranch(
268
+ bootstrap.currentSyncCommitteeBranch,
269
+ currentSyncCommitteeGindexAtFork(targetFork)
270
+ ),
271
+ };
272
+ }
273
+
204
274
  export function upgradeLightClientHeader(
205
275
  config: ChainForkConfig,
206
276
  targetFork: ForkName,
@@ -211,9 +281,7 @@ export function upgradeLightClientHeader(
211
281
  throw Error(`Invalid upgrade request from headerFork=${headerFork} to targetFork=${targetFork}`);
212
282
  }
213
283
 
214
- // We are modifying the same header object, may be we could create a copy, but its
215
- // not required as of now
216
- let upgradedHeader = header;
284
+ let upgradedHeader = {...header} as LightClientHeader;
217
285
  const startUpgradeFromFork = Object.values(ForkName)[ForkSeq[headerFork] + 1];
218
286
 
219
287
  switch (startUpgradeFromFork) {
@@ -231,23 +299,30 @@ export function upgradeLightClientHeader(
231
299
 
232
300
  // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here
233
301
  case ForkName.capella:
234
- (upgradedHeader as LightClientHeader<ForkName.capella>).execution =
235
- ssz.capella.LightClientHeader.fields.execution.defaultValue();
236
- (upgradedHeader as LightClientHeader<ForkName.capella>).executionBranch =
237
- ssz.capella.LightClientHeader.fields.executionBranch.defaultValue();
302
+ upgradedHeader = {
303
+ ...upgradedHeader,
304
+ execution: ssz.capella.LightClientHeader.fields.execution.defaultValue(),
305
+ executionBranch: ssz.capella.LightClientHeader.fields.executionBranch.defaultValue(),
306
+ } as LightClientHeader<ForkName.capella>;
238
307
 
239
308
  // Break if no further upgradation is required else fall through
240
309
  if (ForkSeq[targetFork] <= ForkSeq.capella) break;
241
310
 
242
311
  // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here
243
- case ForkName.deneb:
244
- (upgradedHeader as LightClientHeader<ForkName.deneb>).execution.blobGasUsed =
245
- ssz.deneb.LightClientHeader.fields.execution.fields.blobGasUsed.defaultValue();
246
- (upgradedHeader as LightClientHeader<ForkName.deneb>).execution.excessBlobGas =
247
- ssz.deneb.LightClientHeader.fields.execution.fields.excessBlobGas.defaultValue();
312
+ case ForkName.deneb: {
313
+ const capellaHeader = upgradedHeader as LightClientHeader<ForkName.capella>;
314
+ upgradedHeader = {
315
+ ...capellaHeader,
316
+ execution: {
317
+ ...capellaHeader.execution,
318
+ blobGasUsed: ssz.deneb.LightClientHeader.fields.execution.fields.blobGasUsed.defaultValue(),
319
+ excessBlobGas: ssz.deneb.LightClientHeader.fields.execution.fields.excessBlobGas.defaultValue(),
320
+ },
321
+ } as LightClientHeader<ForkName.deneb>;
248
322
 
249
323
  // Break if no further upgradation is required else fall through
250
324
  if (ForkSeq[targetFork] <= ForkSeq.deneb) break;
325
+ }
251
326
 
252
327
  // biome-ignore lint/suspicious/noFallthroughSwitchClause: We need fall-through behavior here
253
328
  case ForkName.electra:
@@ -361,15 +436,16 @@ export function upgradeLightClientUpdate(
361
436
  targetFork: ForkName,
362
437
  update: LightClientUpdate
363
438
  ): LightClientUpdate {
364
- update.attestedHeader = upgradeLightClientHeader(config, targetFork, update.attestedHeader);
365
- update.finalizedHeader = upgradeLightClientHeader(config, targetFork, update.finalizedHeader);
366
- update.nextSyncCommitteeBranch = normalizeMerkleBranch(
367
- update.nextSyncCommitteeBranch,
368
- nextSyncCommitteeGindexAtFork(targetFork)
369
- );
370
- update.finalityBranch = normalizeMerkleBranch(update.finalityBranch, finalizedRootGindexAtFork(targetFork));
371
-
372
- return update;
439
+ return {
440
+ ...update,
441
+ attestedHeader: upgradeLightClientHeader(config, targetFork, update.attestedHeader),
442
+ finalizedHeader: upgradeLightClientHeader(config, targetFork, update.finalizedHeader),
443
+ nextSyncCommitteeBranch: normalizeMerkleBranch(
444
+ update.nextSyncCommitteeBranch,
445
+ nextSyncCommitteeGindexAtFork(targetFork)
446
+ ),
447
+ finalityBranch: normalizeMerkleBranch(update.finalityBranch, finalizedRootGindexAtFork(targetFork)),
448
+ };
373
449
  }
374
450
 
375
451
  export function upgradeLightClientFinalityUpdate(
@@ -377,14 +453,12 @@ export function upgradeLightClientFinalityUpdate(
377
453
  targetFork: ForkName,
378
454
  finalityUpdate: LightClientFinalityUpdate
379
455
  ): LightClientFinalityUpdate {
380
- finalityUpdate.attestedHeader = upgradeLightClientHeader(config, targetFork, finalityUpdate.attestedHeader);
381
- finalityUpdate.finalizedHeader = upgradeLightClientHeader(config, targetFork, finalityUpdate.finalizedHeader);
382
- finalityUpdate.finalityBranch = normalizeMerkleBranch(
383
- finalityUpdate.finalityBranch,
384
- finalizedRootGindexAtFork(targetFork)
385
- );
386
-
387
- return finalityUpdate;
456
+ return {
457
+ ...finalityUpdate,
458
+ attestedHeader: upgradeLightClientHeader(config, targetFork, finalityUpdate.attestedHeader),
459
+ finalizedHeader: upgradeLightClientHeader(config, targetFork, finalityUpdate.finalizedHeader),
460
+ finalityBranch: normalizeMerkleBranch(finalityUpdate.finalityBranch, finalizedRootGindexAtFork(targetFork)),
461
+ };
388
462
  }
389
463
 
390
464
  export function upgradeLightClientOptimisticUpdate(
@@ -392,9 +466,10 @@ export function upgradeLightClientOptimisticUpdate(
392
466
  targetFork: ForkName,
393
467
  optimisticUpdate: LightClientOptimisticUpdate
394
468
  ): LightClientOptimisticUpdate {
395
- optimisticUpdate.attestedHeader = upgradeLightClientHeader(config, targetFork, optimisticUpdate.attestedHeader);
396
-
397
- return optimisticUpdate;
469
+ return {
470
+ ...optimisticUpdate,
471
+ attestedHeader: upgradeLightClientHeader(config, targetFork, optimisticUpdate.attestedHeader),
472
+ };
398
473
  }
399
474
 
400
475
  /**
@@ -405,25 +480,44 @@ export function upgradeLightClientOptimisticUpdate(
405
480
  export function upgradeLightClientStore(
406
481
  config: ChainForkConfig,
407
482
  targetFork: ForkName,
408
- store: LightClientStore,
409
- signatureSlot: Slot
410
- ): LightClientStore {
411
- const updateSignaturePeriod = computeSyncPeriodAtSlot(signatureSlot);
412
- const bestValidUpdate = store.bestValidUpdates.get(updateSignaturePeriod);
413
-
414
- if (bestValidUpdate) {
415
- store.bestValidUpdates.set(updateSignaturePeriod, {
416
- update: upgradeLightClientUpdate(config, targetFork, bestValidUpdate.update),
417
- summary: bestValidUpdate.summary,
483
+ store: ILightClientStore
484
+ ): ILightClientStore {
485
+ const bestValidUpdates = new Map();
486
+
487
+ for (const [period, entry] of store.bestValidUpdates) {
488
+ bestValidUpdates.set(period, {
489
+ ...entry,
490
+ update: upgradeLightClientUpdate(config, targetFork, entry.update),
418
491
  });
419
492
  }
493
+ const finalizedHeader = upgradeLightClientHeader(config, targetFork, store.finalizedHeader);
494
+ const optimisticHeader = upgradeLightClientHeader(config, targetFork, store.optimisticHeader);
420
495
 
421
- store.finalizedHeader = upgradeLightClientHeader(config, targetFork, store.finalizedHeader);
422
- store.optimisticHeader = upgradeLightClientHeader(config, targetFork, store.optimisticHeader);
496
+ for (const [period, bestValidUpdate] of bestValidUpdates) {
497
+ store.bestValidUpdates.set(period, bestValidUpdate);
498
+ }
499
+ store.finalizedHeader = finalizedHeader;
500
+ store.optimisticHeader = optimisticHeader;
423
501
 
424
502
  return store;
425
503
  }
426
504
 
505
+ function computeBranchRoot(leaf: Uint8Array, branch: Uint8Array[], gindex: bigint): Uint8Array {
506
+ const depth = getGindexDepth(Number(gindex));
507
+ if (branch.length < depth) {
508
+ throw Error(`Invalid Merkle branch length ${branch.length}, expected at least ${depth}`);
509
+ }
510
+ const proof = branch.slice(-depth);
511
+ const index = getGindexIndex(Number(gindex));
512
+ let root = leaf;
513
+
514
+ for (let i = 0; i < depth; i++) {
515
+ root = Math.floor(index / 2 ** i) % 2 === 1 ? hash(proof[i], root) : hash(root, proof[i]);
516
+ }
517
+
518
+ return root;
519
+ }
520
+
427
521
  function isGloasLightClientHeader(header: LightClientHeader): header is LightClientHeader<ForkName.gloas> {
428
522
  return (header as LightClientHeader<ForkName.gloas>).executionBlockHash !== undefined;
429
523
  }