@aztec/node-lib 0.86.0 → 0.87.0-nightly.20250521
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.
- package/dest/actions/build-snapshot-metadata.d.ts +5 -0
- package/dest/actions/build-snapshot-metadata.d.ts.map +1 -0
- package/dest/actions/build-snapshot-metadata.js +19 -0
- package/dest/actions/create-backups.d.ts +6 -0
- package/dest/actions/create-backups.d.ts.map +1 -0
- package/dest/actions/create-backups.js +32 -0
- package/dest/actions/index.d.ts +2 -0
- package/dest/actions/index.d.ts.map +1 -1
- package/dest/actions/index.js +2 -0
- package/dest/actions/snapshot-sync.d.ts +14 -0
- package/dest/actions/snapshot-sync.d.ts.map +1 -1
- package/dest/actions/snapshot-sync.js +146 -105
- package/dest/actions/upload-snapshot.d.ts +1 -2
- package/dest/actions/upload-snapshot.d.ts.map +1 -1
- package/dest/actions/upload-snapshot.js +4 -52
- package/dest/config/index.d.ts +4 -0
- package/dest/config/index.d.ts.map +1 -1
- package/dest/config/index.js +9 -0
- package/package.json +22 -22
- package/src/actions/build-snapshot-metadata.ts +29 -0
- package/src/actions/create-backups.ts +41 -0
- package/src/actions/index.ts +2 -0
- package/src/actions/snapshot-sync.ts +157 -126
- package/src/actions/upload-snapshot.ts +7 -59
- package/src/config/index.ts +14 -0
|
@@ -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
|
+
}
|
package/dest/actions/index.d.ts
CHANGED
|
@@ -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"}
|
package/dest/actions/index.js
CHANGED
|
@@ -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,
|
|
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,
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
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 =
|
|
104
|
-
log.info(`Downloading snapshot
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
137
|
-
|
|
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;
|
|
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 {
|
|
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
|
|
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/dest/config/index.d.ts
CHANGED
|
@@ -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;
|
|
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"}
|
package/dest/config/index.js
CHANGED
|
@@ -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.
|
|
3
|
+
"version": "0.87.0-nightly.20250521",
|
|
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.
|
|
54
|
-
"@aztec/bb-prover": "0.
|
|
55
|
-
"@aztec/blob-sink": "0.
|
|
56
|
-
"@aztec/constants": "0.
|
|
57
|
-
"@aztec/epoch-cache": "0.
|
|
58
|
-
"@aztec/ethereum": "0.
|
|
59
|
-
"@aztec/foundation": "0.
|
|
60
|
-
"@aztec/kv-store": "0.
|
|
61
|
-
"@aztec/merkle-tree": "0.
|
|
62
|
-
"@aztec/p2p": "0.
|
|
63
|
-
"@aztec/protocol-contracts": "0.
|
|
64
|
-
"@aztec/prover-client": "0.
|
|
65
|
-
"@aztec/sequencer-client": "0.
|
|
66
|
-
"@aztec/simulator": "0.
|
|
67
|
-
"@aztec/stdlib": "0.
|
|
68
|
-
"@aztec/telemetry-client": "0.
|
|
69
|
-
"@aztec/validator-client": "0.
|
|
70
|
-
"@aztec/world-state": "0.
|
|
53
|
+
"@aztec/archiver": "0.87.0-nightly.20250521",
|
|
54
|
+
"@aztec/bb-prover": "0.87.0-nightly.20250521",
|
|
55
|
+
"@aztec/blob-sink": "0.87.0-nightly.20250521",
|
|
56
|
+
"@aztec/constants": "0.87.0-nightly.20250521",
|
|
57
|
+
"@aztec/epoch-cache": "0.87.0-nightly.20250521",
|
|
58
|
+
"@aztec/ethereum": "0.87.0-nightly.20250521",
|
|
59
|
+
"@aztec/foundation": "0.87.0-nightly.20250521",
|
|
60
|
+
"@aztec/kv-store": "0.87.0-nightly.20250521",
|
|
61
|
+
"@aztec/merkle-tree": "0.87.0-nightly.20250521",
|
|
62
|
+
"@aztec/p2p": "0.87.0-nightly.20250521",
|
|
63
|
+
"@aztec/protocol-contracts": "0.87.0-nightly.20250521",
|
|
64
|
+
"@aztec/prover-client": "0.87.0-nightly.20250521",
|
|
65
|
+
"@aztec/sequencer-client": "0.87.0-nightly.20250521",
|
|
66
|
+
"@aztec/simulator": "0.87.0-nightly.20250521",
|
|
67
|
+
"@aztec/stdlib": "0.87.0-nightly.20250521",
|
|
68
|
+
"@aztec/telemetry-client": "0.87.0-nightly.20250521",
|
|
69
|
+
"@aztec/validator-client": "0.87.0-nightly.20250521",
|
|
70
|
+
"@aztec/world-state": "0.87.0-nightly.20250521",
|
|
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": "^
|
|
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.
|
|
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": ">=
|
|
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
|
+
}
|
package/src/actions/index.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
44
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
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
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
snapshot
|
|
132
|
-
|
|
133
|
-
|
|
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
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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
|
-
|
|
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 =
|
|
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}`, {
|
|
190
|
+
log.info(`Snapshot downloaded at ${downloadDir}`, { snapshot, downloadPaths });
|
|
163
191
|
|
|
164
|
-
// If download was successful,
|
|
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,
|
|
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`, {
|
|
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,
|
|
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`, {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
}
|
package/src/config/index.ts
CHANGED
|
@@ -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
|
};
|