@lodestar/state-transition 1.47.0 → 1.48.0-dev.0dbea0de1b

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 (38) hide show
  1. package/lib/block/processWithdrawals.d.ts.map +1 -1
  2. package/lib/block/processWithdrawals.js +8 -2
  3. package/lib/block/processWithdrawals.js.map +1 -1
  4. package/lib/index.d.ts +1 -1
  5. package/lib/index.d.ts.map +1 -1
  6. package/lib/index.js.map +1 -1
  7. package/lib/lightClient/spec/index.d.ts +1 -1
  8. package/lib/lightClient/spec/index.d.ts.map +1 -1
  9. package/lib/lightClient/spec/index.js +1 -1
  10. package/lib/lightClient/spec/index.js.map +1 -1
  11. package/lib/lightClient/spec/utils.d.ts +13 -3
  12. package/lib/lightClient/spec/utils.d.ts.map +1 -1
  13. package/lib/lightClient/spec/utils.js +108 -34
  14. package/lib/lightClient/spec/utils.js.map +1 -1
  15. package/lib/stateView/beaconStateView.d.ts +2 -1
  16. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  17. package/lib/stateView/beaconStateView.js +6 -1
  18. package/lib/stateView/beaconStateView.js.map +1 -1
  19. package/lib/stateView/computeNewStateRoot.d.ts +6 -0
  20. package/lib/stateView/computeNewStateRoot.d.ts.map +1 -0
  21. package/lib/stateView/computeNewStateRoot.js +28 -0
  22. package/lib/stateView/computeNewStateRoot.js.map +1 -0
  23. package/lib/stateView/interface.d.ts +17 -2
  24. package/lib/stateView/interface.d.ts.map +1 -1
  25. package/lib/stateView/interface.js.map +1 -1
  26. package/lib/stateView/nativeBeaconStateView.d.ts +2 -1
  27. package/lib/stateView/nativeBeaconStateView.d.ts.map +1 -1
  28. package/lib/stateView/nativeBeaconStateView.js +5 -0
  29. package/lib/stateView/nativeBeaconStateView.js.map +1 -1
  30. package/package.json +7 -7
  31. package/src/block/processWithdrawals.ts +12 -2
  32. package/src/index.ts +2 -0
  33. package/src/lightClient/spec/index.ts +2 -0
  34. package/src/lightClient/spec/utils.ts +142 -48
  35. package/src/stateView/beaconStateView.ts +16 -1
  36. package/src/stateView/computeNewStateRoot.ts +35 -0
  37. package/src/stateView/interface.ts +24 -1
  38. package/src/stateView/nativeBeaconStateView.ts +10 -0
@@ -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/e762dd6e2c45ee05648b5787e7d261279aec226a/specs/gloas/light-client/sync-protocol.md#modified-get_lc_execution_root
211
+ */
212
+ export function getLcExecutionRoot(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
  }
@@ -70,7 +70,15 @@ import {loadState} from "../util/loadState/loadState.js";
70
70
  import {PreVerifyBuilderDepositsResult, preVerifyBuilderDepositsPreGloas} from "../util/preVerifyBuilderDeposits.js";
71
71
  import {getRandaoMix} from "../util/seed.js";
72
72
  import {getLatestWeakSubjectivityCheckpointEpoch} from "../util/weakSubjectivity.js";
73
- import {IBeaconStateView, IBeaconStateViewGloas, IBeaconStateViewLatestFork, isStatePostGloas} from "./interface.js";
73
+ import {computeNewStateRootStateTransitionOpts, getComputeNewStateRootResult} from "./computeNewStateRoot.js";
74
+ import {
75
+ ComputeNewStateRootInput,
76
+ ComputeNewStateRootResult,
77
+ IBeaconStateView,
78
+ IBeaconStateViewGloas,
79
+ IBeaconStateViewLatestFork,
80
+ isStatePostGloas,
81
+ } from "./interface.js";
74
82
 
75
83
  export class BeaconStateView implements IBeaconStateViewLatestFork {
76
84
  private readonly config: BeaconConfig;
@@ -823,6 +831,13 @@ export class BeaconStateView implements IBeaconStateViewLatestFork {
823
831
 
824
832
  // State transition
825
833
 
834
+ computeNewStateRoot({block}: ComputeNewStateRootInput, modules: StateTransitionModules): ComputeNewStateRootResult {
835
+ const postState = new BeaconStateView(
836
+ stateTransition(this.cachedState, block, computeNewStateRootStateTransitionOpts, modules)
837
+ );
838
+ return getComputeNewStateRootResult(postState, modules);
839
+ }
840
+
826
841
  stateTransition(
827
842
  signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock,
828
843
  options: StateTransitionOpts,
@@ -0,0 +1,35 @@
1
+ import {DataAvailabilityStatus, ExecutionPayloadStatus} from "../block/externalData.js";
2
+ import {StateHashTreeRootSource, StateTransitionModules, StateTransitionOpts} from "../stateTransition.js";
3
+ import {ComputeNewStateRootResult, IBeaconStateView} from "./interface.js";
4
+
5
+ /** State transition options for computing the state root of a locally produced block. */
6
+ export const computeNewStateRootStateTransitionOpts: StateTransitionOpts = {
7
+ // ExecutionPayloadStatus.valid: Assume payload valid, it has been produced by a trusted EL
8
+ executionPayloadStatus: ExecutionPayloadStatus.valid,
9
+ // DataAvailabilityStatus.available: Assume the blobs to be available, have just been produced by trusted EL
10
+ dataAvailabilityStatus: DataAvailabilityStatus.Available,
11
+ // verifyStateRoot: false | the root in the block is zero-ed, it's being computed here
12
+ verifyStateRoot: false,
13
+ // verifyProposer: false | as the block signature is zero-ed
14
+ verifyProposer: false,
15
+ // verifySignatures: false | since the data to assemble the block is trusted
16
+ verifySignatures: false,
17
+ // Preserve cache in source state, since the resulting state is not added to the state cache
18
+ dontTransferCache: true,
19
+ };
20
+
21
+ export function getComputeNewStateRootResult(
22
+ postState: IBeaconStateView,
23
+ {metrics}: StateTransitionModules
24
+ ): ComputeNewStateRootResult {
25
+ const {attestations, syncAggregate, slashing} = postState.proposerRewards;
26
+ const proposerReward = BigInt(attestations + syncAggregate + slashing);
27
+
28
+ const hashTreeRootTimer = metrics?.stateHashTreeRootTime.startTimer({
29
+ source: StateHashTreeRootSource.computeNewStateRoot,
30
+ });
31
+ const newStateRoot = postState.hashTreeRoot();
32
+ hashTreeRootTimer?.();
33
+
34
+ return {newStateRoot, proposerReward, postState};
35
+ }
@@ -29,6 +29,7 @@ import {
29
29
  Epoch,
30
30
  ExecutionPayloadBid,
31
31
  ExecutionPayloadHeader,
32
+ Gwei,
32
33
  Root,
33
34
  RootHex,
34
35
  SignedBeaconBlock,
@@ -54,6 +55,20 @@ import {StateTransitionModules, StateTransitionOpts} from "../stateTransition.js
54
55
  import {EpochShuffling} from "../util/epochShuffling.js";
55
56
  import {PreVerifyBuilderDepositsResult} from "../util/preVerifyBuilderDeposits.js";
56
57
 
58
+ /** Inputs for computing the state root of a locally produced block. */
59
+ export type ComputeNewStateRootInput = {
60
+ block: SignedBeaconBlock | SignedBlindedBeaconBlock;
61
+ /** Pre-serialized block bytes for native implementations. */
62
+ ssz?: Uint8Array;
63
+ };
64
+
65
+ /** State root computation result. Includes data derived from the post-state. */
66
+ export type ComputeNewStateRootResult = {
67
+ newStateRoot: Root;
68
+ proposerReward: Gwei;
69
+ postState: IBeaconStateView;
70
+ };
71
+
57
72
  /**
58
73
  * A read-only view of the BeaconState.
59
74
  */
@@ -161,6 +176,7 @@ export interface IBeaconStateView {
161
176
  hashTreeRoot(): Uint8Array;
162
177
 
163
178
  // State transition
179
+ computeNewStateRoot(input: ComputeNewStateRootInput, modules: StateTransitionModules): ComputeNewStateRootResult;
164
180
  stateTransition(
165
181
  signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock,
166
182
  options: StateTransitionOpts,
@@ -308,13 +324,20 @@ export type IBeaconStateViewLatestFork = Omit<
308
324
  * `loadOtherState`, `withParentPayloadApplied`) return `IBeaconStateViewNative`
309
325
  * so callers can re-wrap without an `as unknown` cast. Param lists are reused
310
326
  * via `Parameters<...>` to avoid duplicating signatures.
327
+ * - `computeNewStateRoot` is implemented by the wrapper because its inputs differ
328
+ * between the TypeScript and Zig implementations.
311
329
  *
312
330
  * The TS-side `BeaconStateView` also structurally satisfies this contract since
313
331
  * `BitArray` exposes `uint8Array` and `bitLen`.
314
332
  */
315
333
  export type IBeaconStateViewNative = Omit<
316
334
  IBeaconStateViewLatestFork,
317
- "executionPayloadAvailability" | "loadOtherState" | "stateTransition" | "processSlots" | "withParentPayloadApplied"
335
+ | "computeNewStateRoot"
336
+ | "executionPayloadAvailability"
337
+ | "loadOtherState"
338
+ | "stateTransition"
339
+ | "processSlots"
340
+ | "withParentPayloadApplied"
318
341
  > & {
319
342
  executionPayloadAvailability: {uint8Array: Uint8Array; bitLen: number};
320
343
  loadOtherState(...args: Parameters<IBeaconStateViewLatestFork["loadOtherState"]>): IBeaconStateViewNative;
@@ -36,7 +36,10 @@ import {SyncCommitteeWitness} from "../lightClient/types.js";
36
36
  import {StateTransitionModules, StateTransitionOpts} from "../stateTransition.js";
37
37
  import {EpochShuffling} from "../util/epochShuffling.js";
38
38
  import {PreVerifyBuilderDepositsResult} from "../util/preVerifyBuilderDeposits.js";
39
+ import {computeNewStateRootStateTransitionOpts, getComputeNewStateRootResult} from "./computeNewStateRoot.js";
39
40
  import {
41
+ ComputeNewStateRootInput,
42
+ ComputeNewStateRootResult,
40
43
  IBeaconStateView,
41
44
  IBeaconStateViewGloas,
42
45
  IBeaconStateViewLatestFork,
@@ -637,6 +640,13 @@ export class NativeBeaconStateView implements IBeaconStateViewLatestFork {
637
640
 
638
641
  // State transition
639
642
 
643
+ computeNewStateRoot(input: ComputeNewStateRootInput, modules: StateTransitionModules): ComputeNewStateRootResult {
644
+ const postState = new NativeBeaconStateView(
645
+ this.binding.stateTransition(input.block, computeNewStateRootStateTransitionOpts, modules)
646
+ );
647
+ return getComputeNewStateRootResult(postState, modules);
648
+ }
649
+
640
650
  stateTransition(
641
651
  signedBlock: SignedBeaconBlock | SignedBlindedBeaconBlock,
642
652
  options: StateTransitionOpts,