@aztec/node-lib 0.85.0 → 0.86.0-nightly.20250426

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.
@@ -0,0 +1,5 @@
1
+ import type { Archiver } from '@aztec/archiver';
2
+ import type { UploadSnapshotMetadata } from '@aztec/stdlib/snapshots';
3
+ import type { UploadSnapshotConfig } from './upload-snapshot.js';
4
+ export declare function buildSnapshotMetadata(archiver: Archiver, config: UploadSnapshotConfig): Promise<UploadSnapshotMetadata>;
5
+ //# sourceMappingURL=build-snapshot-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"build-snapshot-metadata.d.ts","sourceRoot":"","sources":["../../src/actions/build-snapshot-metadata.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,yBAAyB,CAAC;AAEtE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AAEjE,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,QAAQ,EAClB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,sBAAsB,CAAC,CAoBjC"}
@@ -0,0 +1,19 @@
1
+ export async function buildSnapshotMetadata(archiver, config) {
2
+ const [rollupAddress, l1BlockNumber, { latest }] = await Promise.all([
3
+ archiver.getRollupAddress(),
4
+ archiver.getL1BlockNumber(),
5
+ archiver.getL2Tips()
6
+ ]);
7
+ const { number: l2BlockNumber, hash: l2BlockHash } = latest;
8
+ if (!l2BlockHash) {
9
+ throw new Error(`Failed to get L2 block hash from archiver.`);
10
+ }
11
+ return {
12
+ l1ChainId: config.l1ChainId,
13
+ rollupVersion: config.rollupVersion,
14
+ rollupAddress,
15
+ l2BlockNumber,
16
+ l2BlockHash,
17
+ l1BlockNumber: Number(l1BlockNumber)
18
+ };
19
+ }
@@ -0,0 +1,6 @@
1
+ import type { Archiver } from '@aztec/archiver';
2
+ import type { Logger } from '@aztec/foundation/log';
3
+ import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
4
+ import type { SnapshotDataUrls } from '@aztec/stdlib/snapshots';
5
+ export declare function createBackups(backupDir: string, archiver: Archiver, worldState: WorldStateSynchronizer, log: Logger): Promise<SnapshotDataUrls>;
6
+ //# sourceMappingURL=create-backups.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-backups.d.ts","sourceRoot":"","sources":["../../src/actions/create-backups.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAChD,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAC9E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAKhE,wBAAsB,aAAa,CACjC,SAAS,EAAE,MAAM,EACjB,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,sBAAsB,EAClC,GAAG,EAAE,MAAM,6BA4BZ"}
@@ -0,0 +1,32 @@
1
+ import { existsSync } from 'fs';
2
+ import { join } from 'path/posix';
3
+ export async function createBackups(backupDir, archiver, worldState, log) {
4
+ try {
5
+ log.info(`Pausing archiver and world state sync to start snapshot upload`);
6
+ await archiver.stop();
7
+ await worldState.stopSync();
8
+ log.info(`Creating backups of lmdb environments to ${backupDir}`);
9
+ const [archiverPath, worldStatePaths] = await Promise.all([
10
+ archiver.backupTo(join(backupDir, 'archiver')),
11
+ worldState.backupTo(join(backupDir, 'world-state'))
12
+ ]);
13
+ const paths = {
14
+ ...worldStatePaths,
15
+ archiver: archiverPath
16
+ };
17
+ const missing = Object.entries(paths).filter(([_key, path])=>!path || !existsSync(path));
18
+ if (missing.length > 0) {
19
+ throw new Error(`Missing backup files: ${missing.map(([key, path])=>`${path} (${key})`).join(', ')}`);
20
+ }
21
+ log.info(`Data stores backed up to ${backupDir}`, {
22
+ paths
23
+ });
24
+ return paths;
25
+ } catch (err) {
26
+ throw new Error(`Error creating backups for snapshot upload: ${err}`);
27
+ } finally{
28
+ log.info(`Resuming archiver and world state sync`);
29
+ worldState.resumeSync();
30
+ archiver.resume();
31
+ }
32
+ }
@@ -1,3 +1,5 @@
1
1
  export * from './snapshot-sync.js';
2
2
  export * from './upload-snapshot.js';
3
+ export * from './create-backups.js';
4
+ export * from './build-snapshot-metadata.js';
3
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/actions/index.ts"],"names":[],"mappings":"AAAA,cAAc,oBAAoB,CAAC;AACnC,cAAc,sBAAsB,CAAC;AACrC,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC"}
@@ -1,2 +1,4 @@
1
1
  export * from './snapshot-sync.js';
2
2
  export * from './upload-snapshot.js';
3
+ export * from './create-backups.js';
4
+ export * from './build-snapshot-metadata.js';
@@ -1,12 +1,26 @@
1
1
  import { type ArchiverConfig } from '@aztec/archiver';
2
2
  import { type EthereumClientConfig } from '@aztec/ethereum';
3
+ import type { EthAddress } from '@aztec/foundation/eth-address';
3
4
  import type { Logger } from '@aztec/foundation/log';
4
5
  import type { DataStoreConfig } from '@aztec/kv-store/config';
5
6
  import type { ChainConfig } from '@aztec/stdlib/config';
7
+ import { type SnapshotMetadata } from '@aztec/stdlib/snapshots';
6
8
  import type { SharedNodeConfig } from '../config/index.js';
7
9
  type SnapshotSyncConfig = Pick<SharedNodeConfig, 'syncMode' | 'snapshotsUrl'> & Pick<ChainConfig, 'l1ChainId' | 'rollupVersion'> & Pick<ArchiverConfig, 'archiverStoreMapSizeKb' | 'maxLogs'> & Required<DataStoreConfig> & EthereumClientConfig & {
8
10
  minL1BlocksToTriggerReplace?: number;
9
11
  };
12
+ /**
13
+ * Connects to a remote snapshot index and downloads the latest snapshot if the local archiver is behind.
14
+ * Behaviour depends on syncing mode.
15
+ */
10
16
  export declare function trySnapshotSync(config: SnapshotSyncConfig, log: Logger): Promise<boolean>;
17
+ /**
18
+ * Downloads the given snapshot replacing any local data stores.
19
+ */
20
+ export declare function snapshotSync(snapshot: Pick<SnapshotMetadata, 'dataUrls'>, log: Logger, config: {
21
+ dataDirectory: string;
22
+ rollupAddress: EthAddress;
23
+ snapshotsUrl: string;
24
+ }): Promise<void>;
11
25
  export {};
12
26
  //# sourceMappingURL=snapshot-sync.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"snapshot-sync.d.ts","sourceRoot":"","sources":["../../src/actions/snapshot-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,cAAc,EAGpB,MAAM,iBAAiB,CAAC;AAEzB,OAAO,EAAE,KAAK,oBAAoB,EAAmB,MAAM,iBAAiB,CAAC;AAG7E,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAexD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK3D,KAAK,kBAAkB,GAAG,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,cAAc,CAAC,GAC3E,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,eAAe,CAAC,GAChD,IAAI,CAAC,cAAc,EAAE,wBAAwB,GAAG,SAAS,CAAC,GAC1D,QAAQ,CAAC,eAAe,CAAC,GACzB,oBAAoB,GAAG;IACrB,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC,CAAC;AAEJ,wBAAsB,eAAe,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,oBAwJ5E"}
1
+ {"version":3,"file":"snapshot-sync.d.ts","sourceRoot":"","sources":["../../src/actions/snapshot-sync.ts"],"names":[],"mappings":"AAAA,OAAO,EAA4C,KAAK,cAAc,EAAuB,MAAM,iBAAiB,CAAC;AAErH,OAAO,EAAE,KAAK,oBAAoB,EAAmB,MAAM,iBAAiB,CAAC;AAC7E,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGxD,OAAO,EACL,KAAK,gBAAgB,EAKtB,MAAM,yBAAyB,CAAC;AAMjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK3D,KAAK,kBAAkB,GAAG,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,cAAc,CAAC,GAC3E,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,eAAe,CAAC,GAChD,IAAI,CAAC,cAAc,EAAE,wBAAwB,GAAG,SAAS,CAAC,GAC1D,QAAQ,CAAC,eAAe,CAAC,GACzB,oBAAoB,GAAG;IACrB,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC,CAAC;AAEJ;;;GAGG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,oBA2H5E;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,EAC5C,GAAG,EAAE,MAAM,EACX,MAAM,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,iBAgDnF"}
@@ -4,125 +4,165 @@ import { getPublicClient } from '@aztec/ethereum';
4
4
  import { tryRmDir } from '@aztec/foundation/fs';
5
5
  import { DatabaseVersionManager } from '@aztec/stdlib/database-version';
6
6
  import { createReadOnlyFileStore } from '@aztec/stdlib/file-store';
7
- import { downloadSnapshot, getLatestSnapshotMetadata, makeSnapshotLocalPaths } from '@aztec/stdlib/snapshots';
7
+ import { downloadSnapshot, getLatestSnapshotMetadata, makeSnapshotPaths } from '@aztec/stdlib/snapshots';
8
8
  import { NATIVE_WORLD_STATE_DBS, WORLD_STATE_DB_VERSION, WORLD_STATE_DIR } from '@aztec/world-state';
9
9
  import { mkdir, mkdtemp, rename } from 'fs/promises';
10
10
  import { join } from 'path';
11
11
  // Half day worth of L1 blocks
12
12
  const MIN_L1_BLOCKS_TO_TRIGGER_REPLACE = 86400 / 2 / 12;
13
- export async function trySnapshotSync(config, log) {
14
- let archiverStore;
13
+ /**
14
+ * Connects to a remote snapshot index and downloads the latest snapshot if the local archiver is behind.
15
+ * Behaviour depends on syncing mode.
16
+ */ export async function trySnapshotSync(config, log) {
17
+ const { syncMode, snapshotsUrl, dataDirectory, l1ChainId, rollupVersion, l1Contracts } = config;
18
+ if (syncMode === 'full') {
19
+ log.debug('Snapshot sync is disabled. Running full sync.', {
20
+ syncMode: syncMode
21
+ });
22
+ return false;
23
+ }
24
+ if (!snapshotsUrl) {
25
+ log.verbose('Snapshot sync is disabled. No snapshots URL provided.');
26
+ return false;
27
+ }
28
+ if (!dataDirectory) {
29
+ log.verbose('Snapshot sync is disabled. No local data directory defined.');
30
+ return false;
31
+ }
32
+ let fileStore;
33
+ try {
34
+ fileStore = await createReadOnlyFileStore(snapshotsUrl, log);
35
+ } catch (err) {
36
+ log.error(`Invalid config for downloading snapshots`, err);
37
+ return false;
38
+ }
39
+ // Create an archiver store to check the current state
40
+ log.verbose(`Creating temporary archiver data store`);
41
+ const archiverStore = await createArchiverStore(config);
42
+ let archiverL1BlockNumber;
43
+ let archiverL2BlockNumber;
44
+ try {
45
+ [archiverL1BlockNumber, archiverL2BlockNumber] = await Promise.all([
46
+ archiverStore.getSynchPoint().then((s)=>s.blocksSynchedTo),
47
+ archiverStore.getSynchedL2BlockNumber()
48
+ ]);
49
+ } finally{
50
+ log.verbose(`Closing temporary archiver data store`, {
51
+ archiverL1BlockNumber,
52
+ archiverL2BlockNumber
53
+ });
54
+ await archiverStore.close();
55
+ }
56
+ const minL1BlocksToTriggerReplace = config.minL1BlocksToTriggerReplace ?? MIN_L1_BLOCKS_TO_TRIGGER_REPLACE;
57
+ if (syncMode === 'snapshot' && archiverL2BlockNumber !== undefined && archiverL2BlockNumber >= INITIAL_L2_BLOCK_NUM) {
58
+ log.verbose(`Skipping non-forced snapshot sync as archiver is already synced to L2 block ${archiverL2BlockNumber}.`);
59
+ return false;
60
+ }
61
+ const currentL1BlockNumber = await getPublicClient(config).getBlockNumber();
62
+ if (archiverL1BlockNumber && currentL1BlockNumber - archiverL1BlockNumber < minL1BlocksToTriggerReplace) {
63
+ log.verbose(`Skipping snapshot sync as archiver is less than ${currentL1BlockNumber - archiverL1BlockNumber} L1 blocks behind.`, {
64
+ archiverL1BlockNumber,
65
+ currentL1BlockNumber,
66
+ minL1BlocksToTriggerReplace
67
+ });
68
+ return false;
69
+ }
70
+ const indexMetadata = {
71
+ l1ChainId,
72
+ rollupVersion,
73
+ rollupAddress: l1Contracts.rollupAddress
74
+ };
75
+ let snapshot;
76
+ try {
77
+ snapshot = await getLatestSnapshotMetadata(indexMetadata, fileStore);
78
+ } catch (err) {
79
+ log.error(`Failed to get latest snapshot metadata. Skipping snapshot sync.`, err, {
80
+ ...indexMetadata,
81
+ snapshotsUrl
82
+ });
83
+ return false;
84
+ }
85
+ if (!snapshot) {
86
+ log.verbose(`No snapshot found. Skipping snapshot sync.`, {
87
+ ...indexMetadata,
88
+ snapshotsUrl
89
+ });
90
+ return false;
91
+ }
92
+ if (snapshot.schemaVersions.archiver !== ARCHIVER_DB_VERSION) {
93
+ log.warn(`Skipping snapshot sync as last snapshot has schema version ${snapshot.schemaVersions.archiver} but expected ${ARCHIVER_DB_VERSION}.`, snapshot);
94
+ return false;
95
+ }
96
+ if (snapshot.schemaVersions.worldState !== WORLD_STATE_DB_VERSION) {
97
+ log.warn(`Skipping snapshot sync as last snapshot has world state schema version ${snapshot.schemaVersions.worldState} but we expected ${WORLD_STATE_DB_VERSION}.`, snapshot);
98
+ return false;
99
+ }
100
+ if (archiverL1BlockNumber && snapshot.l1BlockNumber < archiverL1BlockNumber) {
101
+ log.verbose(`Skipping snapshot sync since local archiver is at L1 block ${archiverL1BlockNumber} which is further than last snapshot at ${snapshot.l1BlockNumber}`, {
102
+ snapshot,
103
+ archiverL1BlockNumber
104
+ });
105
+ return false;
106
+ }
107
+ if (archiverL1BlockNumber && snapshot.l1BlockNumber - Number(archiverL1BlockNumber) < minL1BlocksToTriggerReplace) {
108
+ log.verbose(`Skipping snapshot sync as archiver is less than ${snapshot.l1BlockNumber - Number(archiverL1BlockNumber)} L1 blocks behind latest snapshot.`, {
109
+ snapshot,
110
+ archiverL1BlockNumber
111
+ });
112
+ return false;
113
+ }
114
+ const { l1BlockNumber, l2BlockNumber } = snapshot;
115
+ log.info(`Syncing from snapshot at L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, {
116
+ snapshot,
117
+ snapshotsUrl
118
+ });
119
+ await snapshotSync(snapshot, log, {
120
+ dataDirectory: config.dataDirectory,
121
+ rollupAddress: config.l1Contracts.rollupAddress,
122
+ snapshotsUrl
123
+ });
124
+ log.info(`Snapshot synced to L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, {
125
+ snapshot
126
+ });
127
+ return true;
128
+ }
129
+ /**
130
+ * Downloads the given snapshot replacing any local data stores.
131
+ */ export async function snapshotSync(snapshot, log, config) {
132
+ const { dataDirectory, rollupAddress } = config;
133
+ if (!dataDirectory) {
134
+ throw new Error(`No local data directory defined. Cannot sync snapshot.`);
135
+ }
136
+ const fileStore = await createReadOnlyFileStore(config.snapshotsUrl, log);
15
137
  let downloadDir;
16
138
  try {
17
- const { syncMode, snapshotsUrl, dataDirectory, l1ChainId, rollupVersion, l1Contracts } = config;
18
- if (syncMode === 'full') {
19
- log.debug('Snapshot sync is disabled. Running full sync.', {
20
- syncMode: syncMode
21
- });
22
- return false;
23
- }
24
- if (!snapshotsUrl) {
25
- log.verbose('Snapshot sync is disabled. No snapshots URL provided.');
26
- return false;
27
- }
28
- if (!dataDirectory) {
29
- log.verbose('Snapshot sync is disabled. No local data directory defined.');
30
- return false;
31
- }
32
- let fileStore;
33
- try {
34
- fileStore = await createReadOnlyFileStore(snapshotsUrl, log);
35
- } catch (err) {
36
- log.error(`Invalid config for downloading snapshots`, err);
37
- return false;
38
- }
39
- // Create an archiver store to check the current sync state
40
- archiverStore = await createArchiverStore(config);
41
- const minL1BlocksToTriggerReplace = config.minL1BlocksToTriggerReplace ?? MIN_L1_BLOCKS_TO_TRIGGER_REPLACE;
42
- const archiverL2BlockNumber = await archiverStore.getSynchedL2BlockNumber();
43
- if (syncMode === 'snapshot' && archiverL2BlockNumber !== undefined && archiverL2BlockNumber >= INITIAL_L2_BLOCK_NUM) {
44
- log.verbose(`Skipping non-forced snapshot sync as archiver is already synced to L2 block ${archiverL2BlockNumber}.`);
45
- return false;
46
- }
47
- const currentL1BlockNumber = await getPublicClient(config).getBlockNumber();
48
- const archiverL1BlockNumber = await archiverStore.getSynchPoint().then((s)=>s.blocksSynchedTo);
49
- if (archiverL1BlockNumber && currentL1BlockNumber - archiverL1BlockNumber < minL1BlocksToTriggerReplace) {
50
- log.verbose(`Skipping snapshot sync as archiver is less than ${currentL1BlockNumber - archiverL1BlockNumber} L1 blocks behind.`, {
51
- archiverL1BlockNumber,
52
- currentL1BlockNumber,
53
- minL1BlocksToTriggerReplace
54
- });
55
- return false;
56
- }
57
- const indexMetadata = {
58
- l1ChainId,
59
- rollupVersion,
60
- rollupAddress: l1Contracts.rollupAddress
61
- };
62
- let snapshot;
63
- try {
64
- snapshot = await getLatestSnapshotMetadata(indexMetadata, fileStore);
65
- } catch (err) {
66
- log.error(`Failed to get latest snapshot metadata. Skipping snapshot sync.`, err, {
67
- ...indexMetadata,
68
- snapshotsUrl
69
- });
70
- return false;
71
- }
72
- if (!snapshot) {
73
- log.verbose(`No snapshot found. Skipping snapshot sync.`, {
74
- ...indexMetadata,
75
- snapshotsUrl
76
- });
77
- return false;
78
- }
79
- if (snapshot.schemaVersions.archiver !== ARCHIVER_DB_VERSION) {
80
- log.warn(`Skipping snapshot sync as last snapshot has schema version ${snapshot.schemaVersions.archiver} but expected ${ARCHIVER_DB_VERSION}.`, snapshot);
81
- return false;
82
- }
83
- if (snapshot.schemaVersions.worldState !== WORLD_STATE_DB_VERSION) {
84
- log.warn(`Skipping snapshot sync as last snapshot has world state schema version ${snapshot.schemaVersions.worldState} but we expected ${WORLD_STATE_DB_VERSION}.`, snapshot);
85
- return false;
86
- }
87
- if (archiverL1BlockNumber && snapshot.l1BlockNumber < archiverL1BlockNumber) {
88
- log.verbose(`Skipping snapshot sync since local archiver is at L1 block ${archiverL1BlockNumber} which is further than last snapshot at ${snapshot.l1BlockNumber}`, {
89
- snapshot,
90
- archiverL1BlockNumber
91
- });
92
- return false;
93
- }
94
- if (archiverL1BlockNumber && snapshot.l1BlockNumber - Number(archiverL1BlockNumber) < minL1BlocksToTriggerReplace) {
95
- log.verbose(`Skipping snapshot sync as archiver is less than ${snapshot.l1BlockNumber - Number(archiverL1BlockNumber)} L1 blocks behind latest snapshot.`, {
96
- snapshot,
97
- archiverL1BlockNumber
98
- });
99
- return false;
100
- }
101
- // Green light. Download the snapshot to a temp location.
139
+ // Download the snapshot to a temp location.
140
+ await mkdir(dataDirectory, {
141
+ recursive: true
142
+ });
102
143
  downloadDir = await mkdtemp(join(dataDirectory, 'download-'));
103
- const downloadPaths = makeSnapshotLocalPaths(downloadDir);
104
- log.info(`Downloading snapshot at L1 block ${snapshot.l1BlockNumber} L2 block ${snapshot.l2BlockNumber} from ${snapshotsUrl} to ${downloadDir} for snapshot sync`, {
144
+ const downloadPaths = makeSnapshotPaths(downloadDir);
145
+ log.info(`Downloading snapshot to ${downloadDir}`, {
105
146
  snapshot,
106
147
  downloadPaths
107
148
  });
108
149
  await downloadSnapshot(snapshot, downloadPaths, fileStore);
109
150
  log.info(`Snapshot downloaded at ${downloadDir}`, {
110
- snapshotsUrl,
111
151
  snapshot,
112
152
  downloadPaths
113
153
  });
114
- // If download was successful, close the archiver store, clear lock and version, and move download there
115
- await archiverStore.close();
116
- archiverStore = undefined;
154
+ // If download was successful, clear lock and version, and move download there
117
155
  const archiverPath = join(dataDirectory, ARCHIVER_STORE_NAME);
118
- await prepareTarget(archiverPath, ARCHIVER_DB_VERSION, l1Contracts.rollupAddress);
156
+ await prepareTarget(archiverPath, ARCHIVER_DB_VERSION, rollupAddress);
119
157
  await rename(downloadPaths.archiver, join(archiverPath, 'data.mdb'));
120
158
  log.info(`Archiver database set up from snapshot`, {
121
- path: archiverPath
159
+ path: archiverPath,
160
+ dbVersion: ARCHIVER_DB_VERSION,
161
+ rollupAddress
122
162
  });
123
163
  // Same for the world state dbs, only that we do not close them, since we assume they are not yet in use
124
164
  const worldStateBasePath = join(dataDirectory, WORLD_STATE_DIR);
125
- await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION, l1Contracts.rollupAddress);
165
+ await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION, rollupAddress);
126
166
  for (const [name, dir] of NATIVE_WORLD_STATE_DBS){
127
167
  const path = join(worldStateBasePath, dir);
128
168
  await mkdir(path, {
@@ -130,22 +170,16 @@ export async function trySnapshotSync(config, log) {
130
170
  });
131
171
  await rename(downloadPaths[name], join(path, 'data.mdb'));
132
172
  log.info(`World state database ${name} set up from snapshot`, {
133
- path
173
+ path,
174
+ dbVersion: WORLD_STATE_DB_VERSION,
175
+ rollupAddress
134
176
  });
135
177
  }
136
- log.info(`Snapshot synced to L1 block ${snapshot.l1BlockNumber} L2 block ${snapshot.l2BlockNumber}`, {
137
- snapshot
138
- });
139
178
  } finally{
140
- if (archiverStore) {
141
- log.verbose(`Closing temporary archiver data store`);
142
- await archiverStore.close();
143
- }
144
179
  if (downloadDir) {
145
180
  await tryRmDir(downloadDir, log);
146
181
  }
147
182
  }
148
- return true;
149
183
  }
150
184
  /** Deletes target dir and writes the new version file. */ async function prepareTarget(target, schemaVersion, rollupAddress) {
151
185
  const noOpen = ()=>Promise.resolve(undefined);
@@ -3,11 +3,10 @@ import type { Logger } from '@aztec/foundation/log';
3
3
  import type { DataStoreConfig } from '@aztec/kv-store/config';
4
4
  import type { ChainConfig } from '@aztec/stdlib/config';
5
5
  import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
6
- type UploadSnapshotConfig = Pick<ChainConfig, 'l1ChainId' | 'rollupVersion'> & Pick<DataStoreConfig, 'dataDirectory'>;
6
+ export type UploadSnapshotConfig = Pick<ChainConfig, 'l1ChainId' | 'rollupVersion'> & Pick<DataStoreConfig, 'dataDirectory'>;
7
7
  /**
8
8
  * Pauses the archiver and world state sync, creates backups of the archiver and world state lmdb environments,
9
9
  * and uploads them to the specified location. Location must be a URL supported by our file store (eg `gs://bucketname/path`).
10
10
  */
11
11
  export declare function uploadSnapshot(location: string, archiver: Archiver, worldState: WorldStateSynchronizer, config: UploadSnapshotConfig, log: Logger): Promise<void>;
12
- export {};
13
12
  //# sourceMappingURL=upload-snapshot.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"upload-snapshot.d.ts","sourceRoot":"","sources":["../../src/actions/upload-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAErE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAU9E,KAAK,oBAAoB,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,eAAe,CAAC,GAAG,IAAI,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;AAEtH;;;GAGG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,sBAAsB,EAClC,MAAM,EAAE,oBAAoB,EAC5B,GAAG,EAAE,MAAM,iBAoBZ"}
1
+ {"version":3,"file":"upload-snapshot.d.ts","sourceRoot":"","sources":["../../src/actions/upload-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAuB,KAAK,QAAQ,EAAE,MAAM,iBAAiB,CAAC;AAErE,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AACpD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAExD,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,iCAAiC,CAAC;AAW9E,MAAM,MAAM,oBAAoB,GAAG,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,eAAe,CAAC,GACjF,IAAI,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC;AAEzC;;;GAGG;AACH,wBAAsB,cAAc,CAClC,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,QAAQ,EAClB,UAAU,EAAE,sBAAsB,EAClC,MAAM,EAAE,oBAAoB,EAC5B,GAAG,EAAE,MAAM,iBAoBZ"}
@@ -1,12 +1,13 @@
1
1
  import { ARCHIVER_DB_VERSION } from '@aztec/archiver';
2
2
  import { tryRmDir } from '@aztec/foundation/fs';
3
3
  import { createFileStore } from '@aztec/stdlib/file-store';
4
- import { uploadSnapshot as uploadSnapshotToStore } from '@aztec/stdlib/snapshots';
4
+ import { uploadSnapshotToIndex } from '@aztec/stdlib/snapshots';
5
5
  import { WORLD_STATE_DB_VERSION } from '@aztec/world-state';
6
- import { existsSync } from 'fs';
7
6
  import { mkdtemp } from 'fs/promises';
8
7
  import { tmpdir } from 'os';
9
8
  import { join } from 'path';
9
+ import { buildSnapshotMetadata } from './build-snapshot-metadata.js';
10
+ import { createBackups } from './create-backups.js';
10
11
  /**
11
12
  * Pauses the archiver and world state sync, creates backups of the archiver and world state lmdb environments,
12
13
  * and uploads them to the specified location. Location must be a URL supported by our file store (eg `gs://bucketname/path`).
@@ -26,7 +27,7 @@ import { join } from 'path';
26
27
  log.info(`Uploading snapshot to ${location}`, {
27
28
  snapshot: metadata
28
29
  });
29
- const snapshot = await uploadSnapshotToStore(paths, versions, metadata, store);
30
+ const snapshot = await uploadSnapshotToIndex(paths, versions, metadata, store);
30
31
  log.info(`Snapshot uploaded successfully`, {
31
32
  snapshot
32
33
  });
@@ -35,52 +36,3 @@ import { join } from 'path';
35
36
  await tryRmDir(backupDir, log);
36
37
  }
37
38
  }
38
- async function buildSnapshotMetadata(archiver, config) {
39
- const [rollupAddress, l1BlockNumber, { latest }] = await Promise.all([
40
- archiver.getRollupAddress(),
41
- archiver.getL1BlockNumber(),
42
- archiver.getL2Tips()
43
- ]);
44
- const { number: l2BlockNumber, hash: l2BlockHash } = latest;
45
- if (!l2BlockHash) {
46
- throw new Error(`Failed to get L2 block hash from archiver.`);
47
- }
48
- return {
49
- l1ChainId: config.l1ChainId,
50
- rollupVersion: config.rollupVersion,
51
- rollupAddress,
52
- l2BlockNumber,
53
- l2BlockHash,
54
- l1BlockNumber: Number(l1BlockNumber)
55
- };
56
- }
57
- async function createBackups(backupDir, archiver, worldState, log) {
58
- try {
59
- log.info(`Pausing archiver and world state sync to start snapshot upload`);
60
- await archiver.stop();
61
- await worldState.stopSync();
62
- log.info(`Creating backups of lmdb environments to ${backupDir}`);
63
- const [archiverPath, worldStatePaths] = await Promise.all([
64
- archiver.backupTo(join(backupDir, 'archiver')),
65
- worldState.backupTo(join(backupDir, 'world-state'))
66
- ]);
67
- const paths = {
68
- ...worldStatePaths,
69
- archiver: archiverPath
70
- };
71
- const missing = Object.entries(paths).filter(([_key, path])=>!path || !existsSync(path));
72
- if (missing.length > 0) {
73
- throw new Error(`Missing backup files: ${missing.map(([key, path])=>`${path} (${key})`).join(', ')}`);
74
- }
75
- log.info(`Data stores backed up to ${backupDir}`, {
76
- paths
77
- });
78
- return paths;
79
- } catch (err) {
80
- throw new Error(`Error creating backups for snapshot upload: ${err}`);
81
- } finally{
82
- log.info(`Resuming archiver and world state sync`);
83
- worldState.resumeSync();
84
- archiver.resume();
85
- }
86
- }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/node-lib",
3
- "version": "0.85.0",
3
+ "version": "0.86.0-nightly.20250426",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./actions": "./dest/actions/index.js",
@@ -13,8 +13,6 @@
13
13
  "build": "yarn clean && tsc -b",
14
14
  "build:dev": "tsc -b --watch",
15
15
  "clean": "rm -rf ./dest .tsbuildinfo",
16
- "formatting": "run -T prettier --check ./src && run -T eslint ./src",
17
- "formatting:fix": "run -T eslint --fix ./src && run -T prettier -w ./src",
18
16
  "bb": "node --no-warnings ./dest/bb/index.js",
19
17
  "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}"
20
18
  },
@@ -52,24 +50,24 @@
52
50
  ]
53
51
  },
54
52
  "dependencies": {
55
- "@aztec/archiver": "0.85.0",
56
- "@aztec/bb-prover": "0.85.0",
57
- "@aztec/blob-sink": "0.85.0",
58
- "@aztec/constants": "0.85.0",
59
- "@aztec/epoch-cache": "0.85.0",
60
- "@aztec/ethereum": "0.85.0",
61
- "@aztec/foundation": "0.85.0",
62
- "@aztec/kv-store": "0.85.0",
63
- "@aztec/merkle-tree": "0.85.0",
64
- "@aztec/p2p": "0.85.0",
65
- "@aztec/protocol-contracts": "0.85.0",
66
- "@aztec/prover-client": "0.85.0",
67
- "@aztec/sequencer-client": "0.85.0",
68
- "@aztec/simulator": "0.85.0",
69
- "@aztec/stdlib": "0.85.0",
70
- "@aztec/telemetry-client": "0.85.0",
71
- "@aztec/validator-client": "0.85.0",
72
- "@aztec/world-state": "0.85.0",
53
+ "@aztec/archiver": "0.86.0-nightly.20250426",
54
+ "@aztec/bb-prover": "0.86.0-nightly.20250426",
55
+ "@aztec/blob-sink": "0.86.0-nightly.20250426",
56
+ "@aztec/constants": "0.86.0-nightly.20250426",
57
+ "@aztec/epoch-cache": "0.86.0-nightly.20250426",
58
+ "@aztec/ethereum": "0.86.0-nightly.20250426",
59
+ "@aztec/foundation": "0.86.0-nightly.20250426",
60
+ "@aztec/kv-store": "0.86.0-nightly.20250426",
61
+ "@aztec/merkle-tree": "0.86.0-nightly.20250426",
62
+ "@aztec/p2p": "0.86.0-nightly.20250426",
63
+ "@aztec/protocol-contracts": "0.86.0-nightly.20250426",
64
+ "@aztec/prover-client": "0.86.0-nightly.20250426",
65
+ "@aztec/sequencer-client": "0.86.0-nightly.20250426",
66
+ "@aztec/simulator": "0.86.0-nightly.20250426",
67
+ "@aztec/stdlib": "0.86.0-nightly.20250426",
68
+ "@aztec/telemetry-client": "0.86.0-nightly.20250426",
69
+ "@aztec/validator-client": "0.86.0-nightly.20250426",
70
+ "@aztec/world-state": "0.86.0-nightly.20250426",
73
71
  "tslib": "^2.4.0"
74
72
  },
75
73
  "devDependencies": {
@@ -0,0 +1,29 @@
1
+ import type { Archiver } from '@aztec/archiver';
2
+ import type { UploadSnapshotMetadata } from '@aztec/stdlib/snapshots';
3
+
4
+ import type { UploadSnapshotConfig } from './upload-snapshot.js';
5
+
6
+ export async function buildSnapshotMetadata(
7
+ archiver: Archiver,
8
+ config: UploadSnapshotConfig,
9
+ ): Promise<UploadSnapshotMetadata> {
10
+ const [rollupAddress, l1BlockNumber, { latest }] = await Promise.all([
11
+ archiver.getRollupAddress(),
12
+ archiver.getL1BlockNumber(),
13
+ archiver.getL2Tips(),
14
+ ] as const);
15
+
16
+ const { number: l2BlockNumber, hash: l2BlockHash } = latest;
17
+ if (!l2BlockHash) {
18
+ throw new Error(`Failed to get L2 block hash from archiver.`);
19
+ }
20
+
21
+ return {
22
+ l1ChainId: config.l1ChainId,
23
+ rollupVersion: config.rollupVersion,
24
+ rollupAddress,
25
+ l2BlockNumber,
26
+ l2BlockHash,
27
+ l1BlockNumber: Number(l1BlockNumber),
28
+ };
29
+ }
@@ -0,0 +1,41 @@
1
+ import type { Archiver } from '@aztec/archiver';
2
+ import type { Logger } from '@aztec/foundation/log';
3
+ import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
4
+ import type { SnapshotDataUrls } from '@aztec/stdlib/snapshots';
5
+
6
+ import { existsSync } from 'fs';
7
+ import { join } from 'path/posix';
8
+
9
+ export async function createBackups(
10
+ backupDir: string,
11
+ archiver: Archiver,
12
+ worldState: WorldStateSynchronizer,
13
+ log: Logger,
14
+ ) {
15
+ try {
16
+ log.info(`Pausing archiver and world state sync to start snapshot upload`);
17
+ await archiver.stop();
18
+ await worldState.stopSync();
19
+
20
+ log.info(`Creating backups of lmdb environments to ${backupDir}`);
21
+ const [archiverPath, worldStatePaths] = await Promise.all([
22
+ archiver.backupTo(join(backupDir, 'archiver')),
23
+ worldState.backupTo(join(backupDir, 'world-state')),
24
+ ]);
25
+ const paths: SnapshotDataUrls = { ...worldStatePaths, archiver: archiverPath };
26
+
27
+ const missing = Object.entries(paths).filter(([_key, path]) => !path || !existsSync(path));
28
+ if (missing.length > 0) {
29
+ throw new Error(`Missing backup files: ${missing.map(([key, path]) => `${path} (${key})`).join(', ')}`);
30
+ }
31
+
32
+ log.info(`Data stores backed up to ${backupDir}`, { paths });
33
+ return paths;
34
+ } catch (err) {
35
+ throw new Error(`Error creating backups for snapshot upload: ${err}`);
36
+ } finally {
37
+ log.info(`Resuming archiver and world state sync`);
38
+ worldState.resumeSync();
39
+ archiver.resume();
40
+ }
41
+ }
@@ -1,2 +1,4 @@
1
1
  export * from './snapshot-sync.js';
2
2
  export * from './upload-snapshot.js';
3
+ export * from './create-backups.js';
4
+ export * from './build-snapshot-metadata.js';
@@ -1,10 +1,4 @@
1
- import {
2
- ARCHIVER_DB_VERSION,
3
- ARCHIVER_STORE_NAME,
4
- type ArchiverConfig,
5
- type ArchiverDataStore,
6
- createArchiverStore,
7
- } from '@aztec/archiver';
1
+ import { ARCHIVER_DB_VERSION, ARCHIVER_STORE_NAME, type ArchiverConfig, createArchiverStore } from '@aztec/archiver';
8
2
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
9
3
  import { type EthereumClientConfig, getPublicClient } from '@aztec/ethereum';
10
4
  import type { EthAddress } from '@aztec/foundation/eth-address';
@@ -19,7 +13,7 @@ import {
19
13
  type SnapshotsIndexMetadata,
20
14
  downloadSnapshot,
21
15
  getLatestSnapshotMetadata,
22
- makeSnapshotLocalPaths,
16
+ makeSnapshotPaths,
23
17
  } from '@aztec/stdlib/snapshots';
24
18
  import { NATIVE_WORLD_STATE_DBS, WORLD_STATE_DB_VERSION, WORLD_STATE_DIR } from '@aztec/world-state';
25
19
 
@@ -39,158 +33,189 @@ type SnapshotSyncConfig = Pick<SharedNodeConfig, 'syncMode' | 'snapshotsUrl'> &
39
33
  minL1BlocksToTriggerReplace?: number;
40
34
  };
41
35
 
36
+ /**
37
+ * Connects to a remote snapshot index and downloads the latest snapshot if the local archiver is behind.
38
+ * Behaviour depends on syncing mode.
39
+ */
42
40
  export async function trySnapshotSync(config: SnapshotSyncConfig, log: Logger) {
43
- let archiverStore: ArchiverDataStore | undefined;
44
- let downloadDir: string | undefined;
41
+ const { syncMode, snapshotsUrl, dataDirectory, l1ChainId, rollupVersion, l1Contracts } = config;
42
+ if (syncMode === 'full') {
43
+ log.debug('Snapshot sync is disabled. Running full sync.', { syncMode: syncMode });
44
+ return false;
45
+ }
46
+
47
+ if (!snapshotsUrl) {
48
+ log.verbose('Snapshot sync is disabled. No snapshots URL provided.');
49
+ return false;
50
+ }
51
+
52
+ if (!dataDirectory) {
53
+ log.verbose('Snapshot sync is disabled. No local data directory defined.');
54
+ return false;
55
+ }
45
56
 
57
+ let fileStore: ReadOnlyFileStore;
46
58
  try {
47
- const { syncMode, snapshotsUrl, dataDirectory, l1ChainId, rollupVersion, l1Contracts } = config;
48
- if (syncMode === 'full') {
49
- log.debug('Snapshot sync is disabled. Running full sync.', { syncMode: syncMode });
50
- return false;
51
- }
59
+ fileStore = await createReadOnlyFileStore(snapshotsUrl, log);
60
+ } catch (err) {
61
+ log.error(`Invalid config for downloading snapshots`, err);
62
+ return false;
63
+ }
52
64
 
53
- if (!snapshotsUrl) {
54
- log.verbose('Snapshot sync is disabled. No snapshots URL provided.');
55
- return false;
56
- }
65
+ // Create an archiver store to check the current state
66
+ log.verbose(`Creating temporary archiver data store`);
67
+ const archiverStore = await createArchiverStore(config);
68
+ let archiverL1BlockNumber: bigint | undefined;
69
+ let archiverL2BlockNumber: number | undefined;
70
+ try {
71
+ [archiverL1BlockNumber, archiverL2BlockNumber] = await Promise.all([
72
+ archiverStore.getSynchPoint().then(s => s.blocksSynchedTo),
73
+ archiverStore.getSynchedL2BlockNumber(),
74
+ ] as const);
75
+ } finally {
76
+ log.verbose(`Closing temporary archiver data store`, { archiverL1BlockNumber, archiverL2BlockNumber });
77
+ await archiverStore.close();
78
+ }
57
79
 
58
- if (!dataDirectory) {
59
- log.verbose('Snapshot sync is disabled. No local data directory defined.');
60
- return false;
61
- }
80
+ const minL1BlocksToTriggerReplace = config.minL1BlocksToTriggerReplace ?? MIN_L1_BLOCKS_TO_TRIGGER_REPLACE;
81
+ if (syncMode === 'snapshot' && archiverL2BlockNumber !== undefined && archiverL2BlockNumber >= INITIAL_L2_BLOCK_NUM) {
82
+ log.verbose(
83
+ `Skipping non-forced snapshot sync as archiver is already synced to L2 block ${archiverL2BlockNumber}.`,
84
+ );
85
+ return false;
86
+ }
62
87
 
63
- let fileStore: ReadOnlyFileStore;
64
- try {
65
- fileStore = await createReadOnlyFileStore(snapshotsUrl, log);
66
- } catch (err) {
67
- log.error(`Invalid config for downloading snapshots`, err);
68
- return false;
69
- }
88
+ const currentL1BlockNumber = await getPublicClient(config).getBlockNumber();
89
+ if (archiverL1BlockNumber && currentL1BlockNumber - archiverL1BlockNumber < minL1BlocksToTriggerReplace) {
90
+ log.verbose(
91
+ `Skipping snapshot sync as archiver is less than ${
92
+ currentL1BlockNumber - archiverL1BlockNumber
93
+ } L1 blocks behind.`,
94
+ { archiverL1BlockNumber, currentL1BlockNumber, minL1BlocksToTriggerReplace },
95
+ );
96
+ return false;
97
+ }
70
98
 
71
- // Create an archiver store to check the current sync state
72
- archiverStore = await createArchiverStore(config);
73
-
74
- const minL1BlocksToTriggerReplace = config.minL1BlocksToTriggerReplace ?? MIN_L1_BLOCKS_TO_TRIGGER_REPLACE;
75
- const archiverL2BlockNumber = await archiverStore.getSynchedL2BlockNumber();
76
- if (
77
- syncMode === 'snapshot' &&
78
- archiverL2BlockNumber !== undefined &&
79
- archiverL2BlockNumber >= INITIAL_L2_BLOCK_NUM
80
- ) {
81
- log.verbose(
82
- `Skipping non-forced snapshot sync as archiver is already synced to L2 block ${archiverL2BlockNumber}.`,
83
- );
84
- return false;
85
- }
99
+ const indexMetadata: SnapshotsIndexMetadata = {
100
+ l1ChainId,
101
+ rollupVersion,
102
+ rollupAddress: l1Contracts.rollupAddress,
103
+ };
104
+ let snapshot: SnapshotMetadata | undefined;
105
+ try {
106
+ snapshot = await getLatestSnapshotMetadata(indexMetadata, fileStore);
107
+ } catch (err) {
108
+ log.error(`Failed to get latest snapshot metadata. Skipping snapshot sync.`, err, {
109
+ ...indexMetadata,
110
+ snapshotsUrl,
111
+ });
112
+ return false;
113
+ }
86
114
 
87
- const currentL1BlockNumber = await getPublicClient(config).getBlockNumber();
88
- const archiverL1BlockNumber = await archiverStore.getSynchPoint().then(s => s.blocksSynchedTo);
89
- if (archiverL1BlockNumber && currentL1BlockNumber - archiverL1BlockNumber < minL1BlocksToTriggerReplace) {
90
- log.verbose(
91
- `Skipping snapshot sync as archiver is less than ${
92
- currentL1BlockNumber - archiverL1BlockNumber
93
- } L1 blocks behind.`,
94
- { archiverL1BlockNumber, currentL1BlockNumber, minL1BlocksToTriggerReplace },
95
- );
96
- return false;
97
- }
115
+ if (!snapshot) {
116
+ log.verbose(`No snapshot found. Skipping snapshot sync.`, { ...indexMetadata, snapshotsUrl });
117
+ return false;
118
+ }
98
119
 
99
- const indexMetadata: SnapshotsIndexMetadata = {
100
- l1ChainId,
101
- rollupVersion,
102
- rollupAddress: l1Contracts.rollupAddress,
103
- };
104
- let snapshot: SnapshotMetadata | undefined;
105
- try {
106
- snapshot = await getLatestSnapshotMetadata(indexMetadata, fileStore);
107
- } catch (err) {
108
- log.error(`Failed to get latest snapshot metadata. Skipping snapshot sync.`, err, {
109
- ...indexMetadata,
110
- snapshotsUrl,
111
- });
112
- return false;
113
- }
120
+ if (snapshot.schemaVersions.archiver !== ARCHIVER_DB_VERSION) {
121
+ log.warn(
122
+ `Skipping snapshot sync as last snapshot has schema version ${snapshot.schemaVersions.archiver} but expected ${ARCHIVER_DB_VERSION}.`,
123
+ snapshot,
124
+ );
125
+ return false;
126
+ }
114
127
 
115
- if (!snapshot) {
116
- log.verbose(`No snapshot found. Skipping snapshot sync.`, { ...indexMetadata, snapshotsUrl });
117
- return false;
118
- }
128
+ if (snapshot.schemaVersions.worldState !== WORLD_STATE_DB_VERSION) {
129
+ log.warn(
130
+ `Skipping snapshot sync as last snapshot has world state schema version ${snapshot.schemaVersions.worldState} but we expected ${WORLD_STATE_DB_VERSION}.`,
131
+ snapshot,
132
+ );
133
+ return false;
134
+ }
119
135
 
120
- if (snapshot.schemaVersions.archiver !== ARCHIVER_DB_VERSION) {
121
- log.warn(
122
- `Skipping snapshot sync as last snapshot has schema version ${snapshot.schemaVersions.archiver} but expected ${ARCHIVER_DB_VERSION}.`,
123
- snapshot,
124
- );
125
- return false;
126
- }
136
+ if (archiverL1BlockNumber && snapshot.l1BlockNumber < archiverL1BlockNumber) {
137
+ log.verbose(
138
+ `Skipping snapshot sync since local archiver is at L1 block ${archiverL1BlockNumber} which is further than last snapshot at ${snapshot.l1BlockNumber}`,
139
+ { snapshot, archiverL1BlockNumber },
140
+ );
141
+ return false;
142
+ }
127
143
 
128
- if (snapshot.schemaVersions.worldState !== WORLD_STATE_DB_VERSION) {
129
- log.warn(
130
- `Skipping snapshot sync as last snapshot has world state schema version ${snapshot.schemaVersions.worldState} but we expected ${WORLD_STATE_DB_VERSION}.`,
131
- snapshot,
132
- );
133
- return false;
134
- }
144
+ if (archiverL1BlockNumber && snapshot.l1BlockNumber - Number(archiverL1BlockNumber) < minL1BlocksToTriggerReplace) {
145
+ log.verbose(
146
+ `Skipping snapshot sync as archiver is less than ${
147
+ snapshot.l1BlockNumber - Number(archiverL1BlockNumber)
148
+ } L1 blocks behind latest snapshot.`,
149
+ { snapshot, archiverL1BlockNumber },
150
+ );
151
+ return false;
152
+ }
135
153
 
136
- if (archiverL1BlockNumber && snapshot.l1BlockNumber < archiverL1BlockNumber) {
137
- log.verbose(
138
- `Skipping snapshot sync since local archiver is at L1 block ${archiverL1BlockNumber} which is further than last snapshot at ${snapshot.l1BlockNumber}`,
139
- { snapshot, archiverL1BlockNumber },
140
- );
141
- return false;
142
- }
154
+ const { l1BlockNumber, l2BlockNumber } = snapshot;
155
+ log.info(`Syncing from snapshot at L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, { snapshot, snapshotsUrl });
156
+ await snapshotSync(snapshot, log, {
157
+ dataDirectory: config.dataDirectory!,
158
+ rollupAddress: config.l1Contracts.rollupAddress,
159
+ snapshotsUrl,
160
+ });
161
+ log.info(`Snapshot synced to L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, { snapshot });
162
+ return true;
163
+ }
143
164
 
144
- if (archiverL1BlockNumber && snapshot.l1BlockNumber - Number(archiverL1BlockNumber) < minL1BlocksToTriggerReplace) {
145
- log.verbose(
146
- `Skipping snapshot sync as archiver is less than ${
147
- snapshot.l1BlockNumber - Number(archiverL1BlockNumber)
148
- } L1 blocks behind latest snapshot.`,
149
- { snapshot, archiverL1BlockNumber },
150
- );
151
- return false;
152
- }
165
+ /**
166
+ * Downloads the given snapshot replacing any local data stores.
167
+ */
168
+ export async function snapshotSync(
169
+ snapshot: Pick<SnapshotMetadata, 'dataUrls'>,
170
+ log: Logger,
171
+ config: { dataDirectory: string; rollupAddress: EthAddress; snapshotsUrl: string },
172
+ ) {
173
+ const { dataDirectory, rollupAddress } = config;
174
+ if (!dataDirectory) {
175
+ throw new Error(`No local data directory defined. Cannot sync snapshot.`);
176
+ }
177
+
178
+ const fileStore = await createReadOnlyFileStore(config.snapshotsUrl, log);
153
179
 
154
- // Green light. Download the snapshot to a temp location.
180
+ let downloadDir: string | undefined;
181
+
182
+ try {
183
+ // Download the snapshot to a temp location.
184
+ await mkdir(dataDirectory, { recursive: true });
155
185
  downloadDir = await mkdtemp(join(dataDirectory, 'download-'));
156
- const downloadPaths = makeSnapshotLocalPaths(downloadDir);
157
- log.info(
158
- `Downloading snapshot at L1 block ${snapshot.l1BlockNumber} L2 block ${snapshot.l2BlockNumber} from ${snapshotsUrl} to ${downloadDir} for snapshot sync`,
159
- { snapshot, downloadPaths },
160
- );
186
+ const downloadPaths = makeSnapshotPaths(downloadDir);
187
+ log.info(`Downloading snapshot to ${downloadDir}`, { snapshot, downloadPaths });
161
188
  await downloadSnapshot(snapshot, downloadPaths, fileStore);
162
- log.info(`Snapshot downloaded at ${downloadDir}`, { snapshotsUrl, snapshot, downloadPaths });
189
+ log.info(`Snapshot downloaded at ${downloadDir}`, { snapshot, downloadPaths });
163
190
 
164
- // If download was successful, close the archiver store, clear lock and version, and move download there
165
- await archiverStore.close();
166
- archiverStore = undefined;
191
+ // If download was successful, clear lock and version, and move download there
167
192
  const archiverPath = join(dataDirectory, ARCHIVER_STORE_NAME);
168
- await prepareTarget(archiverPath, ARCHIVER_DB_VERSION, l1Contracts.rollupAddress);
193
+ await prepareTarget(archiverPath, ARCHIVER_DB_VERSION, rollupAddress);
169
194
  await rename(downloadPaths.archiver, join(archiverPath, 'data.mdb'));
170
- log.info(`Archiver database set up from snapshot`, { path: archiverPath });
195
+ log.info(`Archiver database set up from snapshot`, {
196
+ path: archiverPath,
197
+ dbVersion: ARCHIVER_DB_VERSION,
198
+ rollupAddress,
199
+ });
171
200
 
172
201
  // Same for the world state dbs, only that we do not close them, since we assume they are not yet in use
173
202
  const worldStateBasePath = join(dataDirectory, WORLD_STATE_DIR);
174
- await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION, l1Contracts.rollupAddress);
203
+ await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION, rollupAddress);
175
204
  for (const [name, dir] of NATIVE_WORLD_STATE_DBS) {
176
205
  const path = join(worldStateBasePath, dir);
177
206
  await mkdir(path, { recursive: true });
178
207
  await rename(downloadPaths[name], join(path, 'data.mdb'));
179
- log.info(`World state database ${name} set up from snapshot`, { path });
208
+ log.info(`World state database ${name} set up from snapshot`, {
209
+ path,
210
+ dbVersion: WORLD_STATE_DB_VERSION,
211
+ rollupAddress,
212
+ });
180
213
  }
181
-
182
- log.info(`Snapshot synced to L1 block ${snapshot.l1BlockNumber} L2 block ${snapshot.l2BlockNumber}`, { snapshot });
183
214
  } finally {
184
- if (archiverStore) {
185
- log.verbose(`Closing temporary archiver data store`);
186
- await archiverStore.close();
187
- }
188
215
  if (downloadDir) {
189
216
  await tryRmDir(downloadDir, log);
190
217
  }
191
218
  }
192
-
193
- return true;
194
219
  }
195
220
 
196
221
  /** Deletes target dir and writes the new version file. */
@@ -5,16 +5,18 @@ import type { DataStoreConfig } from '@aztec/kv-store/config';
5
5
  import type { ChainConfig } from '@aztec/stdlib/config';
6
6
  import { createFileStore } from '@aztec/stdlib/file-store';
7
7
  import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
8
- import type { SnapshotDataUrls, UploadSnapshotMetadata } from '@aztec/stdlib/snapshots';
9
- import { uploadSnapshot as uploadSnapshotToStore } from '@aztec/stdlib/snapshots';
8
+ import { uploadSnapshotToIndex } from '@aztec/stdlib/snapshots';
10
9
  import { WORLD_STATE_DB_VERSION } from '@aztec/world-state';
11
10
 
12
- import { existsSync } from 'fs';
13
11
  import { mkdtemp } from 'fs/promises';
14
12
  import { tmpdir } from 'os';
15
13
  import { join } from 'path';
16
14
 
17
- type UploadSnapshotConfig = Pick<ChainConfig, 'l1ChainId' | 'rollupVersion'> & Pick<DataStoreConfig, 'dataDirectory'>;
15
+ import { buildSnapshotMetadata } from './build-snapshot-metadata.js';
16
+ import { createBackups } from './create-backups.js';
17
+
18
+ export type UploadSnapshotConfig = Pick<ChainConfig, 'l1ChainId' | 'rollupVersion'> &
19
+ Pick<DataStoreConfig, 'dataDirectory'>;
18
20
 
19
21
  /**
20
22
  * Pauses the archiver and world state sync, creates backups of the archiver and world state lmdb environments,
@@ -39,64 +41,10 @@ export async function uploadSnapshot(
39
41
  const versions = { archiver: ARCHIVER_DB_VERSION, worldState: WORLD_STATE_DB_VERSION };
40
42
  const metadata = await buildSnapshotMetadata(archiver, config);
41
43
  log.info(`Uploading snapshot to ${location}`, { snapshot: metadata });
42
- const snapshot = await uploadSnapshotToStore(paths, versions, metadata, store);
44
+ const snapshot = await uploadSnapshotToIndex(paths, versions, metadata, store);
43
45
  log.info(`Snapshot uploaded successfully`, { snapshot });
44
46
  } finally {
45
47
  log.info(`Cleaning up backup dir ${backupDir}`);
46
48
  await tryRmDir(backupDir, log);
47
49
  }
48
50
  }
49
-
50
- async function buildSnapshotMetadata(
51
- archiver: Archiver,
52
- config: UploadSnapshotConfig,
53
- ): Promise<UploadSnapshotMetadata> {
54
- const [rollupAddress, l1BlockNumber, { latest }] = await Promise.all([
55
- archiver.getRollupAddress(),
56
- archiver.getL1BlockNumber(),
57
- archiver.getL2Tips(),
58
- ] as const);
59
-
60
- const { number: l2BlockNumber, hash: l2BlockHash } = latest;
61
- if (!l2BlockHash) {
62
- throw new Error(`Failed to get L2 block hash from archiver.`);
63
- }
64
-
65
- return {
66
- l1ChainId: config.l1ChainId,
67
- rollupVersion: config.rollupVersion,
68
- rollupAddress,
69
- l2BlockNumber,
70
- l2BlockHash,
71
- l1BlockNumber: Number(l1BlockNumber),
72
- };
73
- }
74
-
75
- async function createBackups(backupDir: string, archiver: Archiver, worldState: WorldStateSynchronizer, log: Logger) {
76
- try {
77
- log.info(`Pausing archiver and world state sync to start snapshot upload`);
78
- await archiver.stop();
79
- await worldState.stopSync();
80
-
81
- log.info(`Creating backups of lmdb environments to ${backupDir}`);
82
- const [archiverPath, worldStatePaths] = await Promise.all([
83
- archiver.backupTo(join(backupDir, 'archiver')),
84
- worldState.backupTo(join(backupDir, 'world-state')),
85
- ]);
86
- const paths: SnapshotDataUrls = { ...worldStatePaths, archiver: archiverPath };
87
-
88
- const missing = Object.entries(paths).filter(([_key, path]) => !path || !existsSync(path));
89
- if (missing.length > 0) {
90
- throw new Error(`Missing backup files: ${missing.map(([key, path]) => `${path} (${key})`).join(', ')}`);
91
- }
92
-
93
- log.info(`Data stores backed up to ${backupDir}`, { paths });
94
- return paths;
95
- } catch (err) {
96
- throw new Error(`Error creating backups for snapshot upload: ${err}`);
97
- } finally {
98
- log.info(`Resuming archiver and world state sync`);
99
- worldState.resumeSync();
100
- archiver.resume();
101
- }
102
- }