@aztec/stdlib 0.82.2 → 0.82.3

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 (114) hide show
  1. package/dest/avm/avm.d.ts +919 -1252
  2. package/dest/avm/avm.d.ts.map +1 -1
  3. package/dest/avm/avm.js +138 -111
  4. package/dest/avm/avm_proving_request.d.ts +400 -575
  5. package/dest/avm/avm_proving_request.d.ts.map +1 -1
  6. package/dest/block/l2_block_downloader/l2_block_stream.d.ts +1 -0
  7. package/dest/block/l2_block_downloader/l2_block_stream.d.ts.map +1 -1
  8. package/dest/block/l2_block_downloader/l2_block_stream.js +6 -0
  9. package/dest/database-version/version_manager.d.ts +4 -2
  10. package/dest/database-version/version_manager.d.ts.map +1 -1
  11. package/dest/database-version/version_manager.js +13 -9
  12. package/dest/epoch-helpers/index.d.ts +2 -0
  13. package/dest/epoch-helpers/index.d.ts.map +1 -1
  14. package/dest/epoch-helpers/index.js +3 -0
  15. package/dest/file-store/factory.d.ts +7 -0
  16. package/dest/file-store/factory.d.ts.map +1 -0
  17. package/dest/file-store/factory.js +46 -0
  18. package/dest/file-store/gcs.d.ts +22 -0
  19. package/dest/file-store/gcs.d.ts.map +1 -0
  20. package/dest/file-store/gcs.js +115 -0
  21. package/dest/file-store/http.d.ts +15 -0
  22. package/dest/file-store/http.d.ts.map +1 -0
  23. package/dest/file-store/http.js +53 -0
  24. package/dest/file-store/index.d.ts +3 -0
  25. package/dest/file-store/index.d.ts.map +1 -0
  26. package/dest/file-store/index.js +2 -0
  27. package/dest/file-store/interface.d.ts +24 -0
  28. package/dest/file-store/interface.d.ts.map +1 -0
  29. package/dest/file-store/interface.js +1 -0
  30. package/dest/file-store/local.d.ts +16 -0
  31. package/dest/file-store/local.d.ts.map +1 -0
  32. package/dest/file-store/local.js +40 -0
  33. package/dest/interfaces/aztec-node-admin.d.ts +9 -1
  34. package/dest/interfaces/aztec-node-admin.d.ts.map +1 -1
  35. package/dest/interfaces/aztec-node-admin.js +2 -1
  36. package/dest/interfaces/aztec-node.d.ts +3 -0
  37. package/dest/interfaces/aztec-node.d.ts.map +1 -1
  38. package/dest/interfaces/aztec-node.js +2 -0
  39. package/dest/interfaces/p2p.d.ts +2 -0
  40. package/dest/interfaces/p2p.d.ts.map +1 -1
  41. package/dest/interfaces/p2p.js +2 -1
  42. package/dest/interfaces/prover-node.d.ts +4 -0
  43. package/dest/interfaces/prover-node.d.ts.map +1 -1
  44. package/dest/interfaces/prover-node.js +5 -1
  45. package/dest/interfaces/proving-job.d.ts +400 -575
  46. package/dest/interfaces/proving-job.d.ts.map +1 -1
  47. package/dest/interfaces/service.d.ts +3 -0
  48. package/dest/interfaces/service.d.ts.map +1 -1
  49. package/dest/interfaces/service.js +7 -0
  50. package/dest/interfaces/world_state.d.ts +13 -15
  51. package/dest/interfaces/world_state.d.ts.map +1 -1
  52. package/dest/snapshots/download.d.ts +9 -0
  53. package/dest/snapshots/download.d.ts.map +1 -0
  54. package/dest/snapshots/download.js +37 -0
  55. package/dest/snapshots/index.d.ts +4 -0
  56. package/dest/snapshots/index.d.ts.map +1 -0
  57. package/dest/snapshots/index.js +3 -0
  58. package/dest/snapshots/types.d.ts +97 -0
  59. package/dest/snapshots/types.d.ts.map +1 -0
  60. package/dest/snapshots/types.js +27 -0
  61. package/dest/snapshots/upload.d.ts +5 -0
  62. package/dest/snapshots/upload.d.ts.map +1 -0
  63. package/dest/snapshots/upload.js +37 -0
  64. package/dest/tests/factories.d.ts +13 -7
  65. package/dest/tests/factories.d.ts.map +1 -1
  66. package/dest/tests/factories.js +37 -25
  67. package/dest/trees/merkle_tree_id.d.ts +8 -0
  68. package/dest/trees/merkle_tree_id.d.ts.map +1 -1
  69. package/dest/trees/merkle_tree_id.js +10 -0
  70. package/dest/trees/nullifier_leaf.d.ts +11 -2
  71. package/dest/trees/nullifier_leaf.d.ts.map +1 -1
  72. package/dest/trees/nullifier_leaf.js +12 -7
  73. package/dest/trees/nullifier_membership_witness.d.ts +7 -7
  74. package/dest/trees/public_data_leaf.d.ts +13 -0
  75. package/dest/trees/public_data_leaf.d.ts.map +1 -1
  76. package/dest/trees/public_data_leaf.js +6 -0
  77. package/dest/trees/public_data_witness.d.ts +7 -7
  78. package/dest/validators/index.d.ts +3 -0
  79. package/dest/validators/index.d.ts.map +1 -0
  80. package/dest/validators/index.js +1 -0
  81. package/dest/validators/schemas.d.ts +342 -0
  82. package/dest/validators/schemas.d.ts.map +1 -0
  83. package/dest/validators/schemas.js +40 -0
  84. package/dest/validators/types.d.ts +39 -0
  85. package/dest/validators/types.d.ts.map +1 -0
  86. package/dest/validators/types.js +1 -0
  87. package/package.json +11 -7
  88. package/src/avm/avm.ts +131 -106
  89. package/src/block/l2_block_downloader/l2_block_stream.ts +6 -0
  90. package/src/database-version/version_manager.ts +12 -8
  91. package/src/epoch-helpers/index.ts +8 -0
  92. package/src/file-store/factory.ts +61 -0
  93. package/src/file-store/gcs.ts +121 -0
  94. package/src/file-store/http.ts +58 -0
  95. package/src/file-store/index.ts +2 -0
  96. package/src/file-store/interface.ts +19 -0
  97. package/src/file-store/local.ts +46 -0
  98. package/src/interfaces/aztec-node-admin.ts +11 -1
  99. package/src/interfaces/aztec-node.ts +7 -0
  100. package/src/interfaces/p2p.ts +4 -0
  101. package/src/interfaces/prover-node.ts +10 -0
  102. package/src/interfaces/service.ts +13 -0
  103. package/src/interfaces/world_state.ts +17 -15
  104. package/src/snapshots/download.ts +60 -0
  105. package/src/snapshots/index.ts +3 -0
  106. package/src/snapshots/types.ts +58 -0
  107. package/src/snapshots/upload.ts +53 -0
  108. package/src/tests/factories.ts +74 -54
  109. package/src/trees/merkle_tree_id.ts +12 -0
  110. package/src/trees/nullifier_leaf.ts +9 -5
  111. package/src/trees/public_data_leaf.ts +9 -0
  112. package/src/validators/index.ts +3 -0
  113. package/src/validators/schemas.ts +53 -0
  114. package/src/validators/types.ts +37 -0
@@ -0,0 +1,58 @@
1
+ import { type Logger, createLogger } from '@aztec/foundation/log';
2
+ import { makeBackoff, retry } from '@aztec/foundation/retry';
3
+
4
+ import { createWriteStream } from 'fs';
5
+ import { mkdir } from 'fs/promises';
6
+ import { dirname } from 'path';
7
+ import { Readable } from 'stream';
8
+ import { finished } from 'stream/promises';
9
+
10
+ import type { ReadOnlyFileStore } from './interface.js';
11
+
12
+ export class HttpFileStore implements ReadOnlyFileStore {
13
+ private readonly fetch: typeof fetch;
14
+
15
+ constructor(private readonly baseUrl: string, private readonly log: Logger = createLogger('stdlib:http-file-store')) {
16
+ this.fetch = async (...args: Parameters<typeof fetch>): Promise<Response> => {
17
+ return await retry(
18
+ () => fetch(...args),
19
+ `Fetching ${args[0]}`,
20
+ makeBackoff([1, 1, 3]),
21
+ this.log,
22
+ /*failSilently=*/ true,
23
+ );
24
+ };
25
+ }
26
+
27
+ public async read(pathOrUrl: string): Promise<Buffer> {
28
+ const url = this.getUrl(pathOrUrl);
29
+ const response = await this.fetch(url);
30
+ if (response.ok) {
31
+ return Buffer.from(await response.arrayBuffer());
32
+ } else {
33
+ throw new Error(`Error fetching file from ${url}: ${response.statusText}`);
34
+ }
35
+ }
36
+
37
+ public async download(pathOrUrl: string, destPath: string): Promise<void> {
38
+ const url = this.getUrl(pathOrUrl);
39
+ const response = await this.fetch(url);
40
+ if (response.ok) {
41
+ await mkdir(dirname(destPath), { recursive: true });
42
+ // Typescript complains about Readable.fromWeb, hence the cast
43
+ await finished(Readable.fromWeb(response.body! as any).pipe(createWriteStream(destPath)));
44
+ } else {
45
+ throw new Error(`Error fetching file from ${url}: ${response.statusText}`);
46
+ }
47
+ }
48
+
49
+ public async exists(pathOrUrl: string): Promise<boolean> {
50
+ const url = this.getUrl(pathOrUrl);
51
+ const response = await this.fetch(url);
52
+ return response.ok;
53
+ }
54
+
55
+ private getUrl(path: string): string {
56
+ return URL.canParse(path) ? path : `${this.baseUrl.replace(/\/$/, '')}/${path}`;
57
+ }
58
+ }
@@ -0,0 +1,2 @@
1
+ export * from './interface.js';
2
+ export * from './factory.js';
@@ -0,0 +1,19 @@
1
+ /** Simple read-only file store. */
2
+ export interface ReadOnlyFileStore {
3
+ /** Reads a file given a path, or an URI as returned by calling `save`. Returns file contents. */
4
+ read(pathOrUrl: string): Promise<Buffer>;
5
+ /** Downloads a file given a path, or an URI as returned by calling `save`. Saves file to local path. */
6
+ download(pathOrUrlStr: string, destPath: string): Promise<void>;
7
+ /** Returns whether a file at the given path or URI exists. */
8
+ exists(pathOrUrl: string): Promise<boolean>;
9
+ }
10
+
11
+ export type FileStoreSaveOptions = { public?: boolean; metadata?: Record<string, string>; compress?: boolean };
12
+
13
+ /** Simple file store. */
14
+ export interface FileStore extends ReadOnlyFileStore {
15
+ /** Saves contents to the given path. Returns an URI that can be used later to `read` the file. */
16
+ save(path: string, data: Buffer, opts?: FileStoreSaveOptions): Promise<string>;
17
+ /** Uploads contents from a local file. Returns an URI that can be used later to `read` the file. */
18
+ upload(destPath: string, srcPath: string, opts?: FileStoreSaveOptions): Promise<string>;
19
+ }
@@ -0,0 +1,46 @@
1
+ import { access, mkdir, readFile, writeFile } from 'fs/promises';
2
+ import { dirname, resolve } from 'path';
3
+
4
+ import type { FileStore } from './interface.js';
5
+
6
+ export class LocalFileStore implements FileStore {
7
+ constructor(private readonly basePath: string) {}
8
+
9
+ public async save(path: string, data: Buffer): Promise<string> {
10
+ const fullPath = this.getFullPath(path);
11
+ await mkdir(dirname(fullPath), { recursive: true });
12
+ await writeFile(fullPath, data);
13
+ return `file://${fullPath}`;
14
+ }
15
+
16
+ public async upload(destPath: string, srcPath: string, _opts: { compress: boolean }): Promise<string> {
17
+ const data = await readFile(srcPath);
18
+ return this.save(destPath, data);
19
+ }
20
+
21
+ public read(pathOrUrlStr: string): Promise<Buffer> {
22
+ const fullPath = this.getFullPath(pathOrUrlStr);
23
+ return readFile(fullPath);
24
+ }
25
+
26
+ public async download(pathOrUrlStr: string, destPath: string): Promise<void> {
27
+ const data = await this.read(pathOrUrlStr);
28
+ const fullPath = this.getFullPath(destPath);
29
+ await writeFile(fullPath, data);
30
+ }
31
+
32
+ public exists(pathOrUrlStr: string): Promise<boolean> {
33
+ const fullPath = this.getFullPath(pathOrUrlStr);
34
+ return access(fullPath)
35
+ .then(() => true)
36
+ .catch(() => false);
37
+ }
38
+
39
+ private getFullPath(pathOrUrl: string): string {
40
+ if (URL.canParse(pathOrUrl)) {
41
+ return new URL(pathOrUrl).pathname;
42
+ } else {
43
+ return resolve(this.basePath, pathOrUrl);
44
+ }
45
+ }
46
+ }
@@ -17,13 +17,23 @@ export interface AztecNodeAdmin {
17
17
  */
18
18
  setConfig(config: Partial<SequencerConfig & ProverConfig>): Promise<void>;
19
19
 
20
- /** Forces the next block to be built bypassing all time and pending checks. Useful for testing. */
20
+ /**
21
+ * Forces the next block to be built bypassing all time and pending checks.
22
+ * Useful for testing.
23
+ */
21
24
  flushTxs(): Promise<void>;
25
+
26
+ /**
27
+ * Pauses syncing, creates a backup of archiver and world-state databases, and uploads them. Returns immediately.
28
+ * @param location - The location to upload the snapshot to.
29
+ */
30
+ startSnapshotUpload(location: string): Promise<void>;
22
31
  }
23
32
 
24
33
  export const AztecNodeAdminApiSchema: ApiSchemaFor<AztecNodeAdmin> = {
25
34
  setConfig: z.function().args(SequencerConfigSchema.merge(ProverConfigSchema).partial()).returns(z.void()),
26
35
  flushTxs: z.function().returns(z.void()),
36
+ startSnapshotUpload: z.function().args(z.string()).returns(z.void()),
27
37
  };
28
38
 
29
39
  export function createAztecNodeAdminClient(
@@ -46,6 +46,8 @@ import {
46
46
  TxValidationResultSchema,
47
47
  } from '../tx/index.js';
48
48
  import { TxEffect } from '../tx/tx_effect.js';
49
+ import { ValidatorsStatsSchema } from '../validators/schemas.js';
50
+ import type { ValidatorsStats } from '../validators/types.js';
49
51
  import { type ComponentsVersions, getVersioningResponseHandler } from '../versioning/index.js';
50
52
  import {
51
53
  type GetContractClassLogsResponse,
@@ -379,6 +381,9 @@ export interface AztecNode
379
381
  */
380
382
  getBlockHeader(blockNumber?: L2BlockNumber): Promise<BlockHeader | undefined>;
381
383
 
384
+ /** Returns stats for validators if enabled. */
385
+ getValidatorsStats(): Promise<ValidatorsStats>;
386
+
382
387
  /**
383
388
  * Simulates the public part of a transaction with the current state.
384
389
  * This currently just checks that the transaction execution succeeds.
@@ -525,6 +530,8 @@ export const AztecNodeApiSchema: ApiSchemaFor<AztecNode> = {
525
530
 
526
531
  getBlockHeader: z.function().args(optional(L2BlockNumberSchema)).returns(BlockHeader.schema.optional()),
527
532
 
533
+ getValidatorsStats: z.function().returns(ValidatorsStatsSchema),
534
+
528
535
  simulatePublicCalls: z.function().args(Tx.schema, optional(z.boolean())).returns(PublicSimulationOutput.schema),
529
536
 
530
537
  isValidTx: z
@@ -50,6 +50,9 @@ export interface P2PClient extends P2PApiWithoutAttestations {
50
50
  * @returns BlockAttestations
51
51
  */
52
52
  getAttestationsForSlot(slot: bigint, proposalId?: string): Promise<BlockAttestation[]>;
53
+
54
+ /** Manually adds an attestation to the p2p client attestation pool. */
55
+ addAttestation(attestation: BlockAttestation): Promise<void>;
53
56
  }
54
57
 
55
58
  export type P2PApi<T extends P2PClientType = P2PClientType.Full> = T extends P2PClientType.Full
@@ -64,4 +67,5 @@ export const P2PApiSchema: ApiSchemaFor<P2PApi> = {
64
67
  getPendingTxs: z.function().returns(z.array(Tx.schema)),
65
68
  getEncodedEnr: z.function().returns(z.string().optional()),
66
69
  getPeers: z.function().args(optional(z.boolean())).returns(z.array(PeerInfoSchema)),
70
+ addAttestation: z.function().args(BlockAttestation.schema).returns(z.void()),
67
71
  };
@@ -1,6 +1,8 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ import { type L2Tips, L2TipsSchema } from '../block/l2_block_source.js';
3
4
  import { type ApiSchemaFor, schemas } from '../schemas/index.js';
5
+ import { type WorldStateSyncStatus, WorldStateSyncStatusSchema } from './world_state.js';
4
6
 
5
7
  const EpochProvingJobState = [
6
8
  'initialized',
@@ -31,6 +33,10 @@ export interface ProverNodeApi {
31
33
  getJobs(): Promise<{ uuid: string; status: EpochProvingJobState; epochNumber: number }[]>;
32
34
 
33
35
  startProof(epochNumber: number): Promise<void>;
36
+
37
+ getL2Tips(): Promise<L2Tips>;
38
+
39
+ getWorldStateSyncStatus(): Promise<WorldStateSyncStatus>;
34
40
  }
35
41
 
36
42
  /** Schemas for prover node API functions. */
@@ -41,4 +47,8 @@ export const ProverNodeApiSchema: ApiSchemaFor<ProverNodeApi> = {
41
47
  .returns(z.array(z.object({ uuid: z.string(), status: z.enum(EpochProvingJobState), epochNumber: z.number() }))),
42
48
 
43
49
  startProof: z.function().args(schemas.Integer).returns(z.void()),
50
+
51
+ getL2Tips: z.function().args().returns(L2TipsSchema),
52
+
53
+ getWorldStateSyncStatus: z.function().args().returns(WorldStateSyncStatusSchema),
44
54
  };
@@ -11,6 +11,9 @@ export interface Service {
11
11
 
12
12
  /** Stops the service. */
13
13
  stop(): Promise<void>;
14
+
15
+ /** Resumes the service after it was stopped */
16
+ resume(): void;
14
17
  }
15
18
 
16
19
  /** Tries to call stop on a given object and awaits it. Logs any errors and does not rethrow. */
@@ -23,3 +26,13 @@ export async function tryStop(service: Maybe<Service>, logger?: Logger): Promise
23
26
  logger?.error(`Error stopping service ${(service as object).constructor?.name}: ${err}`);
24
27
  }
25
28
  }
29
+
30
+ export function tryRestart(service: Maybe<Service>, logger?: Logger) {
31
+ try {
32
+ return typeof service === 'object' && service && 'restart' in service && typeof service.restart === 'function'
33
+ ? service.restart()
34
+ : Promise.resolve();
35
+ } catch (err) {
36
+ logger?.error(`Error restarting service ${(service as object).constructor?.name}: ${err}`);
37
+ }
38
+ }
@@ -1,7 +1,10 @@
1
1
  import { z } from 'zod';
2
2
 
3
+ import type { SnapshotDataKeys } from '../snapshots/types.js';
3
4
  import type { MerkleTreeReadOperations, MerkleTreeWriteOperations } from './merkle_tree_operations.js';
4
5
 
6
+ export type { SnapshotDataKeys };
7
+
5
8
  /**
6
9
  * Defines the possible states of the world state synchronizer.
7
10
  */
@@ -41,37 +44,36 @@ export interface ForkMerkleTreeOperations {
41
44
 
42
45
  /** Gets a handle that allows reading the state as it was at the given block number. */
43
46
  getSnapshot(blockNumber: number): MerkleTreeReadOperations;
47
+
48
+ /** Backups the db to the target path. */
49
+ backupTo(dstPath: string, compact?: boolean): Promise<Record<Exclude<SnapshotDataKeys, 'archiver'>, string>>;
44
50
  }
45
51
 
46
52
  /** Defines the interface for a world state synchronizer. */
47
53
  export interface WorldStateSynchronizer extends ForkMerkleTreeOperations {
48
- /**
49
- * Starts the synchronizer.
50
- * @returns A promise that resolves once the initial sync is completed.
51
- */
54
+ /** Starts the synchronizer. */
52
55
  start(): void;
53
56
 
54
- /**
55
- * Returns the current status of the synchronizer.
56
- * @returns The current status of the synchronizer.
57
- */
57
+ /** Returns the current status of the synchronizer. */
58
58
  status(): Promise<WorldStateSynchronizerStatus>;
59
59
 
60
- /**
61
- * Stops the synchronizer.
62
- */
60
+ /** Stops the synchronizer and its database. */
63
61
  stop(): Promise<void>;
64
62
 
63
+ /** Stops the synchronizer from syncing, but keeps the database online. */
64
+ stopSync(): Promise<void>;
65
+
66
+ /** Resumes synching after a stopSync call. */
67
+ resumeSync(): void;
68
+
65
69
  /**
66
70
  * Forces an immediate sync to an optionally provided minimum block number
67
- * @param minBlockNumber - The minimum block number that we must sync to
71
+ * @param minBlockNumber - The minimum block number that we must sync to (may sync further)
68
72
  * @returns A promise that resolves with the block number the world state was synced to
69
73
  */
70
74
  syncImmediate(minBlockNumber?: number): Promise<number>;
71
75
 
72
- /**
73
- * Returns an instance of MerkleTreeAdminOperations that will not include uncommitted data.
74
- */
76
+ /** Returns an instance of MerkleTreeAdminOperations that will not include uncommitted data. */
75
77
  getCommitted(): MerkleTreeReadOperations;
76
78
  }
77
79
 
@@ -0,0 +1,60 @@
1
+ import { fromEntries, getEntries, maxBy } from '@aztec/foundation/collection';
2
+ import { jsonParseWithSchemaSync } from '@aztec/foundation/json-rpc';
3
+ import type { ReadOnlyFileStore } from '@aztec/stdlib/file-store';
4
+
5
+ import { join } from 'path';
6
+
7
+ import {
8
+ SnapshotDataKeys,
9
+ type SnapshotDataUrls,
10
+ type SnapshotMetadata,
11
+ type SnapshotsIndex,
12
+ type SnapshotsIndexMetadata,
13
+ SnapshotsIndexSchema,
14
+ } from './types.js';
15
+
16
+ export async function getSnapshotIndex(
17
+ metadata: SnapshotsIndexMetadata,
18
+ store: ReadOnlyFileStore,
19
+ ): Promise<SnapshotsIndex | undefined> {
20
+ const basePath = getBasePath(metadata);
21
+ const snapshotIndexPath = `${basePath}/index.json`;
22
+ try {
23
+ if (await store.exists(snapshotIndexPath)) {
24
+ const snapshotIndexData = await store.read(snapshotIndexPath);
25
+ return jsonParseWithSchemaSync(snapshotIndexData.toString(), SnapshotsIndexSchema);
26
+ } else {
27
+ return undefined;
28
+ }
29
+ } catch (err) {
30
+ throw new Error(`Error reading snapshot index from ${snapshotIndexPath}: ${err}`);
31
+ }
32
+ }
33
+
34
+ export async function getLatestSnapshotMetadata(
35
+ metadata: SnapshotsIndexMetadata,
36
+ store: ReadOnlyFileStore,
37
+ ): Promise<SnapshotMetadata | undefined> {
38
+ const snapshotsIndex = await getSnapshotIndex(metadata, store);
39
+ return snapshotsIndex?.snapshots && maxBy(snapshotsIndex?.snapshots, s => s.l1BlockNumber);
40
+ }
41
+
42
+ export function getBasePath(metadata: SnapshotsIndexMetadata): string {
43
+ return `aztec-${metadata.l1ChainId}-${metadata.l2Version}-${metadata.rollupAddress}`;
44
+ }
45
+
46
+ export function getSnapshotIndexPath(metadata: SnapshotsIndexMetadata): string {
47
+ return `${getBasePath(metadata)}/index.json`;
48
+ }
49
+
50
+ export function makeSnapshotLocalPaths(baseDir: string): SnapshotDataUrls {
51
+ return fromEntries(SnapshotDataKeys.map(key => [key, join(baseDir, `${key}.db`)]));
52
+ }
53
+
54
+ export async function downloadSnapshot(
55
+ snapshot: Pick<SnapshotMetadata, 'dataUrls'>,
56
+ localPaths: Record<SnapshotDataKeys, string>,
57
+ store: ReadOnlyFileStore,
58
+ ): Promise<void> {
59
+ await Promise.all(getEntries(localPaths).map(([key, path]) => store.download(snapshot.dataUrls[key], path)));
60
+ }
@@ -0,0 +1,3 @@
1
+ export * from './types.js';
2
+ export * from './upload.js';
3
+ export { downloadSnapshot, getLatestSnapshotMetadata, makeSnapshotLocalPaths } from './download.js';
@@ -0,0 +1,58 @@
1
+ import type { EthAddress } from '@aztec/foundation/eth-address';
2
+ import { type ZodFor, schemas } from '@aztec/foundation/schemas';
3
+
4
+ import { z } from 'zod';
5
+
6
+ export const SnapshotDataKeys = [
7
+ 'archiver',
8
+ 'nullifier-tree',
9
+ 'public-data-tree',
10
+ 'note-hash-tree',
11
+ 'archive-tree',
12
+ 'l1-to-l2-message-tree',
13
+ ] as const;
14
+
15
+ export type SnapshotDataKeys = (typeof SnapshotDataKeys)[number];
16
+
17
+ export type SnapshotDataUrls = Record<SnapshotDataKeys, string>;
18
+
19
+ export type SnapshotMetadata = {
20
+ l2BlockNumber: number;
21
+ l2BlockHash: string;
22
+ l1BlockNumber: number;
23
+ timestamp: number;
24
+ dataUrls: SnapshotDataUrls;
25
+ schemaVersions: { archiver: number; worldState: number };
26
+ };
27
+
28
+ export type SnapshotsIndexMetadata = {
29
+ l1ChainId: number;
30
+ l2Version: number;
31
+ rollupAddress: EthAddress;
32
+ };
33
+
34
+ export type SnapshotsIndex = SnapshotsIndexMetadata & {
35
+ snapshots: SnapshotMetadata[];
36
+ };
37
+
38
+ export const SnapshotsIndexSchema = z.object({
39
+ l1ChainId: z.number(),
40
+ l2Version: z.number(),
41
+ rollupAddress: schemas.EthAddress,
42
+ snapshots: z.array(
43
+ z.object({
44
+ l2BlockNumber: z.number(),
45
+ l2BlockHash: z.string(),
46
+ l1BlockNumber: z.number(),
47
+ timestamp: z.number(),
48
+ schemaVersions: z.object({
49
+ archiver: z.number(),
50
+ worldState: z.number(),
51
+ }),
52
+ dataUrls: z
53
+ .record(z.enum(SnapshotDataKeys), z.string())
54
+ // See https://stackoverflow.com/questions/77958464/zod-record-with-required-keys
55
+ .refine((obj): obj is Required<typeof obj> => SnapshotDataKeys.every(key => !!obj[key])),
56
+ }),
57
+ ),
58
+ }) satisfies ZodFor<SnapshotsIndex>;
@@ -0,0 +1,53 @@
1
+ import { fromEntries, getEntries, pick } from '@aztec/foundation/collection';
2
+ import { jsonStringify } from '@aztec/foundation/json-rpc';
3
+ import type { FileStore } from '@aztec/stdlib/file-store';
4
+
5
+ import { getBasePath, getSnapshotIndex, getSnapshotIndexPath } from './download.js';
6
+ import type { SnapshotDataKeys, SnapshotMetadata, SnapshotsIndex } from './types.js';
7
+
8
+ export type UploadSnapshotMetadata = Pick<SnapshotMetadata, 'l2BlockNumber' | 'l2BlockHash' | 'l1BlockNumber'> &
9
+ Pick<SnapshotsIndex, 'l1ChainId' | 'l2Version' | 'rollupAddress'>;
10
+
11
+ export async function uploadSnapshot(
12
+ localPaths: Record<SnapshotDataKeys, string>,
13
+ schemaVersions: SnapshotMetadata['schemaVersions'],
14
+ metadata: UploadSnapshotMetadata,
15
+ store: FileStore,
16
+ ): Promise<SnapshotMetadata> {
17
+ const timestamp = Date.now();
18
+ const date = new Date().toISOString().replace(/[-:T]/g, '').replace(/\..+$/, '');
19
+ const basePath = getBasePath(metadata);
20
+ const targetPathFor = (key: SnapshotDataKeys) => `${basePath}/${key}-${date}-${metadata.l2BlockHash}.db`;
21
+
22
+ const dataUrls = fromEntries(
23
+ await Promise.all(
24
+ getEntries(localPaths).map(
25
+ async ([key, path]) =>
26
+ [key, await store.upload(targetPathFor(key), path, { compress: true, public: true })] as const,
27
+ ),
28
+ ),
29
+ );
30
+
31
+ const snapshotsIndex = (await getSnapshotIndex(metadata, store)) ?? createEmptyIndex(metadata);
32
+
33
+ const newSnapshotMetadata: SnapshotMetadata = {
34
+ ...pick(metadata, 'l1BlockNumber', 'l2BlockHash', 'l2BlockNumber'),
35
+ schemaVersions,
36
+ timestamp,
37
+ dataUrls,
38
+ };
39
+ snapshotsIndex.snapshots.unshift(newSnapshotMetadata);
40
+
41
+ await store.save(getSnapshotIndexPath(metadata), Buffer.from(jsonStringify(snapshotsIndex, true)), {
42
+ public: true, // Make the index publicly accessible
43
+ metadata: { ['Cache-control']: 'no-store' }, // Do not cache object versions
44
+ });
45
+ return newSnapshotMetadata;
46
+ }
47
+
48
+ function createEmptyIndex(metadata: Pick<SnapshotsIndex, 'l1ChainId' | 'l2Version' | 'rollupAddress'>): SnapshotsIndex {
49
+ return {
50
+ ...pick(metadata, 'l1ChainId', 'l2Version', 'rollupAddress'),
51
+ snapshots: [],
52
+ };
53
+ }