@lodestar/state-transition 1.47.0-dev.f591cb177e → 1.48.0-dev.67344272c0

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 (179) hide show
  1. package/lib/block/isValidIndexedAttestation.d.ts +2 -3
  2. package/lib/block/isValidIndexedAttestation.d.ts.map +1 -1
  3. package/lib/block/isValidIndexedAttestation.js +4 -4
  4. package/lib/block/isValidIndexedAttestation.js.map +1 -1
  5. package/lib/block/isValidIndexedPayloadAttestation.d.ts.map +1 -1
  6. package/lib/block/isValidIndexedPayloadAttestation.js +1 -1
  7. package/lib/block/isValidIndexedPayloadAttestation.js.map +1 -1
  8. package/lib/block/processAttestationPhase0.d.ts.map +1 -1
  9. package/lib/block/processAttestationPhase0.js +1 -1
  10. package/lib/block/processAttestationPhase0.js.map +1 -1
  11. package/lib/block/processAttestationsAltair.js +1 -1
  12. package/lib/block/processAttestationsAltair.js.map +1 -1
  13. package/lib/block/processAttesterSlashing.d.ts +1 -2
  14. package/lib/block/processAttesterSlashing.d.ts.map +1 -1
  15. package/lib/block/processAttesterSlashing.js +3 -3
  16. package/lib/block/processAttesterSlashing.js.map +1 -1
  17. package/lib/block/processDeposit.d.ts.map +1 -1
  18. package/lib/block/processDeposit.js +21 -46
  19. package/lib/block/processDeposit.js.map +1 -1
  20. package/lib/block/processExecutionPayloadBid.d.ts.map +1 -1
  21. package/lib/block/processExecutionPayloadBid.js +2 -5
  22. package/lib/block/processExecutionPayloadBid.js.map +1 -1
  23. package/lib/block/processProposerSlashing.d.ts +1 -2
  24. package/lib/block/processProposerSlashing.d.ts.map +1 -1
  25. package/lib/block/processProposerSlashing.js +3 -3
  26. package/lib/block/processProposerSlashing.js.map +1 -1
  27. package/lib/block/processRandao.js +1 -1
  28. package/lib/block/processRandao.js.map +1 -1
  29. package/lib/block/processSyncCommittee.js +1 -1
  30. package/lib/block/processSyncCommittee.js.map +1 -1
  31. package/lib/block/processVoluntaryExit.d.ts.map +1 -1
  32. package/lib/block/processVoluntaryExit.js +1 -2
  33. package/lib/block/processVoluntaryExit.js.map +1 -1
  34. package/lib/cache/epochCache.d.ts +2 -2
  35. package/lib/cache/epochCache.d.ts.map +1 -1
  36. package/lib/cache/epochCache.js +5 -6
  37. package/lib/cache/epochCache.js.map +1 -1
  38. package/lib/cache/stateCache.js +1 -1
  39. package/lib/cache/stateCache.js.map +1 -1
  40. package/lib/cache/syncCommitteeCache.d.ts +2 -3
  41. package/lib/cache/syncCommitteeCache.d.ts.map +1 -1
  42. package/lib/cache/syncCommitteeCache.js.map +1 -1
  43. package/lib/epoch/processSyncCommitteeUpdates.js +1 -1
  44. package/lib/epoch/processSyncCommitteeUpdates.js.map +1 -1
  45. package/lib/index.d.ts +3 -4
  46. package/lib/index.d.ts.map +1 -1
  47. package/lib/index.js +2 -3
  48. package/lib/index.js.map +1 -1
  49. package/lib/lightClient/spec/store.d.ts +1 -1
  50. package/lib/lightClient/spec/store.d.ts.map +1 -1
  51. package/lib/lightClient/spec/utils.js +1 -1
  52. package/lib/lightClient/spec/utils.js.map +1 -1
  53. package/lib/lightClient/spec/validateLightClientUpdate.js +1 -1
  54. package/lib/lightClient/spec/validateLightClientUpdate.js.map +1 -1
  55. package/lib/rewards/attestationsRewards.d.ts +1 -1
  56. package/lib/rewards/attestationsRewards.d.ts.map +1 -1
  57. package/lib/rewards/attestationsRewards.js.map +1 -1
  58. package/lib/rewards/syncCommitteeRewards.d.ts +1 -1
  59. package/lib/rewards/syncCommitteeRewards.d.ts.map +1 -1
  60. package/lib/rewards/syncCommitteeRewards.js +3 -1
  61. package/lib/rewards/syncCommitteeRewards.js.map +1 -1
  62. package/lib/signatureSets/blsToExecutionChange.d.ts.map +1 -1
  63. package/lib/signatureSets/blsToExecutionChange.js +1 -2
  64. package/lib/signatureSets/blsToExecutionChange.js.map +1 -1
  65. package/lib/signatureSets/executionPayloadEnvelope.d.ts +2 -3
  66. package/lib/signatureSets/executionPayloadEnvelope.d.ts.map +1 -1
  67. package/lib/signatureSets/executionPayloadEnvelope.js +6 -7
  68. package/lib/signatureSets/executionPayloadEnvelope.js.map +1 -1
  69. package/lib/signatureSets/proposer.d.ts +1 -2
  70. package/lib/signatureSets/proposer.d.ts.map +1 -1
  71. package/lib/signatureSets/proposer.js +2 -2
  72. package/lib/signatureSets/proposer.js.map +1 -1
  73. package/lib/signatureSets/randao.d.ts +1 -2
  74. package/lib/signatureSets/randao.d.ts.map +1 -1
  75. package/lib/signatureSets/randao.js +2 -2
  76. package/lib/signatureSets/randao.js.map +1 -1
  77. package/lib/signatureSets/voluntaryExits.d.ts +1 -2
  78. package/lib/signatureSets/voluntaryExits.d.ts.map +1 -1
  79. package/lib/signatureSets/voluntaryExits.js +3 -4
  80. package/lib/signatureSets/voluntaryExits.js.map +1 -1
  81. package/lib/stateTransition.js +1 -1
  82. package/lib/stateTransition.js.map +1 -1
  83. package/lib/stateView/beaconStateView.d.ts +2 -1
  84. package/lib/stateView/beaconStateView.d.ts.map +1 -1
  85. package/lib/stateView/beaconStateView.js +6 -1
  86. package/lib/stateView/beaconStateView.js.map +1 -1
  87. package/lib/stateView/computeNewStateRoot.d.ts +6 -0
  88. package/lib/stateView/computeNewStateRoot.d.ts.map +1 -0
  89. package/lib/stateView/computeNewStateRoot.js +28 -0
  90. package/lib/stateView/computeNewStateRoot.js.map +1 -0
  91. package/lib/stateView/interface.d.ts +17 -2
  92. package/lib/stateView/interface.d.ts.map +1 -1
  93. package/lib/stateView/interface.js.map +1 -1
  94. package/lib/stateView/nativeBeaconStateView.d.ts +2 -1
  95. package/lib/stateView/nativeBeaconStateView.d.ts.map +1 -1
  96. package/lib/stateView/nativeBeaconStateView.js +5 -0
  97. package/lib/stateView/nativeBeaconStateView.js.map +1 -1
  98. package/lib/stateView/stateViewFactory.d.ts +1 -1
  99. package/lib/stateView/stateViewFactory.d.ts.map +1 -1
  100. package/lib/stateView/stateViewFactory.js +2 -13
  101. package/lib/stateView/stateViewFactory.js.map +1 -1
  102. package/lib/testUtils/index.d.ts +1 -0
  103. package/lib/testUtils/index.d.ts.map +1 -1
  104. package/lib/testUtils/index.js +1 -0
  105. package/lib/testUtils/index.js.map +1 -1
  106. package/lib/testUtils/interopPubkeyCache.d.ts +7 -0
  107. package/lib/testUtils/interopPubkeyCache.d.ts.map +1 -0
  108. package/lib/testUtils/interopPubkeyCache.js +92 -0
  109. package/lib/testUtils/interopPubkeyCache.js.map +1 -0
  110. package/lib/testUtils/state.d.ts.map +1 -1
  111. package/lib/testUtils/state.js +5 -5
  112. package/lib/testUtils/state.js.map +1 -1
  113. package/lib/testUtils/testFileCache.d.ts.map +1 -1
  114. package/lib/testUtils/testFileCache.js +3 -0
  115. package/lib/testUtils/testFileCache.js.map +1 -1
  116. package/lib/testUtils/util.d.ts +1 -1
  117. package/lib/testUtils/util.d.ts.map +1 -1
  118. package/lib/testUtils/util.js +27 -35
  119. package/lib/testUtils/util.js.map +1 -1
  120. package/lib/util/gloas.d.ts.map +1 -1
  121. package/lib/util/gloas.js +2 -4
  122. package/lib/util/gloas.js.map +1 -1
  123. package/lib/util/interop.d.ts +1 -1
  124. package/lib/util/interop.d.ts.map +1 -1
  125. package/lib/util/interop.js +1 -1
  126. package/lib/util/interop.js.map +1 -1
  127. package/lib/util/signatureSets.d.ts +5 -13
  128. package/lib/util/signatureSets.d.ts.map +1 -1
  129. package/lib/util/signatureSets.js +30 -39
  130. package/lib/util/signatureSets.js.map +1 -1
  131. package/lib/util/syncCommittee.js +1 -1
  132. package/lib/util/syncCommittee.js.map +1 -1
  133. package/package.json +11 -12
  134. package/src/block/isValidIndexedAttestation.ts +2 -8
  135. package/src/block/isValidIndexedPayloadAttestation.ts +1 -4
  136. package/src/block/processAttestationPhase0.ts +0 -1
  137. package/src/block/processAttestationsAltair.ts +1 -1
  138. package/src/block/processAttesterSlashing.ts +2 -13
  139. package/src/block/processDeposit.ts +35 -47
  140. package/src/block/processExecutionPayloadBid.ts +8 -6
  141. package/src/block/processProposerSlashing.ts +2 -11
  142. package/src/block/processRandao.ts +1 -1
  143. package/src/block/processSyncCommittee.ts +1 -1
  144. package/src/block/processVoluntaryExit.ts +1 -4
  145. package/src/cache/epochCache.ts +6 -7
  146. package/src/cache/stateCache.ts +1 -1
  147. package/src/cache/syncCommitteeCache.ts +3 -2
  148. package/src/epoch/processSyncCommitteeUpdates.ts +1 -1
  149. package/src/index.ts +8 -7
  150. package/src/lightClient/spec/store.ts +1 -1
  151. package/src/lightClient/spec/utils.ts +1 -1
  152. package/src/lightClient/spec/validateLightClientUpdate.ts +1 -1
  153. package/src/rewards/attestationsRewards.ts +1 -1
  154. package/src/rewards/syncCommitteeRewards.ts +4 -2
  155. package/src/signatureSets/blsToExecutionChange.ts +1 -2
  156. package/src/signatureSets/executionPayloadEnvelope.ts +14 -15
  157. package/src/signatureSets/proposer.ts +1 -3
  158. package/src/signatureSets/randao.ts +2 -3
  159. package/src/signatureSets/voluntaryExits.ts +2 -5
  160. package/src/stateTransition.ts +1 -1
  161. package/src/stateView/beaconStateView.ts +16 -1
  162. package/src/stateView/computeNewStateRoot.ts +35 -0
  163. package/src/stateView/interface.ts +24 -1
  164. package/src/stateView/nativeBeaconStateView.ts +10 -0
  165. package/src/stateView/stateViewFactory.ts +2 -14
  166. package/src/testUtils/index.ts +1 -0
  167. package/src/testUtils/interopPubkeyCache.ts +100 -0
  168. package/src/testUtils/state.ts +5 -11
  169. package/src/testUtils/testFileCache.ts +3 -0
  170. package/src/testUtils/util.ts +26 -38
  171. package/src/util/gloas.ts +2 -4
  172. package/src/util/interop.ts +1 -1
  173. package/src/util/signatureSets.ts +34 -51
  174. package/src/util/syncCommittee.ts +1 -1
  175. package/lib/cache/pubkeyCache.d.ts +0 -28
  176. package/lib/cache/pubkeyCache.d.ts.map +0 -1
  177. package/lib/cache/pubkeyCache.js +0 -49
  178. package/lib/cache/pubkeyCache.js.map +0 -1
  179. package/src/cache/pubkeyCache.ts +0 -74
@@ -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,
@@ -1,5 +1,5 @@
1
+ import {type PubkeyCache, pubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
1
2
  import {BeaconConfig} from "@lodestar/config";
2
- import {PubkeyCache, createPubkeyCache} from "../cache/pubkeyCache.js";
3
3
  import {createCachedBeaconState} from "../cache/stateCache.js";
4
4
  import {BeaconStateAllForks} from "../cache/types.js";
5
5
  import {getStateTypeFromBytes} from "../util/sszBytes.js";
@@ -40,17 +40,6 @@ export function createBeaconStateView(opts: NodeJSOpts | NativeOpts): IBeaconSta
40
40
 
41
41
  // ---- createBeaconStateViewForHistoricalRegen (regen path) ----
42
42
 
43
- // Reused across all historical state regen calls in the worker thread
44
- const pubkeyCacheRegen = createPubkeyCache();
45
-
46
- function syncPubkeyCache(state: BeaconStateAllForks, pubkeyCache: PubkeyCache): void {
47
- const newCount = state.validators.length;
48
- for (let i = pubkeyCache.size; i < newCount; i++) {
49
- const pubkey = state.validators.getReadonly(i).pubkey;
50
- pubkeyCache.set(i, pubkey);
51
- }
52
- }
53
-
54
43
  type RegenNodeJSOpts = {
55
44
  useNative: false;
56
45
  config: BeaconConfig;
@@ -74,7 +63,6 @@ export function createBeaconStateViewForHistoricalRegen(opts: RegenNodeJSOpts |
74
63
  }
75
64
  const {config, stateBytes} = opts;
76
65
  const state = getStateTypeFromBytes(config, stateBytes).deserializeToViewDU(stateBytes);
77
- syncPubkeyCache(state, pubkeyCacheRegen);
78
- const cachedState = createCachedBeaconState(state, {config, pubkeyCache: pubkeyCacheRegen}, {skipSyncPubkeys: true});
66
+ const cachedState = createCachedBeaconState(state, {config, pubkeyCache}, {skipSyncPubkeys: true});
79
67
  return new BeaconStateView(cachedState);
80
68
  }
@@ -1,5 +1,6 @@
1
1
  export * from "./cache.js";
2
2
  export * from "./interop.js";
3
+ export * from "./interopPubkeyCache.js";
3
4
  export * from "./params.js";
4
5
  export * from "./testFileCache.js";
5
6
  export * from "./util.js";
@@ -0,0 +1,100 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import {pubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
4
+ import {interopSecretKey} from "../util/interop.js";
5
+ import {testCachePath} from "./cache.js";
6
+
7
+ /**
8
+ * This util persists generated keys as a PKIX snapshot in the local test-cache dir, so
9
+ * they are generated once per machine and reloaded for tests.
10
+ *
11
+ * The snapshot grows to satisfy larger requests. Smaller requests may load more entries
12
+ * than they need since cache contents do not define validator-registry membership.
13
+ */
14
+
15
+ /** Below this count, generating keys is faster than snapshot bookkeeping (~80ms) */
16
+ const MIN_PERSIST_SIZE = 1000;
17
+ const MAX_SNAPSHOT_CAPACITY = 0xffffffff;
18
+ const defaultSnapshotPath = path.join(testCachePath, "interop-pubkeys.pkix");
19
+
20
+ /** Sentinel pubkeys used to verify cache contents, memoized as they are re-checked often */
21
+ const interopPubkeyByIndex = new Map<number, Uint8Array>();
22
+
23
+ function interopPubkey(index: number): Uint8Array {
24
+ let pubkey = interopPubkeyByIndex.get(index);
25
+ if (!pubkey) {
26
+ pubkey = interopSecretKey(index).toPublicKey().toBytes();
27
+ interopPubkeyByIndex.set(index, pubkey);
28
+ }
29
+ return pubkey;
30
+ }
31
+
32
+ function isInteropKeyAt(index: number): boolean {
33
+ const pubkey = pubkeyCache.get(index);
34
+ return pubkey !== undefined && Buffer.compare(pubkey.toBytes(), interopPubkey(index)) === 0;
35
+ }
36
+
37
+ /**
38
+ * True if cache entries [0, count) are the index-matching interop pubkeys. Checking the
39
+ * two endpoints suffices: the native cache is append-only and never remaps an index, so
40
+ * replacing any entry in between would have required a reset() clearing index 0 too.
41
+ */
42
+ function hasInteropPrefix(count: number): boolean {
43
+ return pubkeyCache.size >= count && isInteropKeyAt(0) && isInteropKeyAt(count - 1);
44
+ }
45
+
46
+ function loadSnapshot(snapshotPath: string): void {
47
+ if (!fs.existsSync(snapshotPath)) return;
48
+
49
+ try {
50
+ pubkeyCache.load(snapshotPath, MAX_SNAPSHOT_CAPACITY);
51
+ } catch {
52
+ // Stale ABI, corrupt or torn file: drop it so future runs regenerate it
53
+ fs.rmSync(snapshotPath, {force: true});
54
+ }
55
+ }
56
+
57
+ function saveSnapshot(snapshotPath: string): void {
58
+ fs.mkdirSync(path.dirname(snapshotPath), {recursive: true});
59
+ // Write-then-rename so concurrent test processes never observe a partial file
60
+ const tmpPath = `${snapshotPath}.tmp-${process.pid}`;
61
+ try {
62
+ pubkeyCache.save(tmpPath);
63
+ fs.renameSync(tmpPath, snapshotPath);
64
+ } catch {
65
+ fs.rmSync(tmpPath, {force: true});
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Ensure the process-wide pubkey cache holds the interop pubkeys for indices [0, vc),
71
+ * loading persisted snapshots and generating as few keys as possible. No-op if the cache
72
+ * already holds them.
73
+ */
74
+ export function ensureInteropPubkeyCache(vc: number, snapshotPath = defaultSnapshotPath): void {
75
+ if (vc <= 0 || hasInteropPrefix(vc)) return;
76
+
77
+ // The cache is append-only, so extend a shorter interop prefix in place. Otherwise,
78
+ // replace unrelated contents with the persisted interop cache when available.
79
+ const size = pubkeyCache.size;
80
+ const sizeToExtend = size > 0 && size < vc && hasInteropPrefix(size) ? size : 0;
81
+ if (sizeToExtend === 0) {
82
+ pubkeyCache.reset();
83
+ loadSnapshot(snapshotPath);
84
+ }
85
+
86
+ let loadedSize = pubkeyCache.size;
87
+ if (loadedSize > 0 && !hasInteropPrefix(loadedSize)) {
88
+ pubkeyCache.reset();
89
+ loadedSize = 0;
90
+ }
91
+ if (loadedSize >= vc) return;
92
+
93
+ for (let i = loadedSize; i < vc; i++) {
94
+ pubkeyCache.append(i, interopSecretKey(i).toPublicKey().toBytes());
95
+ }
96
+
97
+ if (vc >= MIN_PERSIST_SIZE) {
98
+ saveSnapshot(snapshotPath);
99
+ }
100
+ }
@@ -1,3 +1,4 @@
1
+ import {pubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
1
2
  import {ChainForkConfig, createBeaconConfig} from "@lodestar/config";
2
3
  import {config, config as minimalConfig} from "@lodestar/config/default";
3
4
  import {
@@ -11,13 +12,7 @@ import {phase0, ssz} from "@lodestar/types";
11
12
  import {EpochCacheOpts} from "../cache/epochCache.js";
12
13
  import {BeaconStateCache} from "../cache/stateCache.js";
13
14
  import {ZERO_HASH} from "../constants/index.js";
14
- import {
15
- BeaconStateAllForks,
16
- BeaconStatePhase0,
17
- CachedBeaconStateAllForks,
18
- createCachedBeaconState,
19
- createPubkeyCache,
20
- } from "../index.js";
15
+ import {BeaconStateAllForks, BeaconStatePhase0, CachedBeaconStateAllForks, createCachedBeaconState} from "../index.js";
21
16
  import {newZeroedArray} from "../util/index.js";
22
17
 
23
18
  /**
@@ -86,10 +81,10 @@ export function generateCachedState(
86
81
  opts: TestBeaconState = {}
87
82
  ): CachedBeaconStateAllForks {
88
83
  const state = generateState(opts);
84
+ pubkeyCache.reset();
89
85
  return createCachedBeaconState(state, {
90
86
  config: createBeaconConfig(config, state.genesisValidatorsRoot),
91
- // This is a test state, there's no need to have a global shared cache of keys
92
- pubkeyCache: createPubkeyCache(),
87
+ pubkeyCache,
93
88
  });
94
89
  }
95
90
 
@@ -102,8 +97,7 @@ export function createCachedBeaconStateTest<T extends BeaconStateAllForks>(
102
97
  state,
103
98
  {
104
99
  config: createBeaconConfig(configCustom, state.genesisValidatorsRoot),
105
- // This is a test state, there's no need to have a global shared cache of keys
106
- pubkeyCache: createPubkeyCache(),
100
+ pubkeyCache,
107
101
  },
108
102
  opts
109
103
  );
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import {pubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
3
4
  import {getClient} from "@lodestar/api";
4
5
  import {ChainForkConfig, createChainForkConfig} from "@lodestar/config";
5
6
  import {NetworkName, networksChainConfig} from "@lodestar/config/networks";
@@ -40,6 +41,7 @@ export async function getNetworkCachedState(
40
41
 
41
42
  if (fs.existsSync(filepath)) {
42
43
  const stateSsz = fs.readFileSync(filepath);
44
+ pubkeyCache.reset();
43
45
  return createCachedBeaconStateTest(config.getForkTypes(slot).BeaconState.deserializeToViewDU(stateSsz), config);
44
46
  }
45
47
 
@@ -57,6 +59,7 @@ export async function getNetworkCachedState(
57
59
  ]);
58
60
 
59
61
  fs.writeFileSync(filepath, stateSsz);
62
+ pubkeyCache.reset();
60
63
  return createCachedBeaconStateTest(config.getForkTypes(slot).BeaconState.deserializeToViewDU(stateSsz), config);
61
64
  }
62
65
 
@@ -1,4 +1,5 @@
1
- import {PublicKey, SecretKey} from "@chainsafe/blst";
1
+ import {PublicKey, SecretKey} from "@chainsafe/lodestar-z/blst";
2
+ import {pubkeyCache} from "@chainsafe/lodestar-z/pubkeys";
2
3
  import {BitArray, fromHexString} from "@chainsafe/ssz";
3
4
  import {BeaconConfig, createBeaconConfig, createChainForkConfig} from "@lodestar/config";
4
5
  import {config} from "@lodestar/config/default";
@@ -22,7 +23,6 @@ import {
22
23
  computeCommitteeCount,
23
24
  computeEpochAtSlot,
24
25
  createCachedBeaconState,
25
- createPubkeyCache,
26
26
  newFilledArray,
27
27
  processSlots,
28
28
  } from "../index.js";
@@ -40,7 +40,7 @@ import {interopSecretKey} from "../util/interop.js";
40
40
  import {computeSigningRoot} from "../util/signingRoot.js";
41
41
  import {getNextSyncCommittee} from "../util/syncCommittee.js";
42
42
  import {getActiveValidatorIndices} from "../util/validator.js";
43
- import {interopPubkeysCached} from "./interop.js";
43
+ import {ensureInteropPubkeyCache} from "./interopPubkeyCache.js";
44
44
 
45
45
  let phase0State: BeaconStatePhase0 | null = null;
46
46
  let phase0CachedState23637: CachedBeaconStatePhase0 | null = null;
@@ -69,47 +69,45 @@ export const perfStateId = `${numValidators} vs - 7PWei`;
69
69
 
70
70
  /** Cache interop secret keys */
71
71
  const secretKeyByModIndex = new Map<number, SecretKey>();
72
+ const pubkeysByCount = new Map<number, Uint8Array[]>();
72
73
  const epoch = 23638;
73
74
  export const perfStateEpoch = epoch;
74
75
 
75
76
  export function getPubkeys(vc = numValidators) {
76
- const pubkeysMod = interopPubkeysCached(keypairsMod);
77
- const pubkeysModObj = pubkeysMod.map((pk) => PublicKey.fromBytes(pk));
78
- const pubkeys = Array.from({length: vc}, (_, i) => pubkeysMod[i % keypairsMod]);
77
+ ensureInteropPubkeyCache(vc);
78
+ let pubkeys = pubkeysByCount.get(vc);
79
+ if (!pubkeys) {
80
+ for (const [count, cached] of pubkeysByCount) {
81
+ if (count >= vc) {
82
+ pubkeys = cached.slice(0, vc);
83
+ break;
84
+ }
85
+ }
86
+ pubkeys ??= Array.from({length: vc}, (_, i) => pubkeyCache.getPubkeyBytesOrThrow(i));
87
+ pubkeysByCount.set(vc, pubkeys);
88
+ }
89
+ const pubkeysMod = pubkeys;
90
+ const pubkeysModObj = pubkeys.slice(0, keypairsMod).map((pk) => PublicKey.fromBytes(pk));
79
91
  return {pubkeysMod, pubkeysModObj, pubkeys};
80
92
  }
81
93
 
82
94
  /** Get secret key of a validatorIndex, if the pubkeys are generated with `getPubkeys()` */
83
95
  export function getSecretKeyFromIndex(validatorIndex: number): SecretKey {
84
- return interopSecretKey(validatorIndex % keypairsMod);
96
+ return interopSecretKey(validatorIndex);
85
97
  }
86
98
 
87
99
  /** Get secret key of a validatorIndex, if the pubkeys are generated with `getPubkeys()` */
88
100
  export function getSecretKeyFromIndexCached(validatorIndex: number): SecretKey {
89
- const keyIndex = validatorIndex % keypairsMod;
90
- let sk = secretKeyByModIndex.get(keyIndex);
101
+ let sk = secretKeyByModIndex.get(validatorIndex);
91
102
  if (!sk) {
92
- sk = interopSecretKey(keyIndex);
93
- secretKeyByModIndex.set(keyIndex, sk);
103
+ sk = interopSecretKey(validatorIndex);
104
+ secretKeyByModIndex.set(validatorIndex, sk);
94
105
  }
95
106
  return sk;
96
107
  }
97
108
 
98
- function getPubkeyCaches({pubkeysMod}: ReturnType<typeof getPubkeys>, vc = numValidators) {
99
- // Manually sync pubkeys to prevent doing BLS opts 110_000 times
100
- const pubkeyCache = createPubkeyCache();
101
- for (let i = 0; i < vc; i++) {
102
- const pubkey = pubkeysMod[i % keypairsMod];
103
- pubkeyCache.set(i, pubkey);
104
- }
105
-
106
- return {pubkeyCache};
107
- }
108
-
109
109
  export function generatePerfTestCachedStatePhase0(opts?: {goBackOneSlot: boolean}): CachedBeaconStatePhase0 {
110
- // Generate only some publicKeys
111
- const {pubkeys, pubkeysMod, pubkeysModObj} = getPubkeys();
112
- const {pubkeyCache} = getPubkeyCaches({pubkeys, pubkeysMod, pubkeysModObj});
110
+ const {pubkeys} = getPubkeys();
113
111
 
114
112
  if (!phase0State) {
115
113
  const state = buildPerformanceStatePhase0();
@@ -218,8 +216,7 @@ export function generatePerfTestCachedStateAltair(opts?: {
218
216
  vc?: number;
219
217
  }): CachedBeaconStateAltair {
220
218
  const vc = opts?.vc ?? numValidators;
221
- const {pubkeys, pubkeysMod, pubkeysModObj} = getPubkeys(vc);
222
- const {pubkeyCache} = getPubkeyCaches({pubkeys, pubkeysMod, pubkeysModObj}, vc);
219
+ const {pubkeys} = getPubkeys(vc);
223
220
 
224
221
  const altairConfig = createChainForkConfig({ALTAIR_FORK_EPOCH: 0});
225
222
 
@@ -259,8 +256,7 @@ export function generatePerfTestCachedStateElectra(opts?: {
259
256
  vc?: number;
260
257
  }): CachedBeaconStateElectra {
261
258
  const vc = opts?.vc ?? numValidators;
262
- const {pubkeys, pubkeysMod, pubkeysModObj} = getPubkeys(vc);
263
- const {pubkeyCache} = getPubkeyCaches({pubkeys, pubkeysMod, pubkeysModObj}, vc);
259
+ const {pubkeys} = getPubkeys(vc);
264
260
 
265
261
  const electraConfig = createChainForkConfig({
266
262
  ALTAIR_FORK_EPOCH: 0,
@@ -495,15 +491,7 @@ export function generateTestCachedBeaconStateOnlyValidators({
495
491
  vc: number;
496
492
  slot: Slot;
497
493
  }): CachedBeaconStateAllForks {
498
- // Generate only some publicKeys
499
- const {pubkeys, pubkeysMod} = getPubkeys(vc);
500
-
501
- // Manually sync pubkeys to prevent doing BLS opts 110_000 times
502
- const pubkeyCache = createPubkeyCache();
503
- for (let i = 0; i < vc; i++) {
504
- const pubkey = pubkeysMod[i % keypairsMod];
505
- pubkeyCache.set(i, pubkey);
506
- }
494
+ const {pubkeys} = getPubkeys(vc);
507
495
 
508
496
  const state = ssz.phase0.BeaconState.defaultViewDU();
509
497
  state.slot = slot;
package/src/util/gloas.ts CHANGED
@@ -1,4 +1,3 @@
1
- import {PublicKey, Signature, verify} from "@chainsafe/blst";
2
1
  import {BeaconConfig} from "@lodestar/config";
3
2
  import {
4
3
  BUILDER_INDEX_FLAG,
@@ -23,6 +22,7 @@ import {computeEpochAtSlot} from "./epoch.js";
23
22
  import {computeEpochShuffling} from "./epochShuffling.js";
24
23
  import {RootCache} from "./rootCache.js";
25
24
  import {computePayloadTimelinessCommitteesForEpoch} from "./seed.js";
25
+ import {createSingleSignatureSetFromComponents, verifySignatureSet} from "./signatureSets.js";
26
26
  import {computeSigningRoot} from "./signingRoot.js";
27
27
  import {getActiveValidatorIndices} from "./validator.js";
28
28
 
@@ -339,9 +339,7 @@ export function isValidBuilderDepositSignature(
339
339
  const domain = computeDomain(DOMAIN_BUILDER_DEPOSIT, config.GENESIS_FORK_VERSION, ZERO_HASH);
340
340
  const signingRoot = computeSigningRoot(ssz.phase0.DepositMessage, depositMessage, domain);
341
341
  try {
342
- const publicKey = PublicKey.fromBytes(pubkey, true);
343
- const sig = Signature.fromBytes(signature, true);
344
- return verify(signingRoot, publicKey, sig);
342
+ return verifySignatureSet(createSingleSignatureSetFromComponents(pubkey, signingRoot, signature));
345
343
  } catch (_e) {
346
344
  return false;
347
345
  }
@@ -1,6 +1,6 @@
1
1
  import {toBufferBE} from "@vekexasia/bigint-buffer2";
2
2
  import {digest} from "@chainsafe/as-sha256";
3
- import {SecretKey} from "@chainsafe/blst";
3
+ import {SecretKey} from "@chainsafe/lodestar-z/blst";
4
4
  import {bytesToBigInt, intToBytes} from "@lodestar/utils";
5
5
 
6
6
  let curveOrder: bigint;
@@ -1,6 +1,5 @@
1
- import {PublicKey, Signature, aggregatePublicKeys, fastAggregateVerify, verify} from "@chainsafe/blst";
1
+ import {BLS_VERIFIER_SET_TYPE, type BlsSignatureSet, verifySignatureSets} from "@chainsafe/lodestar-z/bls-verifier";
2
2
  import {Root} from "@lodestar/types";
3
- import {PubkeyCache} from "../cache/pubkeyCache.js";
4
3
 
5
4
  export enum SignatureSetType {
6
5
  single = "single",
@@ -18,7 +17,7 @@ export enum SignatureSetType {
18
17
  */
19
18
  export type SingleSignatureSet = {
20
19
  type: SignatureSetType.single;
21
- pubkey: PublicKey;
20
+ pubkey: Uint8Array;
22
21
  signingRoot: Root;
23
22
  signature: Uint8Array;
24
23
  };
@@ -47,59 +46,39 @@ export type AggregatedSignatureSet = {
47
46
 
48
47
  export type ISignatureSet = SingleSignatureSet | IndexedSignatureSet | AggregatedSignatureSet;
49
48
 
50
- /**
51
- * Get the pubkey for a signature set, performing aggregation if necessary.
52
- * Requires pubkeyCache for indexed and aggregate sets.
53
- */
54
- export function getSignatureSetPubkey(signatureSet: ISignatureSet, pubkeyCache: PubkeyCache): PublicKey {
49
+ export function toBlsSignatureSet(signatureSet: ISignatureSet): BlsSignatureSet {
55
50
  switch (signatureSet.type) {
56
51
  case SignatureSetType.single:
57
- return signatureSet.pubkey;
58
-
59
- case SignatureSetType.indexed: {
60
- return pubkeyCache.getOrThrow(signatureSet.index);
61
- }
52
+ return {
53
+ type: BLS_VERIFIER_SET_TYPE.single,
54
+ pubkey: signatureSet.pubkey,
55
+ message: signatureSet.signingRoot,
56
+ signature: signatureSet.signature,
57
+ };
58
+
59
+ case SignatureSetType.indexed:
60
+ return {
61
+ type: BLS_VERIFIER_SET_TYPE.indexed,
62
+ index: signatureSet.index,
63
+ message: signatureSet.signingRoot,
64
+ signature: signatureSet.signature,
65
+ };
62
66
 
63
67
  case SignatureSetType.aggregate: {
64
- const pubkeys = signatureSet.indices.map((i) => {
65
- return pubkeyCache.getOrThrow(i);
66
- });
67
- return aggregatePublicKeys(pubkeys);
68
- }
69
-
70
- default:
71
- throw Error("Unknown signature set type");
72
- }
73
- }
74
-
75
- export function verifySignatureSet(signatureSet: SingleSignatureSet, pubkeyCache?: PubkeyCache): boolean;
76
- export function verifySignatureSet(signatureSet: IndexedSignatureSet, pubkeyCache: PubkeyCache): boolean;
77
- export function verifySignatureSet(signatureSet: AggregatedSignatureSet, pubkeyCache: PubkeyCache): boolean;
78
- export function verifySignatureSet(signatureSet: ISignatureSet, pubkeyCache: PubkeyCache): boolean;
79
- export function verifySignatureSet(signatureSet: ISignatureSet, pubkeyCache?: PubkeyCache): boolean {
80
- // All signatures are not trusted and must be group checked (p2.subgroup_check)
81
- const signature = Signature.fromBytes(signatureSet.signature, true);
82
-
83
- switch (signatureSet.type) {
84
- case SignatureSetType.single:
85
- return verify(signatureSet.signingRoot, signatureSet.pubkey, signature);
86
-
87
- case SignatureSetType.indexed: {
88
- if (!pubkeyCache) {
89
- throw Error("pubkeyCache required for indexed signature set");
68
+ const indices = new Uint32Array(signatureSet.indices.length);
69
+ for (const [i, index] of signatureSet.indices.entries()) {
70
+ if (!Number.isInteger(index) || index < 0 || index > 0xffffffff) {
71
+ throw new RangeError(`Invalid validator index ${index}`);
72
+ }
73
+ indices[i] = index;
90
74
  }
91
- const pubkey = pubkeyCache.getOrThrow(signatureSet.index);
92
- return verify(signatureSet.signingRoot, pubkey, signature);
93
- }
94
75
 
95
- case SignatureSetType.aggregate: {
96
- if (!pubkeyCache) {
97
- throw Error("pubkeyCache required for aggregate signature set");
98
- }
99
- const pubkeys = signatureSet.indices.map((i) => {
100
- return pubkeyCache.getOrThrow(i);
101
- });
102
- return fastAggregateVerify(signatureSet.signingRoot, pubkeys, signature);
76
+ return {
77
+ type: BLS_VERIFIER_SET_TYPE.aggregate,
78
+ indices,
79
+ message: signatureSet.signingRoot,
80
+ signature: signatureSet.signature,
81
+ };
103
82
  }
104
83
 
105
84
  default:
@@ -107,8 +86,12 @@ export function verifySignatureSet(signatureSet: ISignatureSet, pubkeyCache?: Pu
107
86
  }
108
87
  }
109
88
 
89
+ export function verifySignatureSet(signatureSet: ISignatureSet): boolean {
90
+ return verifySignatureSets([toBlsSignatureSet(signatureSet)]);
91
+ }
92
+
110
93
  export function createSingleSignatureSetFromComponents(
111
- pubkey: PublicKey,
94
+ pubkey: Uint8Array,
112
95
  signingRoot: Root,
113
96
  signature: Uint8Array
114
97
  ): SingleSignatureSet {
@@ -1,4 +1,4 @@
1
- import {aggregateSerializedPublicKeys} from "@chainsafe/blst";
1
+ import {aggregateSerializedPublicKeys} from "@chainsafe/lodestar-z/blst";
2
2
  import {
3
3
  BASE_REWARD_FACTOR,
4
4
  EFFECTIVE_BALANCE_INCREMENT,
@@ -1,28 +0,0 @@
1
- import { PublicKey } from "@chainsafe/blst";
2
- import { ValidatorIndex, phase0 } from "@lodestar/types";
3
- /**
4
- * Unified pubkey cache coupling index→pubkey and pubkey→index lookups.
5
- * Both directions are kept in sync atomically via `set()`.
6
- */
7
- export interface PubkeyCache {
8
- /** Get deserialized PublicKey by validator index */
9
- get(index: ValidatorIndex): PublicKey | undefined;
10
- /** Get deserialized PublicKey by validator index or throw if not found */
11
- getOrThrow(index: ValidatorIndex): PublicKey;
12
- /** Get validator index by pubkey bytes */
13
- getIndex(pubkey: Uint8Array): ValidatorIndex | null;
14
- /** Set both directions atomically. Takes raw pubkey bytes — deserialization is handled internally. */
15
- set(index: ValidatorIndex, pubkey: Uint8Array): void;
16
- /** Number of entries */
17
- readonly size: number;
18
- }
19
- export declare function createPubkeyCache(): PubkeyCache;
20
- /**
21
- * Checks the pubkey indices against a state and adds missing pubkeys
22
- *
23
- * Mutates `pubkeyCache`
24
- *
25
- * If pubkey cache is empty: SLOW CODE - 🐢
26
- */
27
- export declare function syncPubkeys(pubkeyCache: PubkeyCache, validators: phase0.Validator[]): void;
28
- //# sourceMappingURL=pubkeyCache.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"pubkeyCache.d.ts","sourceRoot":"","sources":["../../src/cache/pubkeyCache.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAC,MAAM,iBAAiB,CAAC;AAE1C,OAAO,EAAC,cAAc,EAAE,MAAM,EAAC,MAAM,iBAAiB,CAAC;AAEvD;;;GAGG;AACH,MAAM,WAAW,WAAW;IAC1B,oDAAoD;IACpD,GAAG,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,GAAG,SAAS,CAAC;IAClD,0EAA0E;IAC1E,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,SAAS,CAAC;IAC7C,0CAA0C;IAC1C,QAAQ,CAAC,MAAM,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI,CAAC;IACpD,sGAAsG;IACtG,GAAG,CAAC,KAAK,EAAE,cAAc,EAAE,MAAM,EAAE,UAAU,GAAG,IAAI,CAAC;IACrD,wBAAwB;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAsCD,wBAAgB,iBAAiB,IAAI,WAAW,CAE/C;AAED;;;;;;GAMG;AACH,wBAAgB,WAAW,CAAC,WAAW,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,SAAS,EAAE,GAAG,IAAI,CAK1F"}