@aztec/node-lib 0.85.0-alpha-testnet.1 → 0.85.0-alpha-testnet.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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 +131 -104
- 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/package.json +19 -19
- 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 +141 -125
- package/src/actions/upload-snapshot.ts +7 -59
|
@@ -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;AAC9D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAGxD,OAAO,EACL,KAAK,gBAAgB,EAKtB,MAAM,yBAAyB,CAAC;AAMjC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAK3D,KAAK,kBAAkB,GAAG,IAAI,CAAC,gBAAgB,EAAE,UAAU,GAAG,cAAc,CAAC,GAC3E,IAAI,CAAC,WAAW,EAAE,WAAW,GAAG,eAAe,CAAC,GAChD,IAAI,CAAC,cAAc,EAAE,wBAAwB,GAAG,SAAS,CAAC,GAC1D,QAAQ,CAAC,eAAe,CAAC,GACzB,oBAAoB,GAAG;IACrB,2BAA2B,CAAC,EAAE,MAAM,CAAC;CACtC,CAAC;AAEJ;;;GAGG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,kBAAkB,EAAE,GAAG,EAAE,MAAM,oBA2H5E;AAED;;GAEG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,IAAI,CAAC,gBAAgB,EAAE,UAAU,CAAC,EAC5C,GAAG,EAAE,MAAM,EACX,MAAM,EAAE;IAAE,aAAa,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,UAAU,CAAC;IAAC,YAAY,EAAE,MAAM,CAAA;CAAE,iBAuCnF"}
|
|
@@ -4,125 +4,160 @@ import { getPublicClient } from '@aztec/ethereum';
|
|
|
4
4
|
import { tryRmDir } from '@aztec/foundation/fs';
|
|
5
5
|
import { DatabaseVersionManager } from '@aztec/stdlib/database-version';
|
|
6
6
|
import { createReadOnlyFileStore } from '@aztec/stdlib/file-store';
|
|
7
|
-
import { downloadSnapshot, getLatestSnapshotMetadata,
|
|
7
|
+
import { downloadSnapshot, getLatestSnapshotMetadata, makeSnapshotPaths } from '@aztec/stdlib/snapshots';
|
|
8
8
|
import { NATIVE_WORLD_STATE_DBS, WORLD_STATE_DB_VERSION, WORLD_STATE_DIR } from '@aztec/world-state';
|
|
9
9
|
import { mkdir, mkdtemp, rename } from 'fs/promises';
|
|
10
10
|
import { join } from 'path';
|
|
11
11
|
// Half day worth of L1 blocks
|
|
12
12
|
const MIN_L1_BLOCKS_TO_TRIGGER_REPLACE = 86400 / 2 / 12;
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Connects to a remote snapshot index and downloads the latest snapshot if the local archiver is behind.
|
|
15
|
+
* Behaviour depends on syncing mode.
|
|
16
|
+
*/ export async function trySnapshotSync(config, log) {
|
|
17
|
+
const { syncMode, snapshotsUrl, dataDirectory, l1ChainId, rollupVersion, l1Contracts } = config;
|
|
18
|
+
if (syncMode === 'full') {
|
|
19
|
+
log.debug('Snapshot sync is disabled. Running full sync.', {
|
|
20
|
+
syncMode: syncMode
|
|
21
|
+
});
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (!snapshotsUrl) {
|
|
25
|
+
log.verbose('Snapshot sync is disabled. No snapshots URL provided.');
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
if (!dataDirectory) {
|
|
29
|
+
log.verbose('Snapshot sync is disabled. No local data directory defined.');
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
let fileStore;
|
|
33
|
+
try {
|
|
34
|
+
fileStore = await createReadOnlyFileStore(snapshotsUrl, log);
|
|
35
|
+
} catch (err) {
|
|
36
|
+
log.error(`Invalid config for downloading snapshots`, err);
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
// Create an archiver store to check the current state
|
|
40
|
+
log.verbose(`Creating temporary archiver data store`);
|
|
41
|
+
const archiverStore = await createArchiverStore(config);
|
|
42
|
+
let archiverL1BlockNumber;
|
|
43
|
+
let archiverL2BlockNumber;
|
|
44
|
+
try {
|
|
45
|
+
[archiverL1BlockNumber, archiverL2BlockNumber] = await Promise.all([
|
|
46
|
+
archiverStore.getSynchPoint().then((s)=>s.blocksSynchedTo),
|
|
47
|
+
archiverStore.getSynchedL2BlockNumber()
|
|
48
|
+
]);
|
|
49
|
+
} finally{
|
|
50
|
+
log.verbose(`Closing temporary archiver data store`, {
|
|
51
|
+
archiverL1BlockNumber,
|
|
52
|
+
archiverL2BlockNumber
|
|
53
|
+
});
|
|
54
|
+
await archiverStore.close();
|
|
55
|
+
}
|
|
56
|
+
const minL1BlocksToTriggerReplace = config.minL1BlocksToTriggerReplace ?? MIN_L1_BLOCKS_TO_TRIGGER_REPLACE;
|
|
57
|
+
if (syncMode === 'snapshot' && archiverL2BlockNumber !== undefined && archiverL2BlockNumber >= INITIAL_L2_BLOCK_NUM) {
|
|
58
|
+
log.verbose(`Skipping non-forced snapshot sync as archiver is already synced to L2 block ${archiverL2BlockNumber}.`);
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
const currentL1BlockNumber = await getPublicClient(config).getBlockNumber();
|
|
62
|
+
if (archiverL1BlockNumber && currentL1BlockNumber - archiverL1BlockNumber < minL1BlocksToTriggerReplace) {
|
|
63
|
+
log.verbose(`Skipping snapshot sync as archiver is less than ${currentL1BlockNumber - archiverL1BlockNumber} L1 blocks behind.`, {
|
|
64
|
+
archiverL1BlockNumber,
|
|
65
|
+
currentL1BlockNumber,
|
|
66
|
+
minL1BlocksToTriggerReplace
|
|
67
|
+
});
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
const indexMetadata = {
|
|
71
|
+
l1ChainId,
|
|
72
|
+
rollupVersion,
|
|
73
|
+
rollupAddress: l1Contracts.rollupAddress
|
|
74
|
+
};
|
|
75
|
+
let snapshot;
|
|
76
|
+
try {
|
|
77
|
+
snapshot = await getLatestSnapshotMetadata(indexMetadata, fileStore);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
log.error(`Failed to get latest snapshot metadata. Skipping snapshot sync.`, err, {
|
|
80
|
+
...indexMetadata,
|
|
81
|
+
snapshotsUrl
|
|
82
|
+
});
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
if (!snapshot) {
|
|
86
|
+
log.verbose(`No snapshot found. Skipping snapshot sync.`, {
|
|
87
|
+
...indexMetadata,
|
|
88
|
+
snapshotsUrl
|
|
89
|
+
});
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
if (snapshot.schemaVersions.archiver !== ARCHIVER_DB_VERSION) {
|
|
93
|
+
log.warn(`Skipping snapshot sync as last snapshot has schema version ${snapshot.schemaVersions.archiver} but expected ${ARCHIVER_DB_VERSION}.`, snapshot);
|
|
94
|
+
return false;
|
|
95
|
+
}
|
|
96
|
+
if (snapshot.schemaVersions.worldState !== WORLD_STATE_DB_VERSION) {
|
|
97
|
+
log.warn(`Skipping snapshot sync as last snapshot has world state schema version ${snapshot.schemaVersions.worldState} but we expected ${WORLD_STATE_DB_VERSION}.`, snapshot);
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
if (archiverL1BlockNumber && snapshot.l1BlockNumber < archiverL1BlockNumber) {
|
|
101
|
+
log.verbose(`Skipping snapshot sync since local archiver is at L1 block ${archiverL1BlockNumber} which is further than last snapshot at ${snapshot.l1BlockNumber}`, {
|
|
102
|
+
snapshot,
|
|
103
|
+
archiverL1BlockNumber
|
|
104
|
+
});
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
if (archiverL1BlockNumber && snapshot.l1BlockNumber - Number(archiverL1BlockNumber) < minL1BlocksToTriggerReplace) {
|
|
108
|
+
log.verbose(`Skipping snapshot sync as archiver is less than ${snapshot.l1BlockNumber - Number(archiverL1BlockNumber)} L1 blocks behind latest snapshot.`, {
|
|
109
|
+
snapshot,
|
|
110
|
+
archiverL1BlockNumber
|
|
111
|
+
});
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
const { l1BlockNumber, l2BlockNumber } = snapshot;
|
|
115
|
+
log.info(`Syncing from snapshot at L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, {
|
|
116
|
+
snapshot,
|
|
117
|
+
snapshotsUrl
|
|
118
|
+
});
|
|
119
|
+
await snapshotSync(snapshot, log, {
|
|
120
|
+
dataDirectory: config.dataDirectory,
|
|
121
|
+
rollupAddress: config.l1Contracts.rollupAddress,
|
|
122
|
+
snapshotsUrl
|
|
123
|
+
});
|
|
124
|
+
log.info(`Snapshot synced to L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, {
|
|
125
|
+
snapshot
|
|
126
|
+
});
|
|
127
|
+
return true;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Downloads the given snapshot replacing any local data stores.
|
|
131
|
+
*/ export async function snapshotSync(snapshot, log, config) {
|
|
132
|
+
const { dataDirectory, rollupAddress } = config;
|
|
133
|
+
if (!dataDirectory) {
|
|
134
|
+
throw new Error(`No local data directory defined. Cannot sync snapshot.`);
|
|
135
|
+
}
|
|
136
|
+
const fileStore = await createReadOnlyFileStore(config.snapshotsUrl, log);
|
|
15
137
|
let downloadDir;
|
|
16
138
|
try {
|
|
17
|
-
|
|
18
|
-
if (syncMode === 'full') {
|
|
19
|
-
log.debug('Snapshot sync is disabled. Running full sync.', {
|
|
20
|
-
syncMode: syncMode
|
|
21
|
-
});
|
|
22
|
-
return false;
|
|
23
|
-
}
|
|
24
|
-
if (!snapshotsUrl) {
|
|
25
|
-
log.verbose('Snapshot sync is disabled. No snapshots URL provided.');
|
|
26
|
-
return false;
|
|
27
|
-
}
|
|
28
|
-
if (!dataDirectory) {
|
|
29
|
-
log.verbose('Snapshot sync is disabled. No local data directory defined.');
|
|
30
|
-
return false;
|
|
31
|
-
}
|
|
32
|
-
let fileStore;
|
|
33
|
-
try {
|
|
34
|
-
fileStore = await createReadOnlyFileStore(snapshotsUrl, log);
|
|
35
|
-
} catch (err) {
|
|
36
|
-
log.error(`Invalid config for downloading snapshots`, err);
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
// Create an archiver store to check the current sync state
|
|
40
|
-
archiverStore = await createArchiverStore(config);
|
|
41
|
-
const minL1BlocksToTriggerReplace = config.minL1BlocksToTriggerReplace ?? MIN_L1_BLOCKS_TO_TRIGGER_REPLACE;
|
|
42
|
-
const archiverL2BlockNumber = await archiverStore.getSynchedL2BlockNumber();
|
|
43
|
-
if (syncMode === 'snapshot' && archiverL2BlockNumber !== undefined && archiverL2BlockNumber >= INITIAL_L2_BLOCK_NUM) {
|
|
44
|
-
log.verbose(`Skipping non-forced snapshot sync as archiver is already synced to L2 block ${archiverL2BlockNumber}.`);
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
const currentL1BlockNumber = await getPublicClient(config).getBlockNumber();
|
|
48
|
-
const archiverL1BlockNumber = await archiverStore.getSynchPoint().then((s)=>s.blocksSynchedTo);
|
|
49
|
-
if (archiverL1BlockNumber && currentL1BlockNumber - archiverL1BlockNumber < minL1BlocksToTriggerReplace) {
|
|
50
|
-
log.verbose(`Skipping snapshot sync as archiver is less than ${currentL1BlockNumber - archiverL1BlockNumber} L1 blocks behind.`, {
|
|
51
|
-
archiverL1BlockNumber,
|
|
52
|
-
currentL1BlockNumber,
|
|
53
|
-
minL1BlocksToTriggerReplace
|
|
54
|
-
});
|
|
55
|
-
return false;
|
|
56
|
-
}
|
|
57
|
-
const indexMetadata = {
|
|
58
|
-
l1ChainId,
|
|
59
|
-
rollupVersion,
|
|
60
|
-
rollupAddress: l1Contracts.rollupAddress
|
|
61
|
-
};
|
|
62
|
-
let snapshot;
|
|
63
|
-
try {
|
|
64
|
-
snapshot = await getLatestSnapshotMetadata(indexMetadata, fileStore);
|
|
65
|
-
} catch (err) {
|
|
66
|
-
log.error(`Failed to get latest snapshot metadata. Skipping snapshot sync.`, err, {
|
|
67
|
-
...indexMetadata,
|
|
68
|
-
snapshotsUrl
|
|
69
|
-
});
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
if (!snapshot) {
|
|
73
|
-
log.verbose(`No snapshot found. Skipping snapshot sync.`, {
|
|
74
|
-
...indexMetadata,
|
|
75
|
-
snapshotsUrl
|
|
76
|
-
});
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
if (snapshot.schemaVersions.archiver !== ARCHIVER_DB_VERSION) {
|
|
80
|
-
log.warn(`Skipping snapshot sync as last snapshot has schema version ${snapshot.schemaVersions.archiver} but expected ${ARCHIVER_DB_VERSION}.`, snapshot);
|
|
81
|
-
return false;
|
|
82
|
-
}
|
|
83
|
-
if (snapshot.schemaVersions.worldState !== WORLD_STATE_DB_VERSION) {
|
|
84
|
-
log.warn(`Skipping snapshot sync as last snapshot has world state schema version ${snapshot.schemaVersions.worldState} but we expected ${WORLD_STATE_DB_VERSION}.`, snapshot);
|
|
85
|
-
return false;
|
|
86
|
-
}
|
|
87
|
-
if (archiverL1BlockNumber && snapshot.l1BlockNumber < archiverL1BlockNumber) {
|
|
88
|
-
log.verbose(`Skipping snapshot sync since local archiver is at L1 block ${archiverL1BlockNumber} which is further than last snapshot at ${snapshot.l1BlockNumber}`, {
|
|
89
|
-
snapshot,
|
|
90
|
-
archiverL1BlockNumber
|
|
91
|
-
});
|
|
92
|
-
return false;
|
|
93
|
-
}
|
|
94
|
-
if (archiverL1BlockNumber && snapshot.l1BlockNumber - Number(archiverL1BlockNumber) < minL1BlocksToTriggerReplace) {
|
|
95
|
-
log.verbose(`Skipping snapshot sync as archiver is less than ${snapshot.l1BlockNumber - Number(archiverL1BlockNumber)} L1 blocks behind latest snapshot.`, {
|
|
96
|
-
snapshot,
|
|
97
|
-
archiverL1BlockNumber
|
|
98
|
-
});
|
|
99
|
-
return false;
|
|
100
|
-
}
|
|
101
|
-
// Green light. Download the snapshot to a temp location.
|
|
139
|
+
// Download the snapshot to a temp location.
|
|
102
140
|
downloadDir = await mkdtemp(join(dataDirectory, 'download-'));
|
|
103
|
-
const downloadPaths =
|
|
104
|
-
log.info(`Downloading snapshot
|
|
141
|
+
const downloadPaths = makeSnapshotPaths(downloadDir);
|
|
142
|
+
log.info(`Downloading snapshot to ${downloadDir}`, {
|
|
105
143
|
snapshot,
|
|
106
144
|
downloadPaths
|
|
107
145
|
});
|
|
108
146
|
await downloadSnapshot(snapshot, downloadPaths, fileStore);
|
|
109
147
|
log.info(`Snapshot downloaded at ${downloadDir}`, {
|
|
110
|
-
snapshotsUrl,
|
|
111
148
|
snapshot,
|
|
112
149
|
downloadPaths
|
|
113
150
|
});
|
|
114
|
-
// If download was successful,
|
|
115
|
-
await archiverStore.close();
|
|
116
|
-
archiverStore = undefined;
|
|
151
|
+
// If download was successful, clear lock and version, and move download there
|
|
117
152
|
const archiverPath = join(dataDirectory, ARCHIVER_STORE_NAME);
|
|
118
|
-
await prepareTarget(archiverPath, ARCHIVER_DB_VERSION,
|
|
153
|
+
await prepareTarget(archiverPath, ARCHIVER_DB_VERSION, rollupAddress);
|
|
119
154
|
await rename(downloadPaths.archiver, join(archiverPath, 'data.mdb'));
|
|
120
155
|
log.info(`Archiver database set up from snapshot`, {
|
|
121
156
|
path: archiverPath
|
|
122
157
|
});
|
|
123
158
|
// Same for the world state dbs, only that we do not close them, since we assume they are not yet in use
|
|
124
159
|
const worldStateBasePath = join(dataDirectory, WORLD_STATE_DIR);
|
|
125
|
-
await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION,
|
|
160
|
+
await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION, rollupAddress);
|
|
126
161
|
for (const [name, dir] of NATIVE_WORLD_STATE_DBS){
|
|
127
162
|
const path = join(worldStateBasePath, dir);
|
|
128
163
|
await mkdir(path, {
|
|
@@ -133,19 +168,11 @@ export async function trySnapshotSync(config, log) {
|
|
|
133
168
|
path
|
|
134
169
|
});
|
|
135
170
|
}
|
|
136
|
-
log.info(`Snapshot synced to L1 block ${snapshot.l1BlockNumber} L2 block ${snapshot.l2BlockNumber}`, {
|
|
137
|
-
snapshot
|
|
138
|
-
});
|
|
139
171
|
} finally{
|
|
140
|
-
if (archiverStore) {
|
|
141
|
-
log.verbose(`Closing temporary archiver data store`);
|
|
142
|
-
await archiverStore.close();
|
|
143
|
-
}
|
|
144
172
|
if (downloadDir) {
|
|
145
173
|
await tryRmDir(downloadDir, log);
|
|
146
174
|
}
|
|
147
175
|
}
|
|
148
|
-
return true;
|
|
149
176
|
}
|
|
150
177
|
/** Deletes target dir and writes the new version file. */ async function prepareTarget(target, schemaVersion, rollupAddress) {
|
|
151
178
|
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aztec/node-lib",
|
|
3
|
-
"version": "0.85.0-alpha-testnet.
|
|
3
|
+
"version": "0.85.0-alpha-testnet.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": {
|
|
6
6
|
"./actions": "./dest/actions/index.js",
|
|
@@ -52,24 +52,24 @@
|
|
|
52
52
|
]
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@aztec/archiver": "0.85.0-alpha-testnet.
|
|
56
|
-
"@aztec/bb-prover": "0.85.0-alpha-testnet.
|
|
57
|
-
"@aztec/blob-sink": "0.85.0-alpha-testnet.
|
|
58
|
-
"@aztec/constants": "0.85.0-alpha-testnet.
|
|
59
|
-
"@aztec/epoch-cache": "0.85.0-alpha-testnet.
|
|
60
|
-
"@aztec/ethereum": "0.85.0-alpha-testnet.
|
|
61
|
-
"@aztec/foundation": "0.85.0-alpha-testnet.
|
|
62
|
-
"@aztec/kv-store": "0.85.0-alpha-testnet.
|
|
63
|
-
"@aztec/merkle-tree": "0.85.0-alpha-testnet.
|
|
64
|
-
"@aztec/p2p": "0.85.0-alpha-testnet.
|
|
65
|
-
"@aztec/protocol-contracts": "0.85.0-alpha-testnet.
|
|
66
|
-
"@aztec/prover-client": "0.85.0-alpha-testnet.
|
|
67
|
-
"@aztec/sequencer-client": "0.85.0-alpha-testnet.
|
|
68
|
-
"@aztec/simulator": "0.85.0-alpha-testnet.
|
|
69
|
-
"@aztec/stdlib": "0.85.0-alpha-testnet.
|
|
70
|
-
"@aztec/telemetry-client": "0.85.0-alpha-testnet.
|
|
71
|
-
"@aztec/validator-client": "0.85.0-alpha-testnet.
|
|
72
|
-
"@aztec/world-state": "0.85.0-alpha-testnet.
|
|
55
|
+
"@aztec/archiver": "0.85.0-alpha-testnet.3",
|
|
56
|
+
"@aztec/bb-prover": "0.85.0-alpha-testnet.3",
|
|
57
|
+
"@aztec/blob-sink": "0.85.0-alpha-testnet.3",
|
|
58
|
+
"@aztec/constants": "0.85.0-alpha-testnet.3",
|
|
59
|
+
"@aztec/epoch-cache": "0.85.0-alpha-testnet.3",
|
|
60
|
+
"@aztec/ethereum": "0.85.0-alpha-testnet.3",
|
|
61
|
+
"@aztec/foundation": "0.85.0-alpha-testnet.3",
|
|
62
|
+
"@aztec/kv-store": "0.85.0-alpha-testnet.3",
|
|
63
|
+
"@aztec/merkle-tree": "0.85.0-alpha-testnet.3",
|
|
64
|
+
"@aztec/p2p": "0.85.0-alpha-testnet.3",
|
|
65
|
+
"@aztec/protocol-contracts": "0.85.0-alpha-testnet.3",
|
|
66
|
+
"@aztec/prover-client": "0.85.0-alpha-testnet.3",
|
|
67
|
+
"@aztec/sequencer-client": "0.85.0-alpha-testnet.3",
|
|
68
|
+
"@aztec/simulator": "0.85.0-alpha-testnet.3",
|
|
69
|
+
"@aztec/stdlib": "0.85.0-alpha-testnet.3",
|
|
70
|
+
"@aztec/telemetry-client": "0.85.0-alpha-testnet.3",
|
|
71
|
+
"@aztec/validator-client": "0.85.0-alpha-testnet.3",
|
|
72
|
+
"@aztec/world-state": "0.85.0-alpha-testnet.3",
|
|
73
73
|
"tslib": "^2.4.0"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Archiver } from '@aztec/archiver';
|
|
2
|
+
import type { UploadSnapshotMetadata } from '@aztec/stdlib/snapshots';
|
|
3
|
+
|
|
4
|
+
import type { UploadSnapshotConfig } from './upload-snapshot.js';
|
|
5
|
+
|
|
6
|
+
export async function buildSnapshotMetadata(
|
|
7
|
+
archiver: Archiver,
|
|
8
|
+
config: UploadSnapshotConfig,
|
|
9
|
+
): Promise<UploadSnapshotMetadata> {
|
|
10
|
+
const [rollupAddress, l1BlockNumber, { latest }] = await Promise.all([
|
|
11
|
+
archiver.getRollupAddress(),
|
|
12
|
+
archiver.getL1BlockNumber(),
|
|
13
|
+
archiver.getL2Tips(),
|
|
14
|
+
] as const);
|
|
15
|
+
|
|
16
|
+
const { number: l2BlockNumber, hash: l2BlockHash } = latest;
|
|
17
|
+
if (!l2BlockHash) {
|
|
18
|
+
throw new Error(`Failed to get L2 block hash from archiver.`);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
return {
|
|
22
|
+
l1ChainId: config.l1ChainId,
|
|
23
|
+
rollupVersion: config.rollupVersion,
|
|
24
|
+
rollupAddress,
|
|
25
|
+
l2BlockNumber,
|
|
26
|
+
l2BlockHash,
|
|
27
|
+
l1BlockNumber: Number(l1BlockNumber),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Archiver } from '@aztec/archiver';
|
|
2
|
+
import type { Logger } from '@aztec/foundation/log';
|
|
3
|
+
import type { WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
|
|
4
|
+
import type { SnapshotDataUrls } from '@aztec/stdlib/snapshots';
|
|
5
|
+
|
|
6
|
+
import { existsSync } from 'fs';
|
|
7
|
+
import { join } from 'path/posix';
|
|
8
|
+
|
|
9
|
+
export async function createBackups(
|
|
10
|
+
backupDir: string,
|
|
11
|
+
archiver: Archiver,
|
|
12
|
+
worldState: WorldStateSynchronizer,
|
|
13
|
+
log: Logger,
|
|
14
|
+
) {
|
|
15
|
+
try {
|
|
16
|
+
log.info(`Pausing archiver and world state sync to start snapshot upload`);
|
|
17
|
+
await archiver.stop();
|
|
18
|
+
await worldState.stopSync();
|
|
19
|
+
|
|
20
|
+
log.info(`Creating backups of lmdb environments to ${backupDir}`);
|
|
21
|
+
const [archiverPath, worldStatePaths] = await Promise.all([
|
|
22
|
+
archiver.backupTo(join(backupDir, 'archiver')),
|
|
23
|
+
worldState.backupTo(join(backupDir, 'world-state')),
|
|
24
|
+
]);
|
|
25
|
+
const paths: SnapshotDataUrls = { ...worldStatePaths, archiver: archiverPath };
|
|
26
|
+
|
|
27
|
+
const missing = Object.entries(paths).filter(([_key, path]) => !path || !existsSync(path));
|
|
28
|
+
if (missing.length > 0) {
|
|
29
|
+
throw new Error(`Missing backup files: ${missing.map(([key, path]) => `${path} (${key})`).join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
log.info(`Data stores backed up to ${backupDir}`, { paths });
|
|
33
|
+
return paths;
|
|
34
|
+
} catch (err) {
|
|
35
|
+
throw new Error(`Error creating backups for snapshot upload: ${err}`);
|
|
36
|
+
} finally {
|
|
37
|
+
log.info(`Resuming archiver and world state sync`);
|
|
38
|
+
worldState.resumeSync();
|
|
39
|
+
archiver.resume();
|
|
40
|
+
}
|
|
41
|
+
}
|
package/src/actions/index.ts
CHANGED
|
@@ -1,10 +1,4 @@
|
|
|
1
|
-
import {
|
|
2
|
-
ARCHIVER_DB_VERSION,
|
|
3
|
-
ARCHIVER_STORE_NAME,
|
|
4
|
-
type ArchiverConfig,
|
|
5
|
-
type ArchiverDataStore,
|
|
6
|
-
createArchiverStore,
|
|
7
|
-
} from '@aztec/archiver';
|
|
1
|
+
import { ARCHIVER_DB_VERSION, ARCHIVER_STORE_NAME, type ArchiverConfig, createArchiverStore } from '@aztec/archiver';
|
|
8
2
|
import { INITIAL_L2_BLOCK_NUM } from '@aztec/constants';
|
|
9
3
|
import { type EthereumClientConfig, getPublicClient } from '@aztec/ethereum';
|
|
10
4
|
import type { EthAddress } from '@aztec/foundation/eth-address';
|
|
@@ -19,7 +13,7 @@ import {
|
|
|
19
13
|
type SnapshotsIndexMetadata,
|
|
20
14
|
downloadSnapshot,
|
|
21
15
|
getLatestSnapshotMetadata,
|
|
22
|
-
|
|
16
|
+
makeSnapshotPaths,
|
|
23
17
|
} from '@aztec/stdlib/snapshots';
|
|
24
18
|
import { NATIVE_WORLD_STATE_DBS, WORLD_STATE_DB_VERSION, WORLD_STATE_DIR } from '@aztec/world-state';
|
|
25
19
|
|
|
@@ -39,158 +33,180 @@ type SnapshotSyncConfig = Pick<SharedNodeConfig, 'syncMode' | 'snapshotsUrl'> &
|
|
|
39
33
|
minL1BlocksToTriggerReplace?: number;
|
|
40
34
|
};
|
|
41
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Connects to a remote snapshot index and downloads the latest snapshot if the local archiver is behind.
|
|
38
|
+
* Behaviour depends on syncing mode.
|
|
39
|
+
*/
|
|
42
40
|
export async function trySnapshotSync(config: SnapshotSyncConfig, log: Logger) {
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
const { syncMode, snapshotsUrl, dataDirectory, l1ChainId, rollupVersion, l1Contracts } = config;
|
|
42
|
+
if (syncMode === 'full') {
|
|
43
|
+
log.debug('Snapshot sync is disabled. Running full sync.', { syncMode: syncMode });
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!snapshotsUrl) {
|
|
48
|
+
log.verbose('Snapshot sync is disabled. No snapshots URL provided.');
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!dataDirectory) {
|
|
53
|
+
log.verbose('Snapshot sync is disabled. No local data directory defined.');
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
45
56
|
|
|
57
|
+
let fileStore: ReadOnlyFileStore;
|
|
46
58
|
try {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
59
|
+
fileStore = await createReadOnlyFileStore(snapshotsUrl, log);
|
|
60
|
+
} catch (err) {
|
|
61
|
+
log.error(`Invalid config for downloading snapshots`, err);
|
|
62
|
+
return false;
|
|
63
|
+
}
|
|
52
64
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
65
|
+
// Create an archiver store to check the current state
|
|
66
|
+
log.verbose(`Creating temporary archiver data store`);
|
|
67
|
+
const archiverStore = await createArchiverStore(config);
|
|
68
|
+
let archiverL1BlockNumber: bigint | undefined;
|
|
69
|
+
let archiverL2BlockNumber: number | undefined;
|
|
70
|
+
try {
|
|
71
|
+
[archiverL1BlockNumber, archiverL2BlockNumber] = await Promise.all([
|
|
72
|
+
archiverStore.getSynchPoint().then(s => s.blocksSynchedTo),
|
|
73
|
+
archiverStore.getSynchedL2BlockNumber(),
|
|
74
|
+
] as const);
|
|
75
|
+
} finally {
|
|
76
|
+
log.verbose(`Closing temporary archiver data store`, { archiverL1BlockNumber, archiverL2BlockNumber });
|
|
77
|
+
await archiverStore.close();
|
|
78
|
+
}
|
|
57
79
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
const minL1BlocksToTriggerReplace = config.minL1BlocksToTriggerReplace ?? MIN_L1_BLOCKS_TO_TRIGGER_REPLACE;
|
|
81
|
+
if (syncMode === 'snapshot' && archiverL2BlockNumber !== undefined && archiverL2BlockNumber >= INITIAL_L2_BLOCK_NUM) {
|
|
82
|
+
log.verbose(
|
|
83
|
+
`Skipping non-forced snapshot sync as archiver is already synced to L2 block ${archiverL2BlockNumber}.`,
|
|
84
|
+
);
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
62
87
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
88
|
+
const currentL1BlockNumber = await getPublicClient(config).getBlockNumber();
|
|
89
|
+
if (archiverL1BlockNumber && currentL1BlockNumber - archiverL1BlockNumber < minL1BlocksToTriggerReplace) {
|
|
90
|
+
log.verbose(
|
|
91
|
+
`Skipping snapshot sync as archiver is less than ${
|
|
92
|
+
currentL1BlockNumber - archiverL1BlockNumber
|
|
93
|
+
} L1 blocks behind.`,
|
|
94
|
+
{ archiverL1BlockNumber, currentL1BlockNumber, minL1BlocksToTriggerReplace },
|
|
95
|
+
);
|
|
96
|
+
return false;
|
|
97
|
+
}
|
|
70
98
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
99
|
+
const indexMetadata: SnapshotsIndexMetadata = {
|
|
100
|
+
l1ChainId,
|
|
101
|
+
rollupVersion,
|
|
102
|
+
rollupAddress: l1Contracts.rollupAddress,
|
|
103
|
+
};
|
|
104
|
+
let snapshot: SnapshotMetadata | undefined;
|
|
105
|
+
try {
|
|
106
|
+
snapshot = await getLatestSnapshotMetadata(indexMetadata, fileStore);
|
|
107
|
+
} catch (err) {
|
|
108
|
+
log.error(`Failed to get latest snapshot metadata. Skipping snapshot sync.`, err, {
|
|
109
|
+
...indexMetadata,
|
|
110
|
+
snapshotsUrl,
|
|
111
|
+
});
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
86
114
|
|
|
87
|
-
|
|
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
|
-
}
|
|
115
|
+
if (!snapshot) {
|
|
116
|
+
log.verbose(`No snapshot found. Skipping snapshot sync.`, { ...indexMetadata, snapshotsUrl });
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
98
119
|
|
|
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
|
-
}
|
|
120
|
+
if (snapshot.schemaVersions.archiver !== ARCHIVER_DB_VERSION) {
|
|
121
|
+
log.warn(
|
|
122
|
+
`Skipping snapshot sync as last snapshot has schema version ${snapshot.schemaVersions.archiver} but expected ${ARCHIVER_DB_VERSION}.`,
|
|
123
|
+
snapshot,
|
|
124
|
+
);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
114
127
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
128
|
+
if (snapshot.schemaVersions.worldState !== WORLD_STATE_DB_VERSION) {
|
|
129
|
+
log.warn(
|
|
130
|
+
`Skipping snapshot sync as last snapshot has world state schema version ${snapshot.schemaVersions.worldState} but we expected ${WORLD_STATE_DB_VERSION}.`,
|
|
131
|
+
snapshot,
|
|
132
|
+
);
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
119
135
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
136
|
+
if (archiverL1BlockNumber && snapshot.l1BlockNumber < archiverL1BlockNumber) {
|
|
137
|
+
log.verbose(
|
|
138
|
+
`Skipping snapshot sync since local archiver is at L1 block ${archiverL1BlockNumber} which is further than last snapshot at ${snapshot.l1BlockNumber}`,
|
|
139
|
+
{ snapshot, archiverL1BlockNumber },
|
|
140
|
+
);
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
127
143
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
snapshot
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
144
|
+
if (archiverL1BlockNumber && snapshot.l1BlockNumber - Number(archiverL1BlockNumber) < minL1BlocksToTriggerReplace) {
|
|
145
|
+
log.verbose(
|
|
146
|
+
`Skipping snapshot sync as archiver is less than ${
|
|
147
|
+
snapshot.l1BlockNumber - Number(archiverL1BlockNumber)
|
|
148
|
+
} L1 blocks behind latest snapshot.`,
|
|
149
|
+
{ snapshot, archiverL1BlockNumber },
|
|
150
|
+
);
|
|
151
|
+
return false;
|
|
152
|
+
}
|
|
135
153
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
154
|
+
const { l1BlockNumber, l2BlockNumber } = snapshot;
|
|
155
|
+
log.info(`Syncing from snapshot at L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, { snapshot, snapshotsUrl });
|
|
156
|
+
await snapshotSync(snapshot, log, {
|
|
157
|
+
dataDirectory: config.dataDirectory!,
|
|
158
|
+
rollupAddress: config.l1Contracts.rollupAddress,
|
|
159
|
+
snapshotsUrl,
|
|
160
|
+
});
|
|
161
|
+
log.info(`Snapshot synced to L1 block ${l1BlockNumber} L2 block ${l2BlockNumber}`, { snapshot });
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
143
164
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
165
|
+
/**
|
|
166
|
+
* Downloads the given snapshot replacing any local data stores.
|
|
167
|
+
*/
|
|
168
|
+
export async function snapshotSync(
|
|
169
|
+
snapshot: Pick<SnapshotMetadata, 'dataUrls'>,
|
|
170
|
+
log: Logger,
|
|
171
|
+
config: { dataDirectory: string; rollupAddress: EthAddress; snapshotsUrl: string },
|
|
172
|
+
) {
|
|
173
|
+
const { dataDirectory, rollupAddress } = config;
|
|
174
|
+
if (!dataDirectory) {
|
|
175
|
+
throw new Error(`No local data directory defined. Cannot sync snapshot.`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const fileStore = await createReadOnlyFileStore(config.snapshotsUrl, log);
|
|
153
179
|
|
|
154
|
-
|
|
180
|
+
let downloadDir: string | undefined;
|
|
181
|
+
|
|
182
|
+
try {
|
|
183
|
+
// Download the snapshot to a temp location.
|
|
155
184
|
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
|
-
);
|
|
185
|
+
const downloadPaths = makeSnapshotPaths(downloadDir);
|
|
186
|
+
log.info(`Downloading snapshot to ${downloadDir}`, { snapshot, downloadPaths });
|
|
161
187
|
await downloadSnapshot(snapshot, downloadPaths, fileStore);
|
|
162
|
-
log.info(`Snapshot downloaded at ${downloadDir}`, {
|
|
188
|
+
log.info(`Snapshot downloaded at ${downloadDir}`, { snapshot, downloadPaths });
|
|
163
189
|
|
|
164
|
-
// If download was successful,
|
|
165
|
-
await archiverStore.close();
|
|
166
|
-
archiverStore = undefined;
|
|
190
|
+
// If download was successful, clear lock and version, and move download there
|
|
167
191
|
const archiverPath = join(dataDirectory, ARCHIVER_STORE_NAME);
|
|
168
|
-
await prepareTarget(archiverPath, ARCHIVER_DB_VERSION,
|
|
192
|
+
await prepareTarget(archiverPath, ARCHIVER_DB_VERSION, rollupAddress);
|
|
169
193
|
await rename(downloadPaths.archiver, join(archiverPath, 'data.mdb'));
|
|
170
194
|
log.info(`Archiver database set up from snapshot`, { path: archiverPath });
|
|
171
195
|
|
|
172
196
|
// Same for the world state dbs, only that we do not close them, since we assume they are not yet in use
|
|
173
197
|
const worldStateBasePath = join(dataDirectory, WORLD_STATE_DIR);
|
|
174
|
-
await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION,
|
|
198
|
+
await prepareTarget(worldStateBasePath, WORLD_STATE_DB_VERSION, rollupAddress);
|
|
175
199
|
for (const [name, dir] of NATIVE_WORLD_STATE_DBS) {
|
|
176
200
|
const path = join(worldStateBasePath, dir);
|
|
177
201
|
await mkdir(path, { recursive: true });
|
|
178
202
|
await rename(downloadPaths[name], join(path, 'data.mdb'));
|
|
179
203
|
log.info(`World state database ${name} set up from snapshot`, { path });
|
|
180
204
|
}
|
|
181
|
-
|
|
182
|
-
log.info(`Snapshot synced to L1 block ${snapshot.l1BlockNumber} L2 block ${snapshot.l2BlockNumber}`, { snapshot });
|
|
183
205
|
} finally {
|
|
184
|
-
if (archiverStore) {
|
|
185
|
-
log.verbose(`Closing temporary archiver data store`);
|
|
186
|
-
await archiverStore.close();
|
|
187
|
-
}
|
|
188
206
|
if (downloadDir) {
|
|
189
207
|
await tryRmDir(downloadDir, log);
|
|
190
208
|
}
|
|
191
209
|
}
|
|
192
|
-
|
|
193
|
-
return true;
|
|
194
210
|
}
|
|
195
211
|
|
|
196
212
|
/** 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
|
-
}
|