@aztec/node-lib 0.86.0 → 0.87.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;AAE9D,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,iBAqDnF"}
@@ -2,127 +2,168 @@ import { ARCHIVER_DB_VERSION, ARCHIVER_STORE_NAME, createArchiverStore } from '@
2
2
  import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
3
3
  import { getPublicClient } from '@aztec/ethereum';
4
4
  import { tryRmDir } from '@aztec/foundation/fs';
5
+ import { P2P_STORE_NAME } from '@aztec/p2p';
5
6
  import { DatabaseVersionManager } from '@aztec/stdlib/database-version';
6
7
  import { createReadOnlyFileStore } from '@aztec/stdlib/file-store';
7
- import { downloadSnapshot, getLatestSnapshotMetadata, makeSnapshotLocalPaths } from '@aztec/stdlib/snapshots';
8
+ import { downloadSnapshot, getLatestSnapshotMetadata, makeSnapshotPaths } from '@aztec/stdlib/snapshots';
8
9
  import { NATIVE_WORLD_STATE_DBS, WORLD_STATE_DB_VERSION, WORLD_STATE_DIR } from '@aztec/world-state';
9
10
  import { mkdir, mkdtemp, rename } from 'fs/promises';
10
11
  import { join } from 'path';
11
12
  // Half day worth of L1 blocks
12
13
  const MIN_L1_BLOCKS_TO_TRIGGER_REPLACE = 86400 / 2 / 12;
13
- export async function trySnapshotSync(config, log) {
14
- let archiverStore;
14
+ /**
15
+ * Connects to a remote snapshot index and downloads the latest snapshot if the local archiver is behind.
16
+ * Behaviour depends on syncing mode.
17
+ */ export async function trySnapshotSync(config, log) {
18
+ const { syncMode, snapshotsUrl, dataDirectory, l1ChainId, rollupVersion, l1Contracts } = config;
19
+ if (syncMode === 'full') {
20
+ log.debug('Snapshot sync is disabled. Running full sync.', {
21
+ syncMode: syncMode
22
+ });
23
+ return false;
24
+ }
25
+ if (!snapshotsUrl) {
26
+ log.verbose('Snapshot sync is disabled. No snapshots URL provided.');
27
+ return false;
28
+ }
29
+ if (!dataDirectory) {
30
+ log.verbose('Snapshot sync is disabled. No local data directory defined.');
31
+ return false;
32
+ }
33
+ let fileStore;
34
+ try {
35
+ fileStore = await createReadOnlyFileStore(snapshotsUrl, log);
36
+ } catch (err) {
37
+ log.error(`Invalid config for downloading snapshots`, err);
38
+ return false;
39
+ }
40
+ // Create an archiver store to check the current state
41
+ log.verbose(`Creating temporary archiver data store`);
42
+ const archiverStore = await createArchiverStore(config);
43
+ let archiverL1BlockNumber;
44
+ let archiverL2BlockNumber;
45
+ try {
46
+ [archiverL1BlockNumber, archiverL2BlockNumber] = await Promise.all([
47
+ archiverStore.getSynchPoint().then((s)=>s.blocksSynchedTo),
48
+ archiverStore.getSynchedL2BlockNumber()
49
+ ]);
50
+ } finally{
51
+ log.verbose(`Closing temporary archiver data store`, {
52
+ archiverL1BlockNumber,
53
+ archiverL2BlockNumber
54
+ });
55
+ await archiverStore.close();
56
+ }
57
+ const minL1BlocksToTriggerReplace = config.minL1BlocksToTriggerReplace ?? MIN_L1_BLOCKS_TO_TRIGGER_REPLACE;
58
+ if (syncMode === 'snapshot' && archiverL2BlockNumber !== undefined && archiverL2BlockNumber >= INITIAL_L2_BLOCK_NUM) {
59
+ log.verbose(`Skipping non-forced snapshot sync as archiver is already synced to L2 block ${archiverL2BlockNumber}.`);
60
+ return false;
61
+ }
62
+ const currentL1BlockNumber = await getPublicClient(config).getBlockNumber();
63
+ if (archiverL1BlockNumber && currentL1BlockNumber - archiverL1BlockNumber < minL1BlocksToTriggerReplace) {
64
+ log.verbose(`Skipping snapshot sync as archiver is less than ${currentL1BlockNumber - archiverL1BlockNumber} L1 blocks behind.`, {
65
+ archiverL1BlockNumber,
66
+ currentL1BlockNumber,
67
+ minL1BlocksToTriggerReplace
68
+ });
69
+ return false;
70
+ }
71
+ const indexMetadata = {
72
+ l1ChainId,
73
+ rollupVersion,
74
+ rollupAddress: l1Contracts.rollupAddress
75
+ };
76
+ let snapshot;
77
+ try {
78
+ snapshot = await getLatestSnapshotMetadata(indexMetadata, fileStore);
79
+ } catch (err) {
80
+ log.error(`Failed to get latest snapshot metadata. Skipping snapshot sync.`, err, {
81
+ ...indexMetadata,
82
+ snapshotsUrl
83
+ });
84
+ return false;
85
+ }
86
+ if (!snapshot) {
87
+ log.verbose(`No snapshot found. Skipping snapshot sync.`, {
88
+ ...indexMetadata,
89
+ snapshotsUrl
90
+ });
91
+ return false;
92
+ }
93
+ if (snapshot.schemaVersions.archiver !== ARCHIVER_DB_VERSION) {
94
+ log.warn(`Skipping snapshot sync as last snapshot has schema version ${snapshot.schemaVersions.archiver} but expected ${ARCHIVER_DB_VERSION}.`, snapshot);
95
+ return false;
96
+ }
97
+ if (snapshot.schemaVersions.worldState !== WORLD_STATE_DB_VERSION) {
98
+ log.warn(`Skipping snapshot sync as last snapshot has world state schema version ${snapshot.schemaVersions.worldState} but we expected ${WORLD_STATE_DB_VERSION}.`, snapshot);
99
+ return false;
100
+ }
101
+ if (archiverL1BlockNumber && snapshot.l1BlockNumber < archiverL1BlockNumber) {
102
+ log.verbose(`Skipping snapshot sync since local archiver is at L1 block ${archiverL1BlockNumber} which is further than last snapshot at ${snapshot.l1BlockNumber}`, {
103
+ snapshot,
104
+ archiverL1BlockNumber
105
+ });
106
+ return false;
107
+ }
108
+ if (archiverL1BlockNumber && snapshot.l1BlockNumber - Number(archiverL1BlockNumber) < minL1BlocksToTriggerReplace) {
109
+ log.verbose(`Skipping snapshot sync as archiver is less than ${snapshot.l1BlockNumber - Number(archiverL1BlockNumber)} L1 blocks behind latest snapshot.`, {
110
+ snapshot,
111
+ archiverL1BlockNumber
112
+ });
113
+ return false;
114
+ }
115
+ const { l1BlockNumber, l2BlockNumber } = snapshot;
116
+ log.info(`Syncing from snapshot at L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, {
117
+ snapshot,
118
+ snapshotsUrl
119
+ });
120
+ await snapshotSync(snapshot, log, {
121
+ dataDirectory: config.dataDirectory,
122
+ rollupAddress: config.l1Contracts.rollupAddress,
123
+ snapshotsUrl
124
+ });
125
+ log.info(`Snapshot synced to L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, {
126
+ snapshot
127
+ });
128
+ return true;
129
+ }
130
+ /**
131
+ * Downloads the given snapshot replacing any local data stores.
132
+ */ export async function snapshotSync(snapshot, log, config) {
133
+ const { dataDirectory, rollupAddress } = config;
134
+ if (!dataDirectory) {
135
+ throw new Error(`No local data directory defined. Cannot sync snapshot.`);
136
+ }
137
+ const fileStore = await createReadOnlyFileStore(config.snapshotsUrl, log);
15
138
  let downloadDir;
16
139
  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.
140
+ // Download the snapshot to a temp location.
141
+ await mkdir(dataDirectory, {
142
+ recursive: true
143
+ });
102
144
  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`, {
145
+ const downloadPaths = makeSnapshotPaths(downloadDir);
146
+ log.info(`Downloading snapshot to ${downloadDir}`, {
105
147
  snapshot,
106
148
  downloadPaths
107
149
  });
108
150
  await downloadSnapshot(snapshot, downloadPaths, fileStore);
109
151
  log.info(`Snapshot downloaded at ${downloadDir}`, {
110
- snapshotsUrl,
111
152
  snapshot,
112
153
  downloadPaths
113
154
  });
114
- // If download was successful, close the archiver store, clear lock and version, and move download there
115
- await archiverStore.close();
116
- archiverStore = undefined;
155
+ // If download was successful, clear lock and version, and move download there
117
156
  const archiverPath = join(dataDirectory, ARCHIVER_STORE_NAME);
118
- await prepareTarget(archiverPath, ARCHIVER_DB_VERSION, l1Contracts.rollupAddress);
157
+ await prepareTarget(archiverPath, ARCHIVER_DB_VERSION, rollupAddress);
119
158
  await rename(downloadPaths.archiver, join(archiverPath, 'data.mdb'));
120
159
  log.info(`Archiver database set up from snapshot`, {
121
- path: archiverPath
160
+ path: archiverPath,
161
+ dbVersion: ARCHIVER_DB_VERSION,
162
+ rollupAddress
122
163
  });
123
164
  // Same for the world state dbs, only that we do not close them, since we assume they are not yet in use
124
165
  const worldStateBasePath = join(dataDirectory, WORLD_STATE_DIR);
125
- await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION, l1Contracts.rollupAddress);
166
+ await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION, rollupAddress);
126
167
  for (const [name, dir] of NATIVE_WORLD_STATE_DBS){
127
168
  const path = join(worldStateBasePath, dir);
128
169
  await mkdir(path, {
@@ -130,22 +171,22 @@ export async function trySnapshotSync(config, log) {
130
171
  });
131
172
  await rename(downloadPaths[name], join(path, 'data.mdb'));
132
173
  log.info(`World state database ${name} set up from snapshot`, {
133
- path
174
+ path,
175
+ dbVersion: WORLD_STATE_DB_VERSION,
176
+ rollupAddress
134
177
  });
135
178
  }
136
- log.info(`Snapshot synced to L1 block ${snapshot.l1BlockNumber} L2 block ${snapshot.l2BlockNumber}`, {
137
- snapshot
179
+ // And clear the p2p db altogether
180
+ const p2pPath = join(dataDirectory, P2P_STORE_NAME);
181
+ await tryRmDir(p2pPath, log);
182
+ log.info(`P2P database cleared`, {
183
+ path: p2pPath
138
184
  });
139
185
  } finally{
140
- if (archiverStore) {
141
- log.verbose(`Closing temporary archiver data store`);
142
- await archiverStore.close();
143
- }
144
186
  if (downloadDir) {
145
187
  await tryRmDir(downloadDir, log);
146
188
  }
147
189
  }
148
- return true;
149
190
  }
150
191
  /** Deletes target dir and writes the new version file. */ async function prepareTarget(target, schemaVersion, rollupAddress) {
151
192
  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
- }
@@ -8,6 +8,10 @@ export type SharedNodeConfig = {
8
8
  syncMode: 'full' | 'snapshot' | 'force-snapshot';
9
9
  /** Base URL for snapshots index. Index file will be searched at `SNAPSHOTS_BASE_URL/aztec-L1_CHAIN_ID-VERSION-ROLLUP_ADDRESS/index.json` */
10
10
  snapshotsUrl?: string;
11
+ /** Auto update mode: disabled - to completely ignore remote signals to update the node. enabled - to respect the signals (potentially shutting this node down). log - check for updates but log a warning instead of applying them*/
12
+ autoUpdate?: 'disabled' | 'notify' | 'config' | 'config-and-version';
13
+ /** The base URL against which to check for updates */
14
+ autoUpdateUrl?: string;
11
15
  };
12
16
  export declare const sharedNodeConfigMappings: ConfigMappingsType<SharedNodeConfig>;
13
17
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAuB,MAAM,0BAA0B,CAAC;AAExF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yFAAyF;IACzF,YAAY,EAAE,OAAO,CAAC;IACtB,yFAAyF;IACzF,YAAY,EAAE,OAAO,CAAC;IACtB,gKAAgK;IAChK,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,gBAAgB,CAAC;IACjD,4IAA4I;IAC5I,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,kBAAkB,CAAC,gBAAgB,CAqBzE,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/config/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,kBAAkB,EAAuB,MAAM,0BAA0B,CAAC;AAExF,MAAM,MAAM,gBAAgB,GAAG;IAC7B,yFAAyF;IACzF,YAAY,EAAE,OAAO,CAAC;IACtB,yFAAyF;IACzF,YAAY,EAAE,OAAO,CAAC;IACtB,gKAAgK;IAChK,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,gBAAgB,CAAC;IACjD,4IAA4I;IAC5I,YAAY,CAAC,EAAE,MAAM,CAAC;IAEtB,qOAAqO;IACrO,UAAU,CAAC,EAAE,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,oBAAoB,CAAC;IACrE,sDAAsD;IACtD,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB,CAAC;AAEF,eAAO,MAAM,wBAAwB,EAAE,kBAAkB,CAAC,gBAAgB,CA8BzE,CAAC"}
@@ -18,5 +18,14 @@ export const sharedNodeConfigMappings = {
18
18
  snapshotsUrl: {
19
19
  env: 'SYNC_SNAPSHOTS_URL',
20
20
  description: 'Base URL for snapshots index.'
21
+ },
22
+ autoUpdate: {
23
+ env: 'AUTO_UPDATE',
24
+ description: 'The auto update mode for this node',
25
+ defaultValue: 'disabled'
26
+ },
27
+ autoUpdateUrl: {
28
+ env: 'AUTO_UPDATE_URL',
29
+ description: 'Base URL to check for updates'
21
30
  }
22
31
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aztec/node-lib",
3
- "version": "0.86.0",
3
+ "version": "0.87.0",
4
4
  "type": "module",
5
5
  "exports": {
6
6
  "./actions": "./dest/actions/index.js",
@@ -50,34 +50,34 @@
50
50
  ]
51
51
  },
52
52
  "dependencies": {
53
- "@aztec/archiver": "0.86.0",
54
- "@aztec/bb-prover": "0.86.0",
55
- "@aztec/blob-sink": "0.86.0",
56
- "@aztec/constants": "0.86.0",
57
- "@aztec/epoch-cache": "0.86.0",
58
- "@aztec/ethereum": "0.86.0",
59
- "@aztec/foundation": "0.86.0",
60
- "@aztec/kv-store": "0.86.0",
61
- "@aztec/merkle-tree": "0.86.0",
62
- "@aztec/p2p": "0.86.0",
63
- "@aztec/protocol-contracts": "0.86.0",
64
- "@aztec/prover-client": "0.86.0",
65
- "@aztec/sequencer-client": "0.86.0",
66
- "@aztec/simulator": "0.86.0",
67
- "@aztec/stdlib": "0.86.0",
68
- "@aztec/telemetry-client": "0.86.0",
69
- "@aztec/validator-client": "0.86.0",
70
- "@aztec/world-state": "0.86.0",
53
+ "@aztec/archiver": "0.87.0",
54
+ "@aztec/bb-prover": "0.87.0",
55
+ "@aztec/blob-sink": "0.87.0",
56
+ "@aztec/constants": "0.87.0",
57
+ "@aztec/epoch-cache": "0.87.0",
58
+ "@aztec/ethereum": "0.87.0",
59
+ "@aztec/foundation": "0.87.0",
60
+ "@aztec/kv-store": "0.87.0",
61
+ "@aztec/merkle-tree": "0.87.0",
62
+ "@aztec/p2p": "0.87.0",
63
+ "@aztec/protocol-contracts": "0.87.0",
64
+ "@aztec/prover-client": "0.87.0",
65
+ "@aztec/sequencer-client": "0.87.0",
66
+ "@aztec/simulator": "0.87.0",
67
+ "@aztec/stdlib": "0.87.0",
68
+ "@aztec/telemetry-client": "0.87.0",
69
+ "@aztec/validator-client": "0.87.0",
70
+ "@aztec/world-state": "0.87.0",
71
71
  "tslib": "^2.4.0"
72
72
  },
73
73
  "devDependencies": {
74
74
  "@jest/globals": "^29.5.0",
75
75
  "@types/jest": "^29.5.0",
76
- "@types/node": "^18.7.23",
76
+ "@types/node": "^22.15.17",
77
77
  "jest": "^29.5.0",
78
78
  "jest-mock-extended": "^3.0.3",
79
79
  "ts-node": "^10.9.1",
80
- "typescript": "^5.0.4"
80
+ "typescript": "^5.3.3"
81
81
  },
82
82
  "files": [
83
83
  "dest",
@@ -86,6 +86,6 @@
86
86
  ],
87
87
  "types": "./dest/index.d.ts",
88
88
  "engines": {
89
- "node": ">=18"
89
+ "node": ">=20.10"
90
90
  }
91
91
  }
@@ -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,16 +1,11 @@
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';
11
5
  import { tryRmDir } from '@aztec/foundation/fs';
12
6
  import type { Logger } from '@aztec/foundation/log';
13
7
  import type { DataStoreConfig } from '@aztec/kv-store/config';
8
+ import { P2P_STORE_NAME } from '@aztec/p2p';
14
9
  import type { ChainConfig } from '@aztec/stdlib/config';
15
10
  import { DatabaseVersionManager } from '@aztec/stdlib/database-version';
16
11
  import { type ReadOnlyFileStore, createReadOnlyFileStore } from '@aztec/stdlib/file-store';
@@ -19,7 +14,7 @@ import {
19
14
  type SnapshotsIndexMetadata,
20
15
  downloadSnapshot,
21
16
  getLatestSnapshotMetadata,
22
- makeSnapshotLocalPaths,
17
+ makeSnapshotPaths,
23
18
  } from '@aztec/stdlib/snapshots';
24
19
  import { NATIVE_WORLD_STATE_DBS, WORLD_STATE_DB_VERSION, WORLD_STATE_DIR } from '@aztec/world-state';
25
20
 
@@ -39,158 +34,194 @@ type SnapshotSyncConfig = Pick<SharedNodeConfig, 'syncMode' | 'snapshotsUrl'> &
39
34
  minL1BlocksToTriggerReplace?: number;
40
35
  };
41
36
 
37
+ /**
38
+ * Connects to a remote snapshot index and downloads the latest snapshot if the local archiver is behind.
39
+ * Behaviour depends on syncing mode.
40
+ */
42
41
  export async function trySnapshotSync(config: SnapshotSyncConfig, log: Logger) {
43
- let archiverStore: ArchiverDataStore | undefined;
44
- let downloadDir: string | undefined;
42
+ const { syncMode, snapshotsUrl, dataDirectory, l1ChainId, rollupVersion, l1Contracts } = config;
43
+ if (syncMode === 'full') {
44
+ log.debug('Snapshot sync is disabled. Running full sync.', { syncMode: syncMode });
45
+ return false;
46
+ }
47
+
48
+ if (!snapshotsUrl) {
49
+ log.verbose('Snapshot sync is disabled. No snapshots URL provided.');
50
+ return false;
51
+ }
52
+
53
+ if (!dataDirectory) {
54
+ log.verbose('Snapshot sync is disabled. No local data directory defined.');
55
+ return false;
56
+ }
45
57
 
58
+ let fileStore: ReadOnlyFileStore;
46
59
  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
- }
60
+ fileStore = await createReadOnlyFileStore(snapshotsUrl, log);
61
+ } catch (err) {
62
+ log.error(`Invalid config for downloading snapshots`, err);
63
+ return false;
64
+ }
52
65
 
53
- if (!snapshotsUrl) {
54
- log.verbose('Snapshot sync is disabled. No snapshots URL provided.');
55
- return false;
56
- }
66
+ // Create an archiver store to check the current state
67
+ log.verbose(`Creating temporary archiver data store`);
68
+ const archiverStore = await createArchiverStore(config);
69
+ let archiverL1BlockNumber: bigint | undefined;
70
+ let archiverL2BlockNumber: number | undefined;
71
+ try {
72
+ [archiverL1BlockNumber, archiverL2BlockNumber] = await Promise.all([
73
+ archiverStore.getSynchPoint().then(s => s.blocksSynchedTo),
74
+ archiverStore.getSynchedL2BlockNumber(),
75
+ ] as const);
76
+ } finally {
77
+ log.verbose(`Closing temporary archiver data store`, { archiverL1BlockNumber, archiverL2BlockNumber });
78
+ await archiverStore.close();
79
+ }
57
80
 
58
- if (!dataDirectory) {
59
- log.verbose('Snapshot sync is disabled. No local data directory defined.');
60
- return false;
61
- }
81
+ const minL1BlocksToTriggerReplace = config.minL1BlocksToTriggerReplace ?? MIN_L1_BLOCKS_TO_TRIGGER_REPLACE;
82
+ if (syncMode === 'snapshot' && archiverL2BlockNumber !== undefined && archiverL2BlockNumber >= INITIAL_L2_BLOCK_NUM) {
83
+ log.verbose(
84
+ `Skipping non-forced snapshot sync as archiver is already synced to L2 block ${archiverL2BlockNumber}.`,
85
+ );
86
+ return false;
87
+ }
62
88
 
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
- }
89
+ const currentL1BlockNumber = await getPublicClient(config).getBlockNumber();
90
+ if (archiverL1BlockNumber && currentL1BlockNumber - archiverL1BlockNumber < minL1BlocksToTriggerReplace) {
91
+ log.verbose(
92
+ `Skipping snapshot sync as archiver is less than ${
93
+ currentL1BlockNumber - archiverL1BlockNumber
94
+ } L1 blocks behind.`,
95
+ { archiverL1BlockNumber, currentL1BlockNumber, minL1BlocksToTriggerReplace },
96
+ );
97
+ return false;
98
+ }
70
99
 
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
- }
100
+ const indexMetadata: SnapshotsIndexMetadata = {
101
+ l1ChainId,
102
+ rollupVersion,
103
+ rollupAddress: l1Contracts.rollupAddress,
104
+ };
105
+ let snapshot: SnapshotMetadata | undefined;
106
+ try {
107
+ snapshot = await getLatestSnapshotMetadata(indexMetadata, fileStore);
108
+ } catch (err) {
109
+ log.error(`Failed to get latest snapshot metadata. Skipping snapshot sync.`, err, {
110
+ ...indexMetadata,
111
+ snapshotsUrl,
112
+ });
113
+ return false;
114
+ }
86
115
 
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
- }
116
+ if (!snapshot) {
117
+ log.verbose(`No snapshot found. Skipping snapshot sync.`, { ...indexMetadata, snapshotsUrl });
118
+ return false;
119
+ }
98
120
 
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
- }
121
+ if (snapshot.schemaVersions.archiver !== ARCHIVER_DB_VERSION) {
122
+ log.warn(
123
+ `Skipping snapshot sync as last snapshot has schema version ${snapshot.schemaVersions.archiver} but expected ${ARCHIVER_DB_VERSION}.`,
124
+ snapshot,
125
+ );
126
+ return false;
127
+ }
114
128
 
115
- if (!snapshot) {
116
- log.verbose(`No snapshot found. Skipping snapshot sync.`, { ...indexMetadata, snapshotsUrl });
117
- return false;
118
- }
129
+ if (snapshot.schemaVersions.worldState !== WORLD_STATE_DB_VERSION) {
130
+ log.warn(
131
+ `Skipping snapshot sync as last snapshot has world state schema version ${snapshot.schemaVersions.worldState} but we expected ${WORLD_STATE_DB_VERSION}.`,
132
+ snapshot,
133
+ );
134
+ return false;
135
+ }
119
136
 
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
- }
137
+ if (archiverL1BlockNumber && snapshot.l1BlockNumber < archiverL1BlockNumber) {
138
+ log.verbose(
139
+ `Skipping snapshot sync since local archiver is at L1 block ${archiverL1BlockNumber} which is further than last snapshot at ${snapshot.l1BlockNumber}`,
140
+ { snapshot, archiverL1BlockNumber },
141
+ );
142
+ return false;
143
+ }
127
144
 
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
- }
145
+ if (archiverL1BlockNumber && snapshot.l1BlockNumber - Number(archiverL1BlockNumber) < minL1BlocksToTriggerReplace) {
146
+ log.verbose(
147
+ `Skipping snapshot sync as archiver is less than ${
148
+ snapshot.l1BlockNumber - Number(archiverL1BlockNumber)
149
+ } L1 blocks behind latest snapshot.`,
150
+ { snapshot, archiverL1BlockNumber },
151
+ );
152
+ return false;
153
+ }
135
154
 
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
- }
155
+ const { l1BlockNumber, l2BlockNumber } = snapshot;
156
+ log.info(`Syncing from snapshot at L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, { snapshot, snapshotsUrl });
157
+ await snapshotSync(snapshot, log, {
158
+ dataDirectory: config.dataDirectory!,
159
+ rollupAddress: config.l1Contracts.rollupAddress,
160
+ snapshotsUrl,
161
+ });
162
+ log.info(`Snapshot synced to L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, { snapshot });
163
+ return true;
164
+ }
143
165
 
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
- }
166
+ /**
167
+ * Downloads the given snapshot replacing any local data stores.
168
+ */
169
+ export async function snapshotSync(
170
+ snapshot: Pick<SnapshotMetadata, 'dataUrls'>,
171
+ log: Logger,
172
+ config: { dataDirectory: string; rollupAddress: EthAddress; snapshotsUrl: string },
173
+ ) {
174
+ const { dataDirectory, rollupAddress } = config;
175
+ if (!dataDirectory) {
176
+ throw new Error(`No local data directory defined. Cannot sync snapshot.`);
177
+ }
178
+
179
+ const fileStore = await createReadOnlyFileStore(config.snapshotsUrl, log);
153
180
 
154
- // Green light. Download the snapshot to a temp location.
181
+ let downloadDir: string | undefined;
182
+
183
+ try {
184
+ // Download the snapshot to a temp location.
185
+ await mkdir(dataDirectory, { recursive: true });
155
186
  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
- );
187
+ const downloadPaths = makeSnapshotPaths(downloadDir);
188
+ log.info(`Downloading snapshot to ${downloadDir}`, { snapshot, downloadPaths });
161
189
  await downloadSnapshot(snapshot, downloadPaths, fileStore);
162
- log.info(`Snapshot downloaded at ${downloadDir}`, { snapshotsUrl, snapshot, downloadPaths });
190
+ log.info(`Snapshot downloaded at ${downloadDir}`, { snapshot, downloadPaths });
163
191
 
164
- // If download was successful, close the archiver store, clear lock and version, and move download there
165
- await archiverStore.close();
166
- archiverStore = undefined;
192
+ // If download was successful, clear lock and version, and move download there
167
193
  const archiverPath = join(dataDirectory, ARCHIVER_STORE_NAME);
168
- await prepareTarget(archiverPath, ARCHIVER_DB_VERSION, l1Contracts.rollupAddress);
194
+ await prepareTarget(archiverPath, ARCHIVER_DB_VERSION, rollupAddress);
169
195
  await rename(downloadPaths.archiver, join(archiverPath, 'data.mdb'));
170
- log.info(`Archiver database set up from snapshot`, { path: archiverPath });
196
+ log.info(`Archiver database set up from snapshot`, {
197
+ path: archiverPath,
198
+ dbVersion: ARCHIVER_DB_VERSION,
199
+ rollupAddress,
200
+ });
171
201
 
172
202
  // Same for the world state dbs, only that we do not close them, since we assume they are not yet in use
173
203
  const worldStateBasePath = join(dataDirectory, WORLD_STATE_DIR);
174
- await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION, l1Contracts.rollupAddress);
204
+ await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION, rollupAddress);
175
205
  for (const [name, dir] of NATIVE_WORLD_STATE_DBS) {
176
206
  const path = join(worldStateBasePath, dir);
177
207
  await mkdir(path, { recursive: true });
178
208
  await rename(downloadPaths[name], join(path, 'data.mdb'));
179
- log.info(`World state database ${name} set up from snapshot`, { path });
209
+ log.info(`World state database ${name} set up from snapshot`, {
210
+ path,
211
+ dbVersion: WORLD_STATE_DB_VERSION,
212
+ rollupAddress,
213
+ });
180
214
  }
181
215
 
182
- log.info(`Snapshot synced to L1 block ${snapshot.l1BlockNumber} L2 block ${snapshot.l2BlockNumber}`, { snapshot });
216
+ // And clear the p2p db altogether
217
+ const p2pPath = join(dataDirectory, P2P_STORE_NAME);
218
+ await tryRmDir(p2pPath, log);
219
+ log.info(`P2P database cleared`, { path: p2pPath });
183
220
  } finally {
184
- if (archiverStore) {
185
- log.verbose(`Closing temporary archiver data store`);
186
- await archiverStore.close();
187
- }
188
221
  if (downloadDir) {
189
222
  await tryRmDir(downloadDir, log);
190
223
  }
191
224
  }
192
-
193
- return true;
194
225
  }
195
226
 
196
227
  /** 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
- }
@@ -9,6 +9,11 @@ export type SharedNodeConfig = {
9
9
  syncMode: 'full' | 'snapshot' | 'force-snapshot';
10
10
  /** Base URL for snapshots index. Index file will be searched at `SNAPSHOTS_BASE_URL/aztec-L1_CHAIN_ID-VERSION-ROLLUP_ADDRESS/index.json` */
11
11
  snapshotsUrl?: string;
12
+
13
+ /** Auto update mode: disabled - to completely ignore remote signals to update the node. enabled - to respect the signals (potentially shutting this node down). log - check for updates but log a warning instead of applying them*/
14
+ autoUpdate?: 'disabled' | 'notify' | 'config' | 'config-and-version';
15
+ /** The base URL against which to check for updates */
16
+ autoUpdateUrl?: string;
12
17
  };
13
18
 
14
19
  export const sharedNodeConfigMappings: ConfigMappingsType<SharedNodeConfig> = {
@@ -32,4 +37,13 @@ export const sharedNodeConfigMappings: ConfigMappingsType<SharedNodeConfig> = {
32
37
  env: 'SYNC_SNAPSHOTS_URL',
33
38
  description: 'Base URL for snapshots index.',
34
39
  },
40
+ autoUpdate: {
41
+ env: 'AUTO_UPDATE',
42
+ description: 'The auto update mode for this node',
43
+ defaultValue: 'disabled',
44
+ },
45
+ autoUpdateUrl: {
46
+ env: 'AUTO_UPDATE_URL',
47
+ description: 'Base URL to check for updates',
48
+ },
35
49
  };