@lodestar/state-transition 1.44.0-rc.2 → 1.44.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 (59) 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 +34 -28
  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/gloas.d.ts +17 -0
  43. package/lib/util/gloas.d.ts.map +1 -1
  44. package/lib/util/gloas.js +59 -1
  45. package/lib/util/gloas.js.map +1 -1
  46. package/package.json +7 -7
  47. package/src/block/index.ts +6 -1
  48. package/src/block/processBuilderDepositRequest.ts +43 -0
  49. package/src/block/processBuilderExitRequest.ts +39 -0
  50. package/src/block/processDepositRequest.ts +3 -124
  51. package/src/block/processExecutionPayloadBid.ts +24 -9
  52. package/src/block/processParentExecutionPayload.ts +23 -11
  53. package/src/block/processProposerSlashing.ts +9 -4
  54. package/src/block/processVoluntaryExit.ts +2 -61
  55. package/src/slot/upgradeStateToGloas.ts +48 -32
  56. package/src/stateView/beaconStateView.ts +1 -1
  57. package/src/stateView/interface.ts +1 -1
  58. package/src/stateView/nativeBeaconStateView.ts +1 -1
  59. package/src/util/gloas.ts +76 -1
@@ -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
 
@@ -49,8 +49,8 @@ export function upgradeStateToGloas(stateFulu: CachedBeaconStateFulu): CachedBea
49
49
  stateGloasView.nextSyncCommittee = stateGloasCloned.nextSyncCommittee;
50
50
  stateGloasView.latestExecutionPayloadBid.blockHash = stateFulu.latestExecutionPayloadHeader.blockHash;
51
51
  stateGloasView.latestExecutionPayloadBid.gasLimit = stateFulu.latestExecutionPayloadHeader.gasLimit;
52
- stateGloasView.latestExecutionPayloadBid.executionRequestsRoot = ssz.electra.ExecutionRequests.hashTreeRoot(
53
- ssz.electra.ExecutionRequests.defaultValue()
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");
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
+ }