@aztec/aztec-node 0.0.1-commit.c7c42ec → 0.0.1-commit.c949de6bc
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/aztec-node/config.d.ts +7 -4
- package/dest/aztec-node/config.d.ts.map +1 -1
- package/dest/aztec-node/config.js +10 -2
- package/dest/aztec-node/node_metrics.d.ts +1 -1
- package/dest/aztec-node/node_metrics.d.ts.map +1 -1
- package/dest/aztec-node/node_metrics.js +9 -16
- package/dest/aztec-node/server.d.ts +56 -102
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +788 -250
- package/dest/sentinel/factory.d.ts +1 -1
- package/dest/sentinel/factory.d.ts.map +1 -1
- package/dest/sentinel/factory.js +1 -1
- package/dest/sentinel/sentinel.d.ts +6 -5
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +81 -50
- package/dest/sentinel/store.d.ts +2 -2
- package/dest/sentinel/store.d.ts.map +1 -1
- package/dest/sentinel/store.js +11 -7
- package/package.json +28 -26
- package/src/aztec-node/config.ts +24 -8
- package/src/aztec-node/node_metrics.ts +6 -17
- package/src/aztec-node/server.ts +513 -323
- package/src/sentinel/factory.ts +1 -6
- package/src/sentinel/sentinel.ts +94 -52
- package/src/sentinel/store.ts +12 -12
package/src/aztec-node/server.ts
CHANGED
|
@@ -1,26 +1,15 @@
|
|
|
1
1
|
import { Archiver, createArchiver } from '@aztec/archiver';
|
|
2
2
|
import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
|
|
3
|
-
import { type BlobClientInterface,
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
createReadOnlyFileStoreBlobClients,
|
|
7
|
-
createWritableFileStoreBlobClient,
|
|
8
|
-
} from '@aztec/blob-client/filestore';
|
|
9
|
-
import {
|
|
10
|
-
ARCHIVE_HEIGHT,
|
|
11
|
-
INITIAL_L2_BLOCK_NUM,
|
|
12
|
-
type L1_TO_L2_MSG_TREE_HEIGHT,
|
|
13
|
-
type NOTE_HASH_TREE_HEIGHT,
|
|
14
|
-
type NULLIFIER_TREE_HEIGHT,
|
|
15
|
-
type PUBLIC_DATA_TREE_HEIGHT,
|
|
16
|
-
} from '@aztec/constants';
|
|
3
|
+
import { type BlobClientInterface, createBlobClientWithFileStores } from '@aztec/blob-client/client';
|
|
4
|
+
import { Blob } from '@aztec/blob-lib';
|
|
5
|
+
import { ARCHIVE_HEIGHT, type L1_TO_L2_MSG_TREE_HEIGHT, type NOTE_HASH_TREE_HEIGHT } from '@aztec/constants';
|
|
17
6
|
import { EpochCache, type EpochCacheInterface } from '@aztec/epoch-cache';
|
|
18
7
|
import { createEthereumChain } from '@aztec/ethereum/chain';
|
|
19
8
|
import { getPublicClient } from '@aztec/ethereum/client';
|
|
20
9
|
import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
21
10
|
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
|
|
22
|
-
import { BlockNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
23
|
-
import { compactArray, pick } from '@aztec/foundation/collection';
|
|
11
|
+
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
12
|
+
import { compactArray, pick, unique } from '@aztec/foundation/collection';
|
|
24
13
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
25
14
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
26
15
|
import { BadRequestError } from '@aztec/foundation/json-rpc';
|
|
@@ -28,22 +17,20 @@ import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
|
28
17
|
import { count } from '@aztec/foundation/string';
|
|
29
18
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
30
19
|
import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
|
|
31
|
-
import { KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
|
|
20
|
+
import { type KeyStore, KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
|
|
32
21
|
import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
|
|
22
|
+
import { createForwarderL1TxUtilsFromSigners, createL1TxUtilsFromSigners } from '@aztec/node-lib/factories';
|
|
33
23
|
import {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
24
|
+
type P2P,
|
|
25
|
+
type P2PClientDeps,
|
|
26
|
+
createP2PClient,
|
|
27
|
+
createTxValidatorForAcceptingTxsOverRPC,
|
|
28
|
+
getDefaultAllowedSetupFunctions,
|
|
29
|
+
} from '@aztec/p2p';
|
|
38
30
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
39
|
-
import {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
SequencerClient,
|
|
43
|
-
type SequencerPublisher,
|
|
44
|
-
createValidatorForAcceptingTxs,
|
|
45
|
-
} from '@aztec/sequencer-client';
|
|
46
|
-
import { CheckpointsBuilder } from '@aztec/sequencer-client';
|
|
31
|
+
import { type ProverNode, type ProverNodeDeps, createProverNode } from '@aztec/prover-node';
|
|
32
|
+
import { createKeyStoreForProver } from '@aztec/prover-node/config';
|
|
33
|
+
import { GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client';
|
|
47
34
|
import { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
48
35
|
import {
|
|
49
36
|
AttestationsBlockWatcher,
|
|
@@ -55,13 +42,14 @@ import {
|
|
|
55
42
|
import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
|
|
56
43
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
57
44
|
import {
|
|
45
|
+
type BlockData,
|
|
46
|
+
BlockHash,
|
|
58
47
|
type BlockParameter,
|
|
59
48
|
type DataInBlock,
|
|
60
|
-
|
|
61
|
-
L2BlockHash,
|
|
49
|
+
L2Block,
|
|
62
50
|
type L2BlockSource,
|
|
63
|
-
type PublishedL2Block,
|
|
64
51
|
} from '@aztec/stdlib/block';
|
|
52
|
+
import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
65
53
|
import type {
|
|
66
54
|
ContractClassPublic,
|
|
67
55
|
ContractDataSource,
|
|
@@ -88,9 +76,9 @@ import {
|
|
|
88
76
|
type WorldStateSynchronizer,
|
|
89
77
|
tryStop,
|
|
90
78
|
} from '@aztec/stdlib/interfaces/server';
|
|
91
|
-
import type { LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
|
|
79
|
+
import type { DebugLogStore, LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
|
|
80
|
+
import { InMemoryDebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
|
|
92
81
|
import { InboxLeaf, type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
93
|
-
import { P2PClientType } from '@aztec/stdlib/p2p';
|
|
94
82
|
import type { Offense, SlashPayloadRound } from '@aztec/stdlib/slashing';
|
|
95
83
|
import type { NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
|
|
96
84
|
import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
|
|
@@ -116,6 +104,8 @@ import {
|
|
|
116
104
|
trackSpan,
|
|
117
105
|
} from '@aztec/telemetry-client';
|
|
118
106
|
import {
|
|
107
|
+
FullNodeCheckpointsBuilder as CheckpointsBuilder,
|
|
108
|
+
FullNodeCheckpointsBuilder,
|
|
119
109
|
NodeKeystoreAdapter,
|
|
120
110
|
ValidatorClient,
|
|
121
111
|
createBlockProposalHandler,
|
|
@@ -135,6 +125,7 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
135
125
|
*/
|
|
136
126
|
export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
137
127
|
private metrics: NodeMetrics;
|
|
128
|
+
private initialHeaderHashPromise: Promise<BlockHash> | undefined = undefined;
|
|
138
129
|
|
|
139
130
|
// Prevent two snapshot operations to happen simultaneously
|
|
140
131
|
private isUploadingSnapshot = false;
|
|
@@ -150,6 +141,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
150
141
|
protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
|
|
151
142
|
protected readonly worldStateSynchronizer: WorldStateSynchronizer,
|
|
152
143
|
protected readonly sequencer: SequencerClient | undefined,
|
|
144
|
+
protected readonly proverNode: ProverNode | undefined,
|
|
153
145
|
protected readonly slasherClient: SlasherClientInterface | undefined,
|
|
154
146
|
protected readonly validatorsSentinel: Sentinel | undefined,
|
|
155
147
|
protected readonly epochPruneWatcher: EpochPruneWatcher | undefined,
|
|
@@ -162,12 +154,22 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
162
154
|
private telemetry: TelemetryClient = getTelemetryClient(),
|
|
163
155
|
private log = createLogger('node'),
|
|
164
156
|
private blobClient?: BlobClientInterface,
|
|
157
|
+
private validatorClient?: ValidatorClient,
|
|
158
|
+
private keyStoreManager?: KeystoreManager,
|
|
159
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
165
160
|
) {
|
|
166
161
|
this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
|
|
167
162
|
this.tracer = telemetry.getTracer('AztecNodeService');
|
|
168
163
|
|
|
169
164
|
this.log.info(`Aztec Node version: ${this.packageVersion}`);
|
|
170
165
|
this.log.info(`Aztec Node started on chain 0x${l1ChainId.toString(16)}`, config.l1Contracts);
|
|
166
|
+
|
|
167
|
+
// A defensive check that protects us against introducing a bug in the complex `createAndSync` function. We must
|
|
168
|
+
// never have debugLogStore enabled when not in test mode because then we would be accumulating debug logs in
|
|
169
|
+
// memory which could be a DoS vector on the sequencer (since no fees are paid for debug logs).
|
|
170
|
+
if (debugLogStore.isEnabled && config.realProofs) {
|
|
171
|
+
throw new Error('debugLogStore should never be enabled when realProofs are set');
|
|
172
|
+
}
|
|
171
173
|
}
|
|
172
174
|
|
|
173
175
|
public async getWorldStateSyncStatus(): Promise<WorldStateSyncStatus> {
|
|
@@ -191,12 +193,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
191
193
|
logger?: Logger;
|
|
192
194
|
publisher?: SequencerPublisher;
|
|
193
195
|
dateProvider?: DateProvider;
|
|
194
|
-
|
|
195
|
-
|
|
196
|
+
p2pClientDeps?: P2PClientDeps;
|
|
197
|
+
proverNodeDeps?: Partial<ProverNodeDeps>;
|
|
196
198
|
} = {},
|
|
197
199
|
options: {
|
|
198
200
|
prefilledPublicData?: PublicDataTreeLeaf[];
|
|
199
201
|
dontStartSequencer?: boolean;
|
|
202
|
+
dontStartProverNode?: boolean;
|
|
200
203
|
} = {},
|
|
201
204
|
): Promise<AztecNodeService> {
|
|
202
205
|
const config = { ...inputConfig }; // Copy the config so we dont mutate the input object
|
|
@@ -206,16 +209,29 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
206
209
|
const dateProvider = deps.dateProvider ?? new DateProvider();
|
|
207
210
|
const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
208
211
|
|
|
209
|
-
// Build a key store from file if given or from environment otherwise
|
|
212
|
+
// Build a key store from file if given or from environment otherwise.
|
|
213
|
+
// We keep the raw KeyStore available so we can merge with prover keys if enableProverNode is set.
|
|
210
214
|
let keyStoreManager: KeystoreManager | undefined;
|
|
211
215
|
const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
|
|
212
216
|
if (keyStoreProvided) {
|
|
213
217
|
const keyStores = loadKeystores(config.keyStoreDirectory!);
|
|
214
218
|
keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
215
219
|
} else {
|
|
216
|
-
const
|
|
217
|
-
|
|
218
|
-
|
|
220
|
+
const rawKeyStores: KeyStore[] = [];
|
|
221
|
+
const validatorKeyStore = createKeyStoreForValidator(config);
|
|
222
|
+
if (validatorKeyStore) {
|
|
223
|
+
rawKeyStores.push(validatorKeyStore);
|
|
224
|
+
}
|
|
225
|
+
if (config.enableProverNode) {
|
|
226
|
+
const proverKeyStore = createKeyStoreForProver(config);
|
|
227
|
+
if (proverKeyStore) {
|
|
228
|
+
rawKeyStores.push(proverKeyStore);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
if (rawKeyStores.length > 0) {
|
|
232
|
+
keyStoreManager = new KeystoreManager(
|
|
233
|
+
rawKeyStores.length === 1 ? rawKeyStores[0] : mergeKeystores(rawKeyStores),
|
|
234
|
+
);
|
|
219
235
|
}
|
|
220
236
|
}
|
|
221
237
|
|
|
@@ -226,10 +242,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
226
242
|
if (keyStoreManager === undefined) {
|
|
227
243
|
throw new Error('Failed to create key store, a requirement for running a validator');
|
|
228
244
|
}
|
|
229
|
-
if (!keyStoreProvided) {
|
|
230
|
-
log.warn(
|
|
231
|
-
'KEY STORE CREATED FROM ENVIRONMENT, IT IS RECOMMENDED TO USE A FILE-BASED KEY STORE IN PRODUCTION ENVIRONMENTS',
|
|
232
|
-
);
|
|
245
|
+
if (!keyStoreProvided && process.env.NODE_ENV !== 'test') {
|
|
246
|
+
log.warn("Keystore created from env: it's recommended to use a file-based key store for production");
|
|
233
247
|
}
|
|
234
248
|
ValidatorClient.validateKeyStoreConfiguration(keyStoreManager, log);
|
|
235
249
|
}
|
|
@@ -271,24 +285,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
271
285
|
);
|
|
272
286
|
}
|
|
273
287
|
|
|
274
|
-
const
|
|
275
|
-
l1ChainId: config.l1ChainId,
|
|
276
|
-
rollupVersion: config.rollupVersion,
|
|
277
|
-
rollupAddress: config.l1Contracts.rollupAddress.toString(),
|
|
278
|
-
};
|
|
279
|
-
|
|
280
|
-
const [fileStoreClients, fileStoreUploadClient] = await Promise.all([
|
|
281
|
-
createReadOnlyFileStoreBlobClients(config.blobFileStoreUrls, blobFileStoreMetadata, log),
|
|
282
|
-
createWritableFileStoreBlobClient(config.blobFileStoreUploadUrl, blobFileStoreMetadata, log),
|
|
283
|
-
]);
|
|
284
|
-
|
|
285
|
-
const blobClient =
|
|
286
|
-
deps.blobClient ??
|
|
287
|
-
createBlobClient(config, {
|
|
288
|
-
logger: createLogger('node:blob-client:client'),
|
|
289
|
-
fileStoreClients,
|
|
290
|
-
fileStoreUploadClient,
|
|
291
|
-
});
|
|
288
|
+
const blobClient = await createBlobClientWithFileStores(config, log.createChild('blob-client'));
|
|
292
289
|
|
|
293
290
|
// attempt snapshot sync if possible
|
|
294
291
|
await trySnapshotSync(config, log);
|
|
@@ -312,14 +309,28 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
312
309
|
config.realProofs || config.debugForceTxProofVerification
|
|
313
310
|
? await BBCircuitVerifier.new(config)
|
|
314
311
|
: new TestCircuitVerifier(config.proverTestVerificationDelayMs);
|
|
312
|
+
|
|
313
|
+
let debugLogStore: DebugLogStore;
|
|
315
314
|
if (!config.realProofs) {
|
|
316
315
|
log.warn(`Aztec node is accepting fake proofs`);
|
|
316
|
+
|
|
317
|
+
debugLogStore = new InMemoryDebugLogStore();
|
|
318
|
+
log.info(
|
|
319
|
+
'Aztec node started in test mode (realProofs set to false) hence debug logs from public functions will be collected and served',
|
|
320
|
+
);
|
|
321
|
+
} else {
|
|
322
|
+
debugLogStore = new NullDebugLogStore();
|
|
317
323
|
}
|
|
324
|
+
|
|
318
325
|
const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier);
|
|
319
326
|
|
|
327
|
+
const proverOnly = config.enableProverNode && config.disableValidator;
|
|
328
|
+
if (proverOnly) {
|
|
329
|
+
log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
|
|
330
|
+
}
|
|
331
|
+
|
|
320
332
|
// create the tx pool and the p2p client, which will need the l2 block source
|
|
321
333
|
const p2pClient = await createP2PClient(
|
|
322
|
-
P2PClientType.Full,
|
|
323
334
|
config,
|
|
324
335
|
archiver,
|
|
325
336
|
proofVerifier,
|
|
@@ -334,7 +345,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
334
345
|
// We should really not be modifying the config object
|
|
335
346
|
config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
|
|
336
347
|
|
|
337
|
-
|
|
348
|
+
// We'll accumulate sentinel watchers here
|
|
349
|
+
const watchers: Watcher[] = [];
|
|
350
|
+
|
|
351
|
+
// Create FullNodeCheckpointsBuilder for block proposal handling and tx validation
|
|
352
|
+
const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
|
|
338
353
|
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
|
|
339
354
|
worldStateSynchronizer,
|
|
340
355
|
archiver,
|
|
@@ -342,45 +357,48 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
342
357
|
telemetry,
|
|
343
358
|
);
|
|
344
359
|
|
|
345
|
-
|
|
346
|
-
const watchers: Watcher[] = [];
|
|
347
|
-
|
|
348
|
-
// Create validator client if required
|
|
349
|
-
const validatorClient = createValidatorClient(config, {
|
|
350
|
-
p2pClient,
|
|
351
|
-
telemetry,
|
|
352
|
-
dateProvider,
|
|
353
|
-
epochCache,
|
|
354
|
-
blockBuilder,
|
|
355
|
-
blockSource: archiver,
|
|
356
|
-
l1ToL2MessageSource: archiver,
|
|
357
|
-
keyStoreManager,
|
|
358
|
-
fileStoreBlobUploadClient: fileStoreUploadClient,
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
// If we have a validator client, register it as a source of offenses for the slasher,
|
|
362
|
-
// and have it register callbacks on the p2p client *before* we start it, otherwise messages
|
|
363
|
-
// like attestations or auths will fail.
|
|
364
|
-
if (validatorClient) {
|
|
365
|
-
watchers.push(validatorClient);
|
|
366
|
-
if (!options.dontStartSequencer) {
|
|
367
|
-
await validatorClient.registerHandlers();
|
|
368
|
-
}
|
|
369
|
-
}
|
|
360
|
+
let validatorClient: ValidatorClient | undefined;
|
|
370
361
|
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
362
|
+
if (!proverOnly) {
|
|
363
|
+
// Create validator client if required
|
|
364
|
+
validatorClient = await createValidatorClient(config, {
|
|
365
|
+
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
366
|
+
worldState: worldStateSynchronizer,
|
|
367
|
+
p2pClient,
|
|
368
|
+
telemetry,
|
|
369
|
+
dateProvider,
|
|
377
370
|
epochCache,
|
|
378
371
|
blockSource: archiver,
|
|
379
372
|
l1ToL2MessageSource: archiver,
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
373
|
+
keyStoreManager,
|
|
374
|
+
blobClient,
|
|
375
|
+
});
|
|
376
|
+
|
|
377
|
+
// If we have a validator client, register it as a source of offenses for the slasher,
|
|
378
|
+
// and have it register callbacks on the p2p client *before* we start it, otherwise messages
|
|
379
|
+
// like attestations or auths will fail.
|
|
380
|
+
if (validatorClient) {
|
|
381
|
+
watchers.push(validatorClient);
|
|
382
|
+
if (!options.dontStartSequencer) {
|
|
383
|
+
await validatorClient.registerHandlers();
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// If there's no validator client but alwaysReexecuteBlockProposals is enabled,
|
|
388
|
+
// create a BlockProposalHandler to reexecute block proposals for monitoring
|
|
389
|
+
if (!validatorClient && config.alwaysReexecuteBlockProposals) {
|
|
390
|
+
log.info('Setting up block proposal reexecution for monitoring');
|
|
391
|
+
createBlockProposalHandler(config, {
|
|
392
|
+
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
393
|
+
worldState: worldStateSynchronizer,
|
|
394
|
+
epochCache,
|
|
395
|
+
blockSource: archiver,
|
|
396
|
+
l1ToL2MessageSource: archiver,
|
|
397
|
+
p2pClient,
|
|
398
|
+
dateProvider,
|
|
399
|
+
telemetry,
|
|
400
|
+
}).registerForReexecution(p2pClient);
|
|
401
|
+
}
|
|
384
402
|
}
|
|
385
403
|
|
|
386
404
|
// Start world state and wait for it to sync to the archiver.
|
|
@@ -389,29 +407,33 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
389
407
|
// Start p2p. Note that it depends on world state to be running.
|
|
390
408
|
await p2pClient.start();
|
|
391
409
|
|
|
392
|
-
|
|
393
|
-
if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
|
|
394
|
-
watchers.push(validatorsSentinel);
|
|
395
|
-
}
|
|
396
|
-
|
|
410
|
+
let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
|
|
397
411
|
let epochPruneWatcher: EpochPruneWatcher | undefined;
|
|
398
|
-
if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
|
|
399
|
-
epochPruneWatcher = new EpochPruneWatcher(
|
|
400
|
-
archiver,
|
|
401
|
-
archiver,
|
|
402
|
-
epochCache,
|
|
403
|
-
p2pClient.getTxProvider(),
|
|
404
|
-
blockBuilder,
|
|
405
|
-
config,
|
|
406
|
-
);
|
|
407
|
-
watchers.push(epochPruneWatcher);
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
// We assume we want to slash for invalid attestations unless all max penalties are set to 0
|
|
411
412
|
let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
413
|
+
|
|
414
|
+
if (!proverOnly) {
|
|
415
|
+
validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
|
|
416
|
+
if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
|
|
417
|
+
watchers.push(validatorsSentinel);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
|
|
421
|
+
epochPruneWatcher = new EpochPruneWatcher(
|
|
422
|
+
archiver,
|
|
423
|
+
archiver,
|
|
424
|
+
epochCache,
|
|
425
|
+
p2pClient.getTxProvider(),
|
|
426
|
+
validatorCheckpointsBuilder,
|
|
427
|
+
config,
|
|
428
|
+
);
|
|
429
|
+
watchers.push(epochPruneWatcher);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// We assume we want to slash for invalid attestations unless all max penalties are set to 0
|
|
433
|
+
if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
|
|
434
|
+
attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
|
|
435
|
+
watchers.push(attestationsBlockWatcher);
|
|
436
|
+
}
|
|
415
437
|
}
|
|
416
438
|
|
|
417
439
|
// Start p2p-related services once the archiver has completed sync
|
|
@@ -448,27 +470,29 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
448
470
|
);
|
|
449
471
|
await slasherClient.start();
|
|
450
472
|
|
|
451
|
-
const l1TxUtils = config.
|
|
452
|
-
? await
|
|
473
|
+
const l1TxUtils = config.sequencerPublisherForwarderAddress
|
|
474
|
+
? await createForwarderL1TxUtilsFromSigners(
|
|
453
475
|
publicClient,
|
|
454
476
|
keyStoreManager!.createAllValidatorPublisherSigners(),
|
|
455
|
-
config.
|
|
477
|
+
config.sequencerPublisherForwarderAddress,
|
|
456
478
|
{ ...config, scope: 'sequencer' },
|
|
457
|
-
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
|
|
479
|
+
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
|
|
458
480
|
)
|
|
459
|
-
: await
|
|
481
|
+
: await createL1TxUtilsFromSigners(
|
|
460
482
|
publicClient,
|
|
461
483
|
keyStoreManager!.createAllValidatorPublisherSigners(),
|
|
462
484
|
{ ...config, scope: 'sequencer' },
|
|
463
|
-
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
|
|
485
|
+
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
|
|
464
486
|
);
|
|
465
487
|
|
|
466
488
|
// Create and start the sequencer client
|
|
467
489
|
const checkpointsBuilder = new CheckpointsBuilder(
|
|
468
490
|
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
|
|
491
|
+
worldStateSynchronizer,
|
|
469
492
|
archiver,
|
|
470
493
|
dateProvider,
|
|
471
494
|
telemetry,
|
|
495
|
+
debugLogStore,
|
|
472
496
|
);
|
|
473
497
|
|
|
474
498
|
sequencer = await SequencerClient.new(config, {
|
|
@@ -496,6 +520,29 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
496
520
|
log.warn(`Sequencer created but not started`);
|
|
497
521
|
}
|
|
498
522
|
|
|
523
|
+
// Create prover node subsystem if enabled
|
|
524
|
+
let proverNode: ProverNode | undefined;
|
|
525
|
+
if (config.enableProverNode) {
|
|
526
|
+
proverNode = await createProverNode(config, {
|
|
527
|
+
...deps.proverNodeDeps,
|
|
528
|
+
telemetry,
|
|
529
|
+
dateProvider,
|
|
530
|
+
archiver,
|
|
531
|
+
worldStateSynchronizer,
|
|
532
|
+
p2pClient,
|
|
533
|
+
epochCache,
|
|
534
|
+
blobClient,
|
|
535
|
+
keyStoreManager,
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
if (!options.dontStartProverNode) {
|
|
539
|
+
await proverNode.start();
|
|
540
|
+
log.info(`Prover node subsystem started`);
|
|
541
|
+
} else {
|
|
542
|
+
log.info(`Prover node subsystem created but not started`);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
499
546
|
const globalVariableBuilder = new GlobalVariableBuilder({
|
|
500
547
|
...config,
|
|
501
548
|
rollupVersion: BigInt(config.rollupVersion),
|
|
@@ -503,7 +550,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
503
550
|
slotDuration: Number(slotDuration),
|
|
504
551
|
});
|
|
505
552
|
|
|
506
|
-
|
|
553
|
+
const node = new AztecNodeService(
|
|
507
554
|
config,
|
|
508
555
|
p2pClient,
|
|
509
556
|
archiver,
|
|
@@ -512,6 +559,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
512
559
|
archiver,
|
|
513
560
|
worldStateSynchronizer,
|
|
514
561
|
sequencer,
|
|
562
|
+
proverNode,
|
|
515
563
|
slasherClient,
|
|
516
564
|
validatorsSentinel,
|
|
517
565
|
epochPruneWatcher,
|
|
@@ -524,7 +572,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
524
572
|
telemetry,
|
|
525
573
|
log,
|
|
526
574
|
blobClient,
|
|
575
|
+
validatorClient,
|
|
576
|
+
keyStoreManager,
|
|
577
|
+
debugLogStore,
|
|
527
578
|
);
|
|
579
|
+
|
|
580
|
+
return node;
|
|
528
581
|
}
|
|
529
582
|
|
|
530
583
|
/**
|
|
@@ -535,6 +588,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
535
588
|
return this.sequencer;
|
|
536
589
|
}
|
|
537
590
|
|
|
591
|
+
/** Returns the prover node subsystem, if enabled. */
|
|
592
|
+
public getProverNode(): ProverNode | undefined {
|
|
593
|
+
return this.proverNode;
|
|
594
|
+
}
|
|
595
|
+
|
|
538
596
|
public getBlockSource(): L2BlockSource {
|
|
539
597
|
return this.blockSource;
|
|
540
598
|
}
|
|
@@ -588,19 +646,26 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
588
646
|
enr,
|
|
589
647
|
l1ContractAddresses: contractAddresses,
|
|
590
648
|
protocolContractAddresses: protocolContractAddresses,
|
|
649
|
+
realProofs: !!this.config.realProofs,
|
|
591
650
|
};
|
|
592
651
|
|
|
593
652
|
return nodeInfo;
|
|
594
653
|
}
|
|
595
654
|
|
|
596
655
|
/**
|
|
597
|
-
* Get a block specified by its number.
|
|
598
|
-
* @param
|
|
656
|
+
* Get a block specified by its block number, block hash, or 'latest'.
|
|
657
|
+
* @param block - The block parameter (block number, block hash, or 'latest').
|
|
599
658
|
* @returns The requested block.
|
|
600
659
|
*/
|
|
601
|
-
public async getBlock(
|
|
602
|
-
|
|
603
|
-
|
|
660
|
+
public async getBlock(block: BlockParameter): Promise<L2Block | undefined> {
|
|
661
|
+
if (BlockHash.isBlockHash(block)) {
|
|
662
|
+
return this.getBlockByHash(block);
|
|
663
|
+
}
|
|
664
|
+
const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
|
|
665
|
+
if (blockNumber === BlockNumber.ZERO) {
|
|
666
|
+
return this.buildInitialBlock();
|
|
667
|
+
}
|
|
668
|
+
return await this.blockSource.getL2Block(blockNumber);
|
|
604
669
|
}
|
|
605
670
|
|
|
606
671
|
/**
|
|
@@ -608,9 +673,17 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
608
673
|
* @param blockHash - The block hash being requested.
|
|
609
674
|
* @returns The requested block.
|
|
610
675
|
*/
|
|
611
|
-
public async getBlockByHash(blockHash:
|
|
612
|
-
const
|
|
613
|
-
|
|
676
|
+
public async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
|
|
677
|
+
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
678
|
+
if (blockHash.equals(initialBlockHash)) {
|
|
679
|
+
return this.buildInitialBlock();
|
|
680
|
+
}
|
|
681
|
+
return await this.blockSource.getL2BlockByHash(blockHash);
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
private buildInitialBlock(): L2Block {
|
|
685
|
+
const initialHeader = this.worldStateSynchronizer.getCommitted().getInitialHeader();
|
|
686
|
+
return L2Block.empty(initialHeader);
|
|
614
687
|
}
|
|
615
688
|
|
|
616
689
|
/**
|
|
@@ -619,8 +692,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
619
692
|
* @returns The requested block.
|
|
620
693
|
*/
|
|
621
694
|
public async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
|
|
622
|
-
|
|
623
|
-
return publishedBlock?.block;
|
|
695
|
+
return await this.blockSource.getL2BlockByArchive(archive);
|
|
624
696
|
}
|
|
625
697
|
|
|
626
698
|
/**
|
|
@@ -630,19 +702,23 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
630
702
|
* @returns The blocks requested.
|
|
631
703
|
*/
|
|
632
704
|
public async getBlocks(from: BlockNumber, limit: number): Promise<L2Block[]> {
|
|
633
|
-
return (await this.blockSource.getBlocks(from, limit)) ?? [];
|
|
705
|
+
return (await this.blockSource.getBlocks(from, BlockNumber(limit))) ?? [];
|
|
634
706
|
}
|
|
635
707
|
|
|
636
|
-
public async
|
|
637
|
-
return (await this.blockSource.
|
|
708
|
+
public async getCheckpoints(from: CheckpointNumber, limit: number): Promise<PublishedCheckpoint[]> {
|
|
709
|
+
return (await this.blockSource.getCheckpoints(from, limit)) ?? [];
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
public async getCheckpointedBlocks(from: BlockNumber, limit: number) {
|
|
713
|
+
return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
|
|
638
714
|
}
|
|
639
715
|
|
|
640
716
|
/**
|
|
641
|
-
* Method to fetch the current
|
|
642
|
-
* @returns The current
|
|
717
|
+
* Method to fetch the current min L2 fees.
|
|
718
|
+
* @returns The current min L2 fees.
|
|
643
719
|
*/
|
|
644
|
-
public async
|
|
645
|
-
return await this.globalVariableBuilder.
|
|
720
|
+
public async getCurrentMinFees(): Promise<GasFees> {
|
|
721
|
+
return await this.globalVariableBuilder.getCurrentMinFees();
|
|
646
722
|
}
|
|
647
723
|
|
|
648
724
|
public async getMaxPriorityFees(): Promise<GasFees> {
|
|
@@ -665,6 +741,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
665
741
|
return await this.blockSource.getProvenBlockNumber();
|
|
666
742
|
}
|
|
667
743
|
|
|
744
|
+
public async getCheckpointedBlockNumber(): Promise<BlockNumber> {
|
|
745
|
+
return await this.blockSource.getCheckpointedL2BlockNumber();
|
|
746
|
+
}
|
|
747
|
+
|
|
668
748
|
/**
|
|
669
749
|
* Method to fetch the version of the package.
|
|
670
750
|
* @returns The node package version
|
|
@@ -697,12 +777,43 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
697
777
|
return this.contractDataSource.getContract(address);
|
|
698
778
|
}
|
|
699
779
|
|
|
700
|
-
public getPrivateLogsByTags(
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
780
|
+
public async getPrivateLogsByTags(
|
|
781
|
+
tags: SiloedTag[],
|
|
782
|
+
page?: number,
|
|
783
|
+
referenceBlock?: BlockHash,
|
|
784
|
+
): Promise<TxScopedL2Log[][]> {
|
|
785
|
+
if (referenceBlock) {
|
|
786
|
+
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
787
|
+
if (!referenceBlock.equals(initialBlockHash)) {
|
|
788
|
+
const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
|
|
789
|
+
if (!header) {
|
|
790
|
+
throw new Error(
|
|
791
|
+
`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
|
|
792
|
+
);
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
return this.logsSource.getPrivateLogsByTags(tags, page);
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
public async getPublicLogsByTagsFromContract(
|
|
800
|
+
contractAddress: AztecAddress,
|
|
801
|
+
tags: Tag[],
|
|
802
|
+
page?: number,
|
|
803
|
+
referenceBlock?: BlockHash,
|
|
804
|
+
): Promise<TxScopedL2Log[][]> {
|
|
805
|
+
if (referenceBlock) {
|
|
806
|
+
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
807
|
+
if (!referenceBlock.equals(initialBlockHash)) {
|
|
808
|
+
const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
|
|
809
|
+
if (!header) {
|
|
810
|
+
throw new Error(
|
|
811
|
+
`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
}
|
|
816
|
+
return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page);
|
|
706
817
|
}
|
|
707
818
|
|
|
708
819
|
/**
|
|
@@ -744,26 +855,36 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
744
855
|
}
|
|
745
856
|
|
|
746
857
|
await this.p2pClient!.sendTx(tx);
|
|
747
|
-
|
|
748
|
-
this.
|
|
858
|
+
const duration = timer.ms();
|
|
859
|
+
this.metrics.receivedTx(duration, true);
|
|
860
|
+
this.log.info(`Received tx ${txHash} in ${duration}ms`, { txHash });
|
|
749
861
|
}
|
|
750
862
|
|
|
751
863
|
public async getTxReceipt(txHash: TxHash): Promise<TxReceipt> {
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
// and we would incorrectly return a TxReceipt with status DROPPED
|
|
757
|
-
if ((await this.p2pClient.getTxStatus(txHash)) === 'pending') {
|
|
758
|
-
txReceipt = new TxReceipt(txHash, TxStatus.PENDING, '');
|
|
759
|
-
}
|
|
864
|
+
// Check the tx pool status first. If the tx is known to the pool (pending or mined), we'll use that
|
|
865
|
+
// as a fallback if we don't find a settled receipt in the archiver.
|
|
866
|
+
const txPoolStatus = await this.p2pClient.getTxStatus(txHash);
|
|
867
|
+
const isKnownToPool = txPoolStatus === 'pending' || txPoolStatus === 'mined';
|
|
760
868
|
|
|
869
|
+
// Then get the actual tx from the archiver, which tracks every tx in a mined block.
|
|
761
870
|
const settledTxReceipt = await this.blockSource.getSettledTxReceipt(txHash);
|
|
871
|
+
|
|
872
|
+
let receipt: TxReceipt;
|
|
762
873
|
if (settledTxReceipt) {
|
|
763
|
-
|
|
874
|
+
receipt = settledTxReceipt;
|
|
875
|
+
} else if (isKnownToPool) {
|
|
876
|
+
// If the tx is in the pool but not in the archiver, it's pending.
|
|
877
|
+
// This handles race conditions between archiver and p2p, where the archiver
|
|
878
|
+
// has pruned the block in which a tx was mined, but p2p has not caught up yet.
|
|
879
|
+
receipt = new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
|
|
880
|
+
} else {
|
|
881
|
+
// Otherwise, if we don't know the tx, we consider it dropped.
|
|
882
|
+
receipt = new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
|
|
764
883
|
}
|
|
765
884
|
|
|
766
|
-
|
|
885
|
+
this.debugLogStore.decorateReceiptWithLogs(txHash.toString(), receipt);
|
|
886
|
+
|
|
887
|
+
return receipt;
|
|
767
888
|
}
|
|
768
889
|
|
|
769
890
|
public getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
|
|
@@ -780,6 +901,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
780
901
|
await tryStop(this.slasherClient);
|
|
781
902
|
await tryStop(this.proofVerifier);
|
|
782
903
|
await tryStop(this.sequencer);
|
|
904
|
+
await tryStop(this.proverNode);
|
|
783
905
|
await tryStop(this.p2pClient);
|
|
784
906
|
await tryStop(this.worldStateSynchronizer);
|
|
785
907
|
await tryStop(this.blockSource);
|
|
@@ -788,6 +910,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
788
910
|
this.log.info(`Stopped Aztec Node`);
|
|
789
911
|
}
|
|
790
912
|
|
|
913
|
+
/**
|
|
914
|
+
* Returns the blob client used by this node.
|
|
915
|
+
* @internal - Exposed for testing purposes only.
|
|
916
|
+
*/
|
|
917
|
+
public getBlobClient(): BlobClientInterface | undefined {
|
|
918
|
+
return this.blobClient;
|
|
919
|
+
}
|
|
920
|
+
|
|
791
921
|
/**
|
|
792
922
|
* Method to retrieve pending txs.
|
|
793
923
|
* @param limit - The number of items to returns
|
|
@@ -820,20 +950,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
820
950
|
return compactArray(await Promise.all(txHashes.map(txHash => this.getTxByHash(txHash))));
|
|
821
951
|
}
|
|
822
952
|
|
|
823
|
-
/**
|
|
824
|
-
* Find the indexes of the given leaves in the given tree along with a block metadata pointing to the block in which
|
|
825
|
-
* the leaves were inserted.
|
|
826
|
-
* @param blockNumber - The block number at which to get the data or 'latest' for latest data.
|
|
827
|
-
* @param treeId - The tree to search in.
|
|
828
|
-
* @param leafValues - The values to search for.
|
|
829
|
-
* @returns The indices of leaves and the block metadata of a block in which the leaves were inserted.
|
|
830
|
-
*/
|
|
831
953
|
public async findLeavesIndexes(
|
|
832
|
-
|
|
954
|
+
referenceBlock: BlockParameter,
|
|
833
955
|
treeId: MerkleTreeId,
|
|
834
956
|
leafValues: Fr[],
|
|
835
957
|
): Promise<(DataInBlock<bigint> | undefined)[]> {
|
|
836
|
-
const committedDb = await this.#getWorldState(
|
|
958
|
+
const committedDb = await this.#getWorldState(referenceBlock);
|
|
837
959
|
const maybeIndices = await committedDb.findLeafIndices(
|
|
838
960
|
treeId,
|
|
839
961
|
leafValues.map(x => x.toBuffer()),
|
|
@@ -885,56 +1007,28 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
885
1007
|
}
|
|
886
1008
|
return {
|
|
887
1009
|
l2BlockNumber: BlockNumber(Number(blockNumber)),
|
|
888
|
-
l2BlockHash:
|
|
1010
|
+
l2BlockHash: new BlockHash(blockHash),
|
|
889
1011
|
data: index,
|
|
890
1012
|
};
|
|
891
1013
|
});
|
|
892
1014
|
}
|
|
893
1015
|
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
* @param leafIndex - The index of the leaf for which the sibling path is required.
|
|
898
|
-
* @returns The sibling path for the leaf index.
|
|
899
|
-
*/
|
|
900
|
-
public async getNullifierSiblingPath(
|
|
901
|
-
blockNumber: BlockParameter,
|
|
902
|
-
leafIndex: bigint,
|
|
903
|
-
): Promise<SiblingPath<typeof NULLIFIER_TREE_HEIGHT>> {
|
|
904
|
-
const committedDb = await this.#getWorldState(blockNumber);
|
|
905
|
-
return committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex);
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
/**
|
|
909
|
-
* Returns a sibling path for the given index in the data tree.
|
|
910
|
-
* @param blockNumber - The block number at which to get the data.
|
|
911
|
-
* @param leafIndex - The index of the leaf for which the sibling path is required.
|
|
912
|
-
* @returns The sibling path for the leaf index.
|
|
913
|
-
*/
|
|
914
|
-
public async getNoteHashSiblingPath(
|
|
915
|
-
blockNumber: BlockParameter,
|
|
916
|
-
leafIndex: bigint,
|
|
917
|
-
): Promise<SiblingPath<typeof NOTE_HASH_TREE_HEIGHT>> {
|
|
918
|
-
const committedDb = await this.#getWorldState(blockNumber);
|
|
919
|
-
return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
|
|
920
|
-
}
|
|
921
|
-
|
|
922
|
-
public async getArchiveMembershipWitness(
|
|
923
|
-
blockNumber: BlockParameter,
|
|
924
|
-
archive: Fr,
|
|
1016
|
+
public async getBlockHashMembershipWitness(
|
|
1017
|
+
referenceBlock: BlockParameter,
|
|
1018
|
+
blockHash: BlockHash,
|
|
925
1019
|
): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
|
|
926
|
-
const committedDb = await this.#getWorldState(
|
|
927
|
-
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [
|
|
1020
|
+
const committedDb = await this.#getWorldState(referenceBlock);
|
|
1021
|
+
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [blockHash]);
|
|
928
1022
|
return pathAndIndex === undefined
|
|
929
1023
|
? undefined
|
|
930
1024
|
: MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
|
|
931
1025
|
}
|
|
932
1026
|
|
|
933
1027
|
public async getNoteHashMembershipWitness(
|
|
934
|
-
|
|
1028
|
+
referenceBlock: BlockParameter,
|
|
935
1029
|
noteHash: Fr,
|
|
936
1030
|
): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
|
|
937
|
-
const committedDb = await this.#getWorldState(
|
|
1031
|
+
const committedDb = await this.#getWorldState(referenceBlock);
|
|
938
1032
|
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
|
|
939
1033
|
MerkleTreeId.NOTE_HASH_TREE,
|
|
940
1034
|
[noteHash],
|
|
@@ -944,17 +1038,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
944
1038
|
: MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
|
|
945
1039
|
}
|
|
946
1040
|
|
|
947
|
-
/**
|
|
948
|
-
* Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree.
|
|
949
|
-
* @param blockNumber - The block number at which to get the data.
|
|
950
|
-
* @param l1ToL2Message - The l1ToL2Message to get the index / sibling path for.
|
|
951
|
-
* @returns A tuple of the index and the sibling path of the L1ToL2Message (undefined if not found).
|
|
952
|
-
*/
|
|
953
1041
|
public async getL1ToL2MessageMembershipWitness(
|
|
954
|
-
|
|
1042
|
+
referenceBlock: BlockParameter,
|
|
955
1043
|
l1ToL2Message: Fr,
|
|
956
1044
|
): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
|
|
957
|
-
const db = await this.#getWorldState(
|
|
1045
|
+
const db = await this.#getWorldState(referenceBlock);
|
|
958
1046
|
const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
|
|
959
1047
|
if (!witness) {
|
|
960
1048
|
return undefined;
|
|
@@ -982,56 +1070,36 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
982
1070
|
}
|
|
983
1071
|
|
|
984
1072
|
/**
|
|
985
|
-
* Returns all the L2 to L1 messages in
|
|
986
|
-
* @param
|
|
987
|
-
* @returns The L2 to L1 messages (
|
|
1073
|
+
* Returns all the L2 to L1 messages in an epoch.
|
|
1074
|
+
* @param epoch - The epoch at which to get the data.
|
|
1075
|
+
* @returns The L2 to L1 messages (empty array if the epoch is not found).
|
|
988
1076
|
*/
|
|
989
|
-
public async getL2ToL1Messages(
|
|
990
|
-
|
|
991
|
-
|
|
1077
|
+
public async getL2ToL1Messages(epoch: EpochNumber): Promise<Fr[][][][]> {
|
|
1078
|
+
// Assumes `getCheckpointedBlocksForEpoch` returns blocks in ascending order of block number.
|
|
1079
|
+
const checkpointedBlocks = await this.blockSource.getCheckpointedBlocksForEpoch(epoch);
|
|
1080
|
+
const blocksInCheckpoints: L2Block[][] = [];
|
|
1081
|
+
let previousSlotNumber = SlotNumber.ZERO;
|
|
1082
|
+
let checkpointIndex = -1;
|
|
1083
|
+
for (const checkpointedBlock of checkpointedBlocks) {
|
|
1084
|
+
const block = checkpointedBlock.block;
|
|
1085
|
+
const slotNumber = block.header.globalVariables.slotNumber;
|
|
1086
|
+
if (slotNumber !== previousSlotNumber) {
|
|
1087
|
+
checkpointIndex++;
|
|
1088
|
+
blocksInCheckpoints.push([]);
|
|
1089
|
+
previousSlotNumber = slotNumber;
|
|
1090
|
+
}
|
|
1091
|
+
blocksInCheckpoints[checkpointIndex].push(block);
|
|
1092
|
+
}
|
|
1093
|
+
return blocksInCheckpoints.map(blocks =>
|
|
1094
|
+
blocks.map(block => block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs)),
|
|
992
1095
|
);
|
|
993
|
-
return block?.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
|
|
994
|
-
}
|
|
995
|
-
|
|
996
|
-
/**
|
|
997
|
-
* Returns a sibling path for a leaf in the committed blocks tree.
|
|
998
|
-
* @param blockNumber - The block number at which to get the data.
|
|
999
|
-
* @param leafIndex - Index of the leaf in the tree.
|
|
1000
|
-
* @returns The sibling path.
|
|
1001
|
-
*/
|
|
1002
|
-
public async getArchiveSiblingPath(
|
|
1003
|
-
blockNumber: BlockParameter,
|
|
1004
|
-
leafIndex: bigint,
|
|
1005
|
-
): Promise<SiblingPath<typeof ARCHIVE_HEIGHT>> {
|
|
1006
|
-
const committedDb = await this.#getWorldState(blockNumber);
|
|
1007
|
-
return committedDb.getSiblingPath(MerkleTreeId.ARCHIVE, leafIndex);
|
|
1008
1096
|
}
|
|
1009
1097
|
|
|
1010
|
-
/**
|
|
1011
|
-
* Returns a sibling path for a leaf in the committed public data tree.
|
|
1012
|
-
* @param blockNumber - The block number at which to get the data.
|
|
1013
|
-
* @param leafIndex - Index of the leaf in the tree.
|
|
1014
|
-
* @returns The sibling path.
|
|
1015
|
-
*/
|
|
1016
|
-
public async getPublicDataSiblingPath(
|
|
1017
|
-
blockNumber: BlockParameter,
|
|
1018
|
-
leafIndex: bigint,
|
|
1019
|
-
): Promise<SiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>> {
|
|
1020
|
-
const committedDb = await this.#getWorldState(blockNumber);
|
|
1021
|
-
return committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
|
|
1022
|
-
}
|
|
1023
|
-
|
|
1024
|
-
/**
|
|
1025
|
-
* Returns a nullifier membership witness for a given nullifier at a given block.
|
|
1026
|
-
* @param blockNumber - The block number at which to get the index.
|
|
1027
|
-
* @param nullifier - Nullifier we try to find witness for.
|
|
1028
|
-
* @returns The nullifier membership witness (if found).
|
|
1029
|
-
*/
|
|
1030
1098
|
public async getNullifierMembershipWitness(
|
|
1031
|
-
|
|
1099
|
+
referenceBlock: BlockParameter,
|
|
1032
1100
|
nullifier: Fr,
|
|
1033
1101
|
): Promise<NullifierMembershipWitness | undefined> {
|
|
1034
|
-
const db = await this.#getWorldState(
|
|
1102
|
+
const db = await this.#getWorldState(referenceBlock);
|
|
1035
1103
|
const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
|
|
1036
1104
|
if (!witness) {
|
|
1037
1105
|
return undefined;
|
|
@@ -1048,7 +1116,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1048
1116
|
|
|
1049
1117
|
/**
|
|
1050
1118
|
* Returns a low nullifier membership witness for a given nullifier at a given block.
|
|
1051
|
-
* @param
|
|
1119
|
+
* @param referenceBlock - The block parameter (block number, block hash, or 'latest') at which to get the data
|
|
1120
|
+
* (which contains the root of the nullifier tree in which we are searching for the nullifier).
|
|
1052
1121
|
* @param nullifier - Nullifier we try to find the low nullifier witness for.
|
|
1053
1122
|
* @returns The low nullifier membership witness (if found).
|
|
1054
1123
|
* @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
|
|
@@ -1061,10 +1130,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1061
1130
|
* TODO: This is a confusing behavior and we should eventually address that.
|
|
1062
1131
|
*/
|
|
1063
1132
|
public async getLowNullifierMembershipWitness(
|
|
1064
|
-
|
|
1133
|
+
referenceBlock: BlockParameter,
|
|
1065
1134
|
nullifier: Fr,
|
|
1066
1135
|
): Promise<NullifierMembershipWitness | undefined> {
|
|
1067
|
-
const committedDb = await this.#getWorldState(
|
|
1136
|
+
const committedDb = await this.#getWorldState(referenceBlock);
|
|
1068
1137
|
const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
|
|
1069
1138
|
if (!findResult) {
|
|
1070
1139
|
return undefined;
|
|
@@ -1079,8 +1148,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1079
1148
|
return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
|
|
1080
1149
|
}
|
|
1081
1150
|
|
|
1082
|
-
async getPublicDataWitness(
|
|
1083
|
-
const committedDb = await this.#getWorldState(
|
|
1151
|
+
async getPublicDataWitness(referenceBlock: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
|
|
1152
|
+
const committedDb = await this.#getWorldState(referenceBlock);
|
|
1084
1153
|
const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
|
|
1085
1154
|
if (!lowLeafResult) {
|
|
1086
1155
|
return undefined;
|
|
@@ -1094,19 +1163,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1094
1163
|
}
|
|
1095
1164
|
}
|
|
1096
1165
|
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
*
|
|
1100
|
-
* @remarks The storage slot here refers to the slot as it is defined in Noir not the index in the merkle tree.
|
|
1101
|
-
* Aztec's version of `eth_getStorageAt`.
|
|
1102
|
-
*
|
|
1103
|
-
* @param contract - Address of the contract to query.
|
|
1104
|
-
* @param slot - Slot to query.
|
|
1105
|
-
* @param blockNumber - The block number at which to get the data or 'latest'.
|
|
1106
|
-
* @returns Storage value at the given contract slot.
|
|
1107
|
-
*/
|
|
1108
|
-
public async getPublicStorageAt(blockNumber: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
|
|
1109
|
-
const committedDb = await this.#getWorldState(blockNumber);
|
|
1166
|
+
public async getPublicStorageAt(referenceBlock: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
|
|
1167
|
+
const committedDb = await this.#getWorldState(referenceBlock);
|
|
1110
1168
|
const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
|
|
1111
1169
|
|
|
1112
1170
|
const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
|
|
@@ -1120,24 +1178,22 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1120
1178
|
return preimage.leaf.value;
|
|
1121
1179
|
}
|
|
1122
1180
|
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
public async getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
|
|
1140
|
-
return await this.blockSource.getBlockHeaderByHash(blockHash);
|
|
1181
|
+
public async getBlockHeader(block: BlockParameter = 'latest'): Promise<BlockHeader | undefined> {
|
|
1182
|
+
if (BlockHash.isBlockHash(block)) {
|
|
1183
|
+
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
1184
|
+
if (block.equals(initialBlockHash)) {
|
|
1185
|
+
// Block source doesn't handle initial header so we need to handle the case separately.
|
|
1186
|
+
return this.worldStateSynchronizer.getCommitted().getInitialHeader();
|
|
1187
|
+
}
|
|
1188
|
+
return this.blockSource.getBlockHeaderByHash(block);
|
|
1189
|
+
} else {
|
|
1190
|
+
// Block source doesn't handle initial header so we need to handle the case separately.
|
|
1191
|
+
const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
|
|
1192
|
+
if (blockNumber === BlockNumber.ZERO) {
|
|
1193
|
+
return this.worldStateSynchronizer.getCommitted().getInitialHeader();
|
|
1194
|
+
}
|
|
1195
|
+
return this.blockSource.getBlockHeader(block);
|
|
1196
|
+
}
|
|
1141
1197
|
}
|
|
1142
1198
|
|
|
1143
1199
|
/**
|
|
@@ -1149,6 +1205,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1149
1205
|
return await this.blockSource.getBlockHeaderByArchive(archive);
|
|
1150
1206
|
}
|
|
1151
1207
|
|
|
1208
|
+
public getBlockData(number: BlockNumber): Promise<BlockData | undefined> {
|
|
1209
|
+
return this.blockSource.getBlockData(number);
|
|
1210
|
+
}
|
|
1211
|
+
|
|
1212
|
+
public getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
|
|
1213
|
+
return this.blockSource.getBlockDataByArchive(archive);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1152
1216
|
/**
|
|
1153
1217
|
* Simulates the public part of a transaction with the current state.
|
|
1154
1218
|
* @param tx - The transaction to simulate.
|
|
@@ -1172,7 +1236,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1172
1236
|
}
|
|
1173
1237
|
|
|
1174
1238
|
const txHash = tx.getTxHash();
|
|
1175
|
-
const
|
|
1239
|
+
const latestBlockNumber = await this.blockSource.getBlockNumber();
|
|
1240
|
+
const blockNumber = BlockNumber.add(latestBlockNumber, 1);
|
|
1176
1241
|
|
|
1177
1242
|
// If sequencer is not initialized, we just set these values to zero for simulation.
|
|
1178
1243
|
const coinbase = EthAddress.ZERO;
|
|
@@ -1187,6 +1252,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1187
1252
|
this.contractDataSource,
|
|
1188
1253
|
new DateProvider(),
|
|
1189
1254
|
this.telemetry,
|
|
1255
|
+
this.log.getBindings(),
|
|
1190
1256
|
);
|
|
1191
1257
|
|
|
1192
1258
|
this.log.verbose(`Simulating public calls for tx ${txHash}`, {
|
|
@@ -1195,6 +1261,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1195
1261
|
blockNumber,
|
|
1196
1262
|
});
|
|
1197
1263
|
|
|
1264
|
+
// Ensure world-state has caught up with the latest block we loaded from the archiver
|
|
1265
|
+
await this.worldStateSynchronizer.syncImmediate(latestBlockNumber);
|
|
1198
1266
|
const merkleTreeFork = await this.worldStateSynchronizer.fork();
|
|
1199
1267
|
try {
|
|
1200
1268
|
const config = PublicSimulatorConfig.from({
|
|
@@ -1210,7 +1278,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1210
1278
|
const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
|
|
1211
1279
|
|
|
1212
1280
|
// REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
|
|
1213
|
-
const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([tx]);
|
|
1281
|
+
const [processedTxs, failedTxs, _usedTxs, returns, _blobFields, debugLogs] = await processor.process([tx]);
|
|
1214
1282
|
// REFACTOR: Consider returning the error rather than throwing
|
|
1215
1283
|
if (failedTxs.length) {
|
|
1216
1284
|
this.log.warn(`Simulated tx ${txHash} fails: ${failedTxs[0].error}`, { txHash });
|
|
@@ -1224,6 +1292,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1224
1292
|
processedTx.txEffect,
|
|
1225
1293
|
returns,
|
|
1226
1294
|
processedTx.gasUsed,
|
|
1295
|
+
debugLogs,
|
|
1227
1296
|
);
|
|
1228
1297
|
} finally {
|
|
1229
1298
|
await merkleTreeFork.close();
|
|
@@ -1237,19 +1306,25 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1237
1306
|
const db = this.worldStateSynchronizer.getCommitted();
|
|
1238
1307
|
const verifier = isSimulation ? undefined : this.proofVerifier;
|
|
1239
1308
|
|
|
1240
|
-
// We accept transactions if they are not expired by the next slot (checked based on the
|
|
1309
|
+
// We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
|
|
1241
1310
|
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1242
1311
|
const blockNumber = BlockNumber((await this.blockSource.getBlockNumber()) + 1);
|
|
1243
|
-
const validator =
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1312
|
+
const validator = createTxValidatorForAcceptingTxsOverRPC(
|
|
1313
|
+
db,
|
|
1314
|
+
this.contractDataSource,
|
|
1315
|
+
verifier,
|
|
1316
|
+
{
|
|
1317
|
+
timestamp: nextSlotTimestamp,
|
|
1318
|
+
blockNumber,
|
|
1319
|
+
l1ChainId: this.l1ChainId,
|
|
1320
|
+
rollupVersion: this.version,
|
|
1321
|
+
setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()),
|
|
1322
|
+
gasFees: await this.getCurrentMinFees(),
|
|
1323
|
+
skipFeeEnforcement,
|
|
1324
|
+
txsPermitted: !this.config.disableTransactions,
|
|
1325
|
+
},
|
|
1326
|
+
this.log.getBindings(),
|
|
1327
|
+
);
|
|
1253
1328
|
|
|
1254
1329
|
return await validator.validateTx(tx);
|
|
1255
1330
|
}
|
|
@@ -1318,7 +1393,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1318
1393
|
}
|
|
1319
1394
|
|
|
1320
1395
|
// And it has an L2 block hash
|
|
1321
|
-
const l2BlockHash = await archiver.getL2Tips().then(tips => tips.
|
|
1396
|
+
const l2BlockHash = await archiver.getL2Tips().then(tips => tips.proposed.hash);
|
|
1322
1397
|
if (!l2BlockHash) {
|
|
1323
1398
|
this.metrics.recordSnapshotError();
|
|
1324
1399
|
throw new Error(`Archiver has no latest L2 block hash downloaded. Cannot start snapshot.`);
|
|
@@ -1352,7 +1427,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1352
1427
|
throw new Error('Archiver implementation does not support rollbacks.');
|
|
1353
1428
|
}
|
|
1354
1429
|
|
|
1355
|
-
const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.number);
|
|
1430
|
+
const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.block.number);
|
|
1356
1431
|
if (targetBlock < finalizedBlock) {
|
|
1357
1432
|
if (force) {
|
|
1358
1433
|
this.log.warn(`Clearing world state database to allow rolling back behind finalized block ${finalizedBlock}`);
|
|
@@ -1413,16 +1488,107 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1413
1488
|
}
|
|
1414
1489
|
}
|
|
1415
1490
|
|
|
1491
|
+
public async reloadKeystore(): Promise<void> {
|
|
1492
|
+
if (!this.config.keyStoreDirectory?.length) {
|
|
1493
|
+
throw new BadRequestError(
|
|
1494
|
+
'Cannot reload keystore: node is not using a file-based keystore. ' +
|
|
1495
|
+
'Set KEY_STORE_DIRECTORY to use file-based keystores.',
|
|
1496
|
+
);
|
|
1497
|
+
}
|
|
1498
|
+
if (!this.validatorClient) {
|
|
1499
|
+
throw new BadRequestError('Cannot reload keystore: validator is not enabled.');
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
this.log.info('Reloading keystore from disk');
|
|
1503
|
+
|
|
1504
|
+
// Re-read and validate keystore files
|
|
1505
|
+
const keyStores = loadKeystores(this.config.keyStoreDirectory);
|
|
1506
|
+
const newManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
1507
|
+
await newManager.validateSigners();
|
|
1508
|
+
ValidatorClient.validateKeyStoreConfiguration(newManager, this.log);
|
|
1509
|
+
|
|
1510
|
+
// Validate that every validator's publisher keys overlap with the L1 signers
|
|
1511
|
+
// that were initialized at startup. Publishers cannot be hot-reloaded, so a
|
|
1512
|
+
// validator with a publisher key that doesn't match any existing L1 signer
|
|
1513
|
+
// would silently fail on every proposer slot.
|
|
1514
|
+
if (this.keyStoreManager && this.sequencer) {
|
|
1515
|
+
const oldAdapter = NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager);
|
|
1516
|
+
const availablePublishers = new Set(
|
|
1517
|
+
oldAdapter
|
|
1518
|
+
.getAttesterAddresses()
|
|
1519
|
+
.flatMap(a => oldAdapter.getPublisherAddresses(a).map(p => p.toString().toLowerCase())),
|
|
1520
|
+
);
|
|
1521
|
+
|
|
1522
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
1523
|
+
for (const attester of newAdapter.getAttesterAddresses()) {
|
|
1524
|
+
const pubs = newAdapter.getPublisherAddresses(attester);
|
|
1525
|
+
if (pubs.length > 0 && !pubs.some(p => availablePublishers.has(p.toString().toLowerCase()))) {
|
|
1526
|
+
throw new BadRequestError(
|
|
1527
|
+
`Cannot reload keystore: validator ${attester} has publisher keys ` +
|
|
1528
|
+
`[${pubs.map(p => p.toString()).join(', ')}] but none match the L1 signers initialized at startup ` +
|
|
1529
|
+
`[${[...availablePublishers].join(', ')}]. Publishers cannot be hot-reloaded — ` +
|
|
1530
|
+
`use an existing publisher key or restart the node.`,
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// Build adapters for old and new keystores to compute diff
|
|
1537
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
1538
|
+
const newAddresses = newAdapter.getAttesterAddresses();
|
|
1539
|
+
const oldAddresses = this.keyStoreManager
|
|
1540
|
+
? NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager).getAttesterAddresses()
|
|
1541
|
+
: [];
|
|
1542
|
+
|
|
1543
|
+
const oldSet = new Set(oldAddresses.map(a => a.toString()));
|
|
1544
|
+
const newSet = new Set(newAddresses.map(a => a.toString()));
|
|
1545
|
+
const added = newAddresses.filter(a => !oldSet.has(a.toString()));
|
|
1546
|
+
const removed = oldAddresses.filter(a => !newSet.has(a.toString()));
|
|
1547
|
+
|
|
1548
|
+
if (added.length > 0) {
|
|
1549
|
+
this.log.info(`Keystore reload: adding attester keys: ${added.map(a => a.toString()).join(', ')}`);
|
|
1550
|
+
}
|
|
1551
|
+
if (removed.length > 0) {
|
|
1552
|
+
this.log.info(`Keystore reload: removing attester keys: ${removed.map(a => a.toString()).join(', ')}`);
|
|
1553
|
+
}
|
|
1554
|
+
if (added.length === 0 && removed.length === 0) {
|
|
1555
|
+
this.log.info('Keystore reload: attester keys unchanged');
|
|
1556
|
+
}
|
|
1557
|
+
|
|
1558
|
+
// Update the validator client (coinbase, feeRecipient, attester keys)
|
|
1559
|
+
this.validatorClient.reloadKeystore(newManager);
|
|
1560
|
+
|
|
1561
|
+
// Update the publisher factory's keystore so newly-added validators
|
|
1562
|
+
// can be matched to existing publisher keys when proposing blocks.
|
|
1563
|
+
if (this.sequencer) {
|
|
1564
|
+
this.sequencer.updatePublisherNodeKeyStore(newAdapter);
|
|
1565
|
+
}
|
|
1566
|
+
|
|
1567
|
+
// Update slasher's "don't-slash-self" list with new validator addresses
|
|
1568
|
+
if (this.slasherClient && !this.config.slashSelfAllowed) {
|
|
1569
|
+
const slashValidatorsNever = unique(
|
|
1570
|
+
[...(this.config.slashValidatorsNever ?? []), ...newAddresses].map(a => a.toString()),
|
|
1571
|
+
).map(EthAddress.fromString);
|
|
1572
|
+
this.slasherClient.updateConfig({ slashValidatorsNever });
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
this.keyStoreManager = newManager;
|
|
1576
|
+
this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
|
|
1577
|
+
}
|
|
1578
|
+
|
|
1579
|
+
#getInitialHeaderHash(): Promise<BlockHash> {
|
|
1580
|
+
if (!this.initialHeaderHashPromise) {
|
|
1581
|
+
this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
|
|
1582
|
+
}
|
|
1583
|
+
return this.initialHeaderHashPromise;
|
|
1584
|
+
}
|
|
1585
|
+
|
|
1416
1586
|
/**
|
|
1417
1587
|
* Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
|
|
1418
|
-
* @param
|
|
1588
|
+
* @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
|
|
1419
1589
|
* @returns An instance of a committed MerkleTreeOperations
|
|
1420
1590
|
*/
|
|
1421
|
-
async #getWorldState(
|
|
1422
|
-
if (typeof blockNumber === 'number' && blockNumber < INITIAL_L2_BLOCK_NUM - 1) {
|
|
1423
|
-
throw new Error('Invalid block number to get world state for: ' + blockNumber);
|
|
1424
|
-
}
|
|
1425
|
-
|
|
1591
|
+
async #getWorldState(block: BlockParameter) {
|
|
1426
1592
|
let blockSyncedTo: BlockNumber = BlockNumber.ZERO;
|
|
1427
1593
|
try {
|
|
1428
1594
|
// Attempt to sync the world state if necessary
|
|
@@ -1431,15 +1597,39 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1431
1597
|
this.log.error(`Error getting world state: ${err}`);
|
|
1432
1598
|
}
|
|
1433
1599
|
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
this.log.debug(`Using committed db for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
|
|
1600
|
+
if (block === 'latest') {
|
|
1601
|
+
this.log.debug(`Using committed db for block 'latest', world state synced upto ${blockSyncedTo}`);
|
|
1437
1602
|
return this.worldStateSynchronizer.getCommitted();
|
|
1438
|
-
}
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
if (BlockHash.isBlockHash(block)) {
|
|
1606
|
+
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
1607
|
+
if (block.equals(initialBlockHash)) {
|
|
1608
|
+
// Block source doesn't handle initial header so we need to handle the case separately.
|
|
1609
|
+
return this.worldStateSynchronizer.getSnapshot(BlockNumber.ZERO);
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
const header = await this.blockSource.getBlockHeaderByHash(block);
|
|
1613
|
+
if (!header) {
|
|
1614
|
+
throw new Error(
|
|
1615
|
+
`Block hash ${block.toString()} not found when querying world state. If the node API has been queried with anchor block hash possibly a reorg has occurred.`,
|
|
1616
|
+
);
|
|
1617
|
+
}
|
|
1618
|
+
const blockNumber = header.getBlockNumber();
|
|
1439
1619
|
this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
|
|
1440
|
-
return this.worldStateSynchronizer.getSnapshot(blockNumber
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1620
|
+
return this.worldStateSynchronizer.getSnapshot(blockNumber);
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// Block number provided
|
|
1624
|
+
{
|
|
1625
|
+
const blockNumber = block as BlockNumber;
|
|
1626
|
+
|
|
1627
|
+
if (blockNumber > blockSyncedTo) {
|
|
1628
|
+
throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
|
|
1632
|
+
return this.worldStateSynchronizer.getSnapshot(blockNumber);
|
|
1443
1633
|
}
|
|
1444
1634
|
}
|
|
1445
1635
|
|