@lodestar/state-transition 1.44.0-dev.481dde6669 → 1.44.0-dev.4b91ba8c2f

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 (67) hide show
  1. package/lib/block/index.d.ts +2 -0
  2. package/lib/block/index.d.ts.map +1 -1
  3. package/lib/block/index.js +3 -1
  4. package/lib/block/index.js.map +1 -1
  5. package/lib/block/processBuilderDepositRequest.d.ts +10 -0
  6. package/lib/block/processBuilderDepositRequest.d.ts.map +1 -0
  7. package/lib/block/processBuilderDepositRequest.js +27 -0
  8. package/lib/block/processBuilderDepositRequest.js.map +1 -0
  9. package/lib/block/processBuilderExitRequest.d.ts +13 -0
  10. package/lib/block/processBuilderExitRequest.d.ts.map +1 -0
  11. package/lib/block/processBuilderExitRequest.js +29 -0
  12. package/lib/block/processBuilderExitRequest.js.map +1 -0
  13. package/lib/block/processDepositRequest.d.ts +2 -8
  14. package/lib/block/processDepositRequest.d.ts.map +1 -1
  15. package/lib/block/processDepositRequest.js +2 -98
  16. package/lib/block/processDepositRequest.js.map +1 -1
  17. package/lib/block/processExecutionPayloadBid.d.ts +2 -3
  18. package/lib/block/processExecutionPayloadBid.d.ts.map +1 -1
  19. package/lib/block/processExecutionPayloadBid.js +16 -8
  20. package/lib/block/processExecutionPayloadBid.js.map +1 -1
  21. package/lib/block/processParentExecutionPayload.d.ts +2 -2
  22. package/lib/block/processParentExecutionPayload.d.ts.map +1 -1
  23. package/lib/block/processParentExecutionPayload.js +16 -8
  24. package/lib/block/processParentExecutionPayload.js.map +1 -1
  25. package/lib/block/processProposerSlashing.d.ts.map +1 -1
  26. package/lib/block/processProposerSlashing.js +9 -1
  27. package/lib/block/processProposerSlashing.js.map +1 -1
  28. package/lib/block/processVoluntaryExit.d.ts +1 -1
  29. package/lib/block/processVoluntaryExit.d.ts.map +1 -1
  30. package/lib/block/processVoluntaryExit.js +1 -35
  31. package/lib/block/processVoluntaryExit.js.map +1 -1
  32. package/lib/slot/upgradeStateToGloas.js +35 -29
  33. package/lib/slot/upgradeStateToGloas.js.map +1 -1
  34. package/lib/stateView/beaconStateView.d.ts +1 -1
  35. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  36. package/lib/stateView/beaconStateView.js.map +1 -1
  37. package/lib/stateView/interface.d.ts +1 -1
  38. package/lib/stateView/interface.d.ts.map +1 -1
  39. package/lib/stateView/nativeBeaconStateView.d.ts +1 -1
  40. package/lib/stateView/nativeBeaconStateView.d.ts.map +1 -1
  41. package/lib/stateView/nativeBeaconStateView.js.map +1 -1
  42. package/lib/util/epochShuffling.js +1 -1
  43. package/lib/util/epochShuffling.js.map +1 -1
  44. package/lib/util/gloas.d.ts +17 -0
  45. package/lib/util/gloas.d.ts.map +1 -1
  46. package/lib/util/gloas.js +59 -1
  47. package/lib/util/gloas.js.map +1 -1
  48. package/lib/util/shuffling.d.ts +2 -2
  49. package/lib/util/shuffling.d.ts.map +1 -1
  50. package/lib/util/shuffling.js +4 -3
  51. package/lib/util/shuffling.js.map +1 -1
  52. package/package.json +7 -7
  53. package/src/block/index.ts +6 -1
  54. package/src/block/processBuilderDepositRequest.ts +43 -0
  55. package/src/block/processBuilderExitRequest.ts +39 -0
  56. package/src/block/processDepositRequest.ts +3 -124
  57. package/src/block/processExecutionPayloadBid.ts +24 -9
  58. package/src/block/processParentExecutionPayload.ts +23 -11
  59. package/src/block/processProposerSlashing.ts +9 -4
  60. package/src/block/processVoluntaryExit.ts +2 -61
  61. package/src/slot/upgradeStateToGloas.ts +49 -33
  62. package/src/stateView/beaconStateView.ts +1 -1
  63. package/src/stateView/interface.ts +1 -1
  64. package/src/stateView/nativeBeaconStateView.ts +1 -1
  65. package/src/util/epochShuffling.ts +1 -1
  66. package/src/util/gloas.ts +76 -1
  67. package/src/util/shuffling.ts +4 -3
@@ -32,6 +32,10 @@ export function processProposerSlashing(
32
32
  );
33
33
 
34
34
  if (fork >= ForkSeq.gloas) {
35
+ // Remove the BuilderPendingPayment corresponding to this proposal if it is still in the
36
+ // 2-epoch window. Only clear it when the slashed validator is the proposer associated with
37
+ // the payment; otherwise an unrelated same-slot equivocation could grief an honest proposer's
38
+ // payment.
35
39
  const slot = Number(proposerSlashing.signedHeader1.message.slot);
36
40
  const proposalEpoch = computeEpochAtSlot(slot);
37
41
  const currentEpoch = state.epochCtx.epoch;
@@ -45,10 +49,11 @@ export function processProposerSlashing(
45
49
  : undefined;
46
50
 
47
51
  if (paymentIndex !== undefined) {
48
- (state as CachedBeaconStateGloas).builderPendingPayments.set(
49
- paymentIndex,
50
- ssz.gloas.BuilderPendingPayment.defaultViewDU()
51
- );
52
+ const builderPendingPayments = (state as CachedBeaconStateGloas).builderPendingPayments;
53
+ const payment = builderPendingPayments.get(paymentIndex);
54
+ if (payment.proposerIndex === proposerSlashing.signedHeader1.message.proposerIndex) {
55
+ builderPendingPayments.set(paymentIndex, ssz.gloas.BuilderPendingPayment.defaultViewDU());
56
+ }
52
57
  }
53
58
  }
54
59
 
@@ -2,14 +2,7 @@ import {FAR_FUTURE_EPOCH, ForkSeq} from "@lodestar/params";
2
2
  import {phase0} from "@lodestar/types";
3
3
  import {verifyVoluntaryExitSignature} from "../signatureSets/index.js";
4
4
  import {BeaconStateView} from "../stateView/beaconStateView.js";
5
- import {CachedBeaconStateAllForks, CachedBeaconStateElectra, CachedBeaconStateGloas} from "../types.js";
6
- import {
7
- convertValidatorIndexToBuilderIndex,
8
- getPendingBalanceToWithdrawForBuilder,
9
- initiateBuilderExit,
10
- isActiveBuilder,
11
- isBuilderIndex,
12
- } from "../util/gloas.js";
5
+ import {CachedBeaconStateAllForks, CachedBeaconStateElectra} from "../types.js";
13
6
  import {getPendingBalanceToWithdraw, isActiveValidator} from "../util/index.js";
14
7
  import {initiateValidatorExit} from "./index.js";
15
8
 
@@ -24,7 +17,7 @@ export enum VoluntaryExitValidity {
24
17
  }
25
18
 
26
19
  /**
27
- * Process a VoluntaryExit operation. Initiates the exit of a validator or builder.
20
+ * Process a VoluntaryExit operation. Initiates the exit of a validator.
28
21
  *
29
22
  * PERF: Work depends on number of VoluntaryExit per block. On regular networks the average is 0 / block.
30
23
  */
@@ -34,21 +27,11 @@ export function processVoluntaryExit(
34
27
  signedVoluntaryExit: phase0.SignedVoluntaryExit,
35
28
  verifySignature = true
36
29
  ): void {
37
- const voluntaryExit = signedVoluntaryExit.message;
38
-
39
30
  const validity = getVoluntaryExitValidity(fork, state, signedVoluntaryExit, verifySignature);
40
31
  if (validity !== VoluntaryExitValidity.valid) {
41
32
  throw Error(`Invalid voluntary exit at forkSeq=${fork} reason=${validity}`);
42
33
  }
43
34
 
44
- if (fork >= ForkSeq.gloas && isBuilderIndex(voluntaryExit.validatorIndex)) {
45
- initiateBuilderExit(
46
- state as CachedBeaconStateGloas,
47
- convertValidatorIndexToBuilderIndex(voluntaryExit.validatorIndex)
48
- );
49
- return;
50
- }
51
-
52
35
  const validator = state.validators.get(signedVoluntaryExit.message.validatorIndex);
53
36
  initiateValidatorExit(fork, state, validator);
54
37
  }
@@ -67,51 +50,9 @@ export function getVoluntaryExitValidity(
67
50
  return VoluntaryExitValidity.earlyEpoch;
68
51
  }
69
52
 
70
- // Check if this is a builder exit
71
- if (fork >= ForkSeq.gloas && isBuilderIndex(voluntaryExit.validatorIndex)) {
72
- return getBuilderVoluntaryExitValidity(state as CachedBeaconStateGloas, signedVoluntaryExit, verifySignature);
73
- }
74
-
75
53
  return getValidatorVoluntaryExitValidity(fork, state, signedVoluntaryExit, verifySignature);
76
54
  }
77
55
 
78
- function getBuilderVoluntaryExitValidity(
79
- state: CachedBeaconStateGloas,
80
- signedVoluntaryExit: phase0.SignedVoluntaryExit,
81
- verifySignature: boolean
82
- ): VoluntaryExitValidity {
83
- const {config, epochCtx} = state;
84
- const builderIndex = convertValidatorIndexToBuilderIndex(signedVoluntaryExit.message.validatorIndex);
85
-
86
- if (builderIndex >= state.builders.length) {
87
- return VoluntaryExitValidity.inactive;
88
- }
89
-
90
- const builder = state.builders.getReadonly(builderIndex);
91
-
92
- // Verify the builder is active
93
- if (!isActiveBuilder(builder, state.finalizedCheckpoint.epoch)) {
94
- return builder.withdrawableEpoch !== FAR_FUTURE_EPOCH
95
- ? VoluntaryExitValidity.alreadyExited
96
- : VoluntaryExitValidity.inactive;
97
- }
98
-
99
- // Only exit builder if it has no pending withdrawals in the queue
100
- if (getPendingBalanceToWithdrawForBuilder(state, builderIndex) !== 0) {
101
- return VoluntaryExitValidity.pendingWithdrawals;
102
- }
103
-
104
- // Verify signature
105
- if (
106
- verifySignature &&
107
- !verifyVoluntaryExitSignature(config, epochCtx.pubkeyCache, new BeaconStateView(state), signedVoluntaryExit)
108
- ) {
109
- return VoluntaryExitValidity.invalidSignature;
110
- }
111
-
112
- return VoluntaryExitValidity.valid;
113
- }
114
-
115
56
  function getValidatorVoluntaryExitValidity(
116
57
  fork: ForkSeq,
117
58
  state: CachedBeaconStateAllForks,
@@ -1,10 +1,10 @@
1
- import {SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
1
+ import {PAYLOAD_BUILDER_VERSION, SLOTS_PER_HISTORICAL_ROOT} from "@lodestar/params";
2
2
  import {ssz} from "@lodestar/types";
3
3
  import {toPubkeyHex} from "@lodestar/utils";
4
- import {applyDepositForBuilder} from "../block/processDepositRequest.js";
4
+ import {isValidDepositSignature} from "../block/processDeposit.js";
5
5
  import {getCachedBeaconState} from "../cache/stateCache.js";
6
6
  import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
7
- import {initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js";
7
+ import {addBuilderToRegistry, initializePtcWindow, isBuilderWithdrawalCredential} from "../util/gloas.js";
8
8
  import {isValidatorKnown} from "../util/index.js";
9
9
  import {PendingDepositsLookup} from "../util/pendingDepositsLookup.js";
10
10
 
@@ -48,9 +48,9 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
48
48
  stateGloasView.currentSyncCommittee = stateGloasCloned.currentSyncCommittee;
49
49
  stateGloasView.nextSyncCommittee = stateGloasCloned.nextSyncCommittee;
50
50
  stateGloasView.latestExecutionPayloadBid.blockHash = stateFulu.latestExecutionPayloadHeader.blockHash;
51
- stateGloasView.latestExecutionPayloadBid.gasLimit = BigInt(stateFulu.latestExecutionPayloadHeader.gasLimit);
52
- stateGloasView.latestExecutionPayloadBid.executionRequestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(
53
- ssz.electra.ExecutionRequests.defaultValue()
51
+ stateGloasView.latestExecutionPayloadBid.gasLimit = stateFulu.latestExecutionPayloadHeader.gasLimit;
52
+ stateGloasView.latestExecutionPayloadBid.executionRequestsRoot = ssz.gloas.ExecutionRequests.hashTreeRoot(
53
+ ssz.gloas.ExecutionRequests.defaultValue()
54
54
  );
55
55
  stateGloasView.nextWithdrawalIndex = stateGloasCloned.nextWithdrawalIndex;
56
56
  stateGloasView.nextWithdrawalValidatorIndex = stateGloasCloned.nextWithdrawalValidatorIndex;
@@ -90,7 +90,8 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
90
90
  * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.8/specs/gloas/fork.md#new-onboard_builders_from_pending_deposits
91
91
  */
92
92
  function onboardBuildersFromPendingDeposits(state: CachedBeaconStateGloas): void {
93
- // Track pubkeys of new builders added when applying deposits
93
+ // Track pubkeys of new builders added when applying deposits. `state.builders` starts empty
94
+ // at the fork, so every builder pubkey here is one added in an earlier iteration.
94
95
  const builderPubkeys = new Set<string>();
95
96
 
96
97
  const pendingDeposits = ssz.electra.PendingDeposits.defaultViewDU();
@@ -109,40 +110,55 @@ function onboardBuildersFromPendingDeposits(state: CachedBeaconStateGloas): void
109
110
  continue;
110
111
  }
111
112
 
112
- // `applyDepositForBuilder` can mutate the state and add a builder to the registry, so
113
- // the set of builder pubkeys must be recomputed each iteration. `builderPubkeys` stands
114
- // in for the spec's `[b.pubkey for b in state.builders]`: `state.builders` starts empty
115
- // at the fork, so every builder is one added in a previous iteration of this loop.
116
- if (!builderPubkeys.has(pubkeyHex)) {
117
- // Deposits for non-builders stay in the pending queue. If there is a valid pending
118
- // deposit for a new validator with this pubkey, keep this deposit in the pending
119
- // queue to be applied to that validator later.
120
- if (!isBuilderWithdrawalCredential(deposit.withdrawalCredentials)) {
121
- pendingDeposits.push(deposit);
122
- pendingDepositsLookup.add(deposit, pubkeyHex);
123
- continue;
124
- }
125
- if (pendingDepositsLookup.hasPendingValidator(state.config, pubkeyHex)) {
126
- pendingDeposits.push(deposit);
127
- pendingDepositsLookup.add(deposit, pubkeyHex);
128
- continue;
113
+ if (builderPubkeys.has(pubkeyHex)) {
114
+ // Top up an already-onboarded builder
115
+ // TODO GLOAS: linear search; consider builder pubkey cache when we drop the upgrade-time set
116
+ for (let j = 0; j < state.builders.length; j++) {
117
+ if (toPubkeyHex(state.builders.getReadonly(j).pubkey) === pubkeyHex) {
118
+ state.builders.get(j).balance += deposit.amount;
119
+ break;
120
+ }
129
121
  }
122
+ continue;
130
123
  }
131
124
 
132
- const buildersLenBefore = state.builders.length;
133
- // TODO GLOAS: handle 20k 1ETH deposits on time
134
- // there is a note in the spec https://github.com/ethereum/consensus-specs/pull/5227
135
- applyDepositForBuilder(
125
+ // Deposits for non-builders stay in the pending queue. If there is a valid pending
126
+ // deposit for a new validator with this pubkey, keep this deposit pending so the validator
127
+ // can pick it up later.
128
+ if (!isBuilderWithdrawalCredential(deposit.withdrawalCredentials)) {
129
+ pendingDeposits.push(deposit);
130
+ pendingDepositsLookup.add(deposit, pubkeyHex);
131
+ continue;
132
+ }
133
+ if (pendingDepositsLookup.hasPendingValidator(state.config, pubkeyHex)) {
134
+ pendingDeposits.push(deposit);
135
+ pendingDepositsLookup.add(deposit, pubkeyHex);
136
+ continue;
137
+ }
138
+
139
+ // Verify the deposit signature (proof of possession). If invalid the deposit is silently
140
+ // dropped — stake is forfeited, matching the validator deposit contract behavior.
141
+ if (
142
+ !isValidDepositSignature(
143
+ state.config,
144
+ deposit.pubkey,
145
+ deposit.withdrawalCredentials,
146
+ deposit.amount,
147
+ deposit.signature
148
+ )
149
+ ) {
150
+ continue;
151
+ }
152
+
153
+ addBuilderToRegistry(
136
154
  state,
137
155
  deposit.pubkey,
138
- deposit.withdrawalCredentials,
156
+ PAYLOAD_BUILDER_VERSION,
157
+ deposit.withdrawalCredentials.subarray(12),
139
158
  deposit.amount,
140
- deposit.signature,
141
159
  deposit.slot
142
160
  );
143
- if (state.builders.length > buildersLenBefore) {
144
- builderPubkeys.add(pubkeyHex);
145
- }
161
+ builderPubkeys.add(pubkeyHex);
146
162
  }
147
163
 
148
164
  state.pendingDeposits = pendingDeposits;
@@ -831,7 +831,7 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
831
831
  /**
832
832
  * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.5/specs/gloas/validator.md#executionpayload
833
833
  */
834
- withParentPayloadApplied(executionRequests: electra.ExecutionRequests): IBeaconStateViewGloas {
834
+ withParentPayloadApplied(executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas {
835
835
  if (this.config.getForkSeq(this.cachedState.slot) < ForkSeq.gloas) {
836
836
  throw new Error("withParentPayloadApplied is not available before Gloas");
837
837
  }
@@ -264,7 +264,7 @@ export interface IBeaconStateViewGloas extends IBeaconStateViewFulu {
264
264
  * operation selection (e.g. voluntary exits) see the same post-apply state that the block
265
265
  * processor will see at import.
266
266
  */
267
- withParentPayloadApplied(executionRequests: electra.ExecutionRequests): IBeaconStateViewGloas;
267
+ withParentPayloadApplied(executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas;
268
268
  }
269
269
 
270
270
  /**
@@ -905,7 +905,7 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
905
905
  return this.binding.getIndicesInPayloadTimelinessCommittee(validatorIndex, slot);
906
906
  }
907
907
 
908
- withParentPayloadApplied(executionRequests: electra.ExecutionRequests): IBeaconStateViewGloas {
908
+ withParentPayloadApplied(executionRequests: gloas.ExecutionRequests): IBeaconStateViewGloas {
909
909
  const view = new NativeBeaconStateView(this.binding.withParentPayloadApplied(executionRequests));
910
910
  if (!isStatePostGloas(view)) {
911
911
  throw new Error("Expected gloas state from withParentPayloadApplied");
@@ -124,7 +124,7 @@ export async function computeEpochShufflingAsync(
124
124
  }
125
125
 
126
126
  export function calculateDecisionRoot(state: BeaconStateAllForks, epoch: Epoch): RootHex {
127
- const pivotSlot = computeStartSlotAtEpoch(epoch - 1) - 1;
127
+ const pivotSlot = Math.max(GENESIS_SLOT, computeStartSlotAtEpoch(epoch - 1) - 1);
128
128
  return toRootHex(getBlockRootAtSlot(state, pivotSlot));
129
129
  }
130
130
 
package/src/util/gloas.ts CHANGED
@@ -1,8 +1,11 @@
1
+ import {PublicKey, Signature, verify} from "@chainsafe/blst";
2
+ import {BeaconConfig} from "@lodestar/config";
1
3
  import {
2
4
  BUILDER_INDEX_FLAG,
3
5
  BUILDER_PAYMENT_THRESHOLD_DENOMINATOR,
4
6
  BUILDER_PAYMENT_THRESHOLD_NUMERATOR,
5
7
  BUILDER_WITHDRAWAL_PREFIX,
8
+ DOMAIN_BUILDER_DEPOSIT,
6
9
  EFFECTIVE_BALANCE_INCREMENT,
7
10
  FAR_FUTURE_EPOCH,
8
11
  MIN_DEPOSIT_AMOUNT,
@@ -10,15 +13,18 @@ import {
10
13
  PTC_SIZE,
11
14
  SLOTS_PER_EPOCH,
12
15
  } from "@lodestar/params";
13
- import {BuilderIndex, Epoch, ValidatorIndex, gloas} from "@lodestar/types";
16
+ import {BuilderIndex, Epoch, ValidatorIndex, gloas, ssz} from "@lodestar/types";
14
17
  import {AttestationData} from "@lodestar/types/phase0";
15
18
  import {byteArrayEquals} from "@lodestar/utils";
19
+ import {ZERO_HASH} from "../constants/index.js";
16
20
  import {CachedBeaconStateFulu, CachedBeaconStateGloas} from "../types.js";
17
21
  import {getBlockRootAtSlot} from "./blockRoot.js";
22
+ import {computeDomain} from "./domain.js";
18
23
  import {computeEpochAtSlot} from "./epoch.js";
19
24
  import {computeEpochShuffling} from "./epochShuffling.js";
20
25
  import {RootCache} from "./rootCache.js";
21
26
  import {computePayloadTimelinessCommitteesForEpoch} from "./seed.js";
27
+ import {computeSigningRoot} from "./signingRoot.js";
22
28
  import {getActiveValidatorIndices} from "./validator.js";
23
29
 
24
30
  export function isBuilderWithdrawalCredential(withdrawalCredentials: Uint8Array): boolean {
@@ -241,3 +247,72 @@ export function getPtcWindowEpochCacheData(state: CachedBeaconStateGloas): {
241
247
  nextPayloadTimelinessCommittees: toUint32Arrays(nextPtcWindow),
242
248
  };
243
249
  }
250
+
251
+ /**
252
+ * Add a new builder to the builders registry. Reuses slots from exited and fully withdrawn
253
+ * builders when available, otherwise appends.
254
+ *
255
+ * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.11/specs/gloas/beacon-chain.md#new-add_builder_to_registry
256
+ */
257
+ export function addBuilderToRegistry(
258
+ state: CachedBeaconStateGloas,
259
+ pubkey: Uint8Array,
260
+ version: number,
261
+ executionAddress: Uint8Array,
262
+ amount: number,
263
+ slot: number
264
+ ): void {
265
+ const currentEpoch = computeEpochAtSlot(state.slot);
266
+ const depositEpoch = computeEpochAtSlot(slot);
267
+
268
+ let builderIndex = state.builders.length;
269
+ for (let i = 0; i < state.builders.length; i++) {
270
+ const builder = state.builders.getReadonly(i);
271
+ if (builder.withdrawableEpoch <= currentEpoch && builder.balance === 0) {
272
+ builderIndex = i;
273
+ break;
274
+ }
275
+ }
276
+
277
+ const newBuilder = ssz.gloas.Builder.toViewDU({
278
+ pubkey,
279
+ version,
280
+ executionAddress,
281
+ balance: amount,
282
+ depositEpoch,
283
+ withdrawableEpoch: FAR_FUTURE_EPOCH,
284
+ });
285
+
286
+ if (builderIndex < state.builders.length) {
287
+ state.builders.set(builderIndex, newBuilder);
288
+ } else {
289
+ state.builders.push(newBuilder);
290
+ }
291
+ }
292
+
293
+ /**
294
+ * Verify the proof of possession on a builder deposit request.
295
+ *
296
+ * The dedicated `DOMAIN_BUILDER_DEPOSIT` (vs the validator `DOMAIN_DEPOSIT`) prevents replay
297
+ * of validator deposit signatures against the builder deposit contract and vice versa.
298
+ *
299
+ * Spec: https://github.com/ethereum/consensus-specs/blob/v1.7.0-alpha.11/specs/gloas/beacon-chain.md#new-is_valid_builder_deposit_signature
300
+ */
301
+ export function isValidBuilderDepositSignature(
302
+ config: BeaconConfig,
303
+ pubkey: Uint8Array,
304
+ withdrawalCredentials: Uint8Array,
305
+ amount: number,
306
+ signature: Uint8Array
307
+ ): boolean {
308
+ const depositMessage = {pubkey, withdrawalCredentials, amount};
309
+ const domain = computeDomain(DOMAIN_BUILDER_DEPOSIT, config.GENESIS_FORK_VERSION, ZERO_HASH);
310
+ const signingRoot = computeSigningRoot(ssz.phase0.DepositMessage, depositMessage, domain);
311
+ try {
312
+ const publicKey = PublicKey.fromBytes(pubkey, true);
313
+ const sig = Signature.fromBytes(signature, true);
314
+ return verify(signingRoot, publicKey, sig);
315
+ } catch (_e) {
316
+ return false;
317
+ }
318
+ }
@@ -21,8 +21,8 @@ import {EpochShuffling} from "./epochShuffling.js";
21
21
  * Computed for the requested epoch, not `state.epoch`, so it is correct when `state` is one
22
22
  * epoch off the requested epoch (e.g. serving next-epoch duties from the current state).
23
23
  *
24
- * Returns `null` when the genesis block decides its own shuffling (caller falls back to the
25
- * genesis block root).
24
+ * Returns `null` when the decision block is not in the state's past (the genesis block decides
25
+ * its own shuffling, or it has not been produced yet); caller supplies the fallback.
26
26
  */
27
27
  export function proposerShufflingDecisionRoot(
28
28
  fork: ForkName,
@@ -30,7 +30,8 @@ export function proposerShufflingDecisionRoot(
30
30
  proposalEpoch: Epoch
31
31
  ): Root | null {
32
32
  const decisionSlot = proposerShufflingDecisionSlot(fork, proposalEpoch);
33
- if (state.slot === decisionSlot) {
33
+ // Return null when the decision block is not in the state's past or the getBlockRootAtSlot() api will throw
34
+ if (decisionSlot >= state.slot) {
34
35
  return null;
35
36
  }
36
37
  return state.getBlockRootAtSlot(decisionSlot);