@aztec/aztec-node 0.0.1-commit.c2595eba → 0.0.1-commit.c2eed6949
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 +8 -5
- package/dest/aztec-node/config.d.ts.map +1 -1
- package/dest/aztec-node/config.js +11 -3
- package/dest/aztec-node/server.d.ts +45 -35
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +425 -201
- package/dest/sentinel/factory.d.ts +2 -2
- package/dest/sentinel/factory.d.ts.map +1 -1
- package/dest/sentinel/sentinel.d.ts +2 -2
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +61 -35
- 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 +26 -25
- package/src/aztec-node/config.ts +25 -9
- package/src/aztec-node/server.ts +494 -241
- package/src/sentinel/factory.ts +1 -1
- package/src/sentinel/sentinel.ts +64 -31
- package/src/sentinel/store.ts +12 -12
package/src/aztec-node/server.ts
CHANGED
|
@@ -1,20 +1,17 @@
|
|
|
1
1
|
import { Archiver, createArchiver } from '@aztec/archiver';
|
|
2
|
-
import { BBCircuitVerifier,
|
|
2
|
+
import { BBCircuitVerifier, BatchChonkVerifier, QueuedIVCVerifier } from '@aztec/bb-prover';
|
|
3
|
+
import { TestCircuitVerifier } from '@aztec/bb-prover/test';
|
|
3
4
|
import { type BlobClientInterface, createBlobClientWithFileStores } from '@aztec/blob-client/client';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
type L1_TO_L2_MSG_TREE_HEIGHT,
|
|
7
|
-
type NOTE_HASH_TREE_HEIGHT,
|
|
8
|
-
type NULLIFIER_TREE_HEIGHT,
|
|
9
|
-
type PUBLIC_DATA_TREE_HEIGHT,
|
|
10
|
-
} from '@aztec/constants';
|
|
5
|
+
import { Blob } from '@aztec/blob-lib';
|
|
6
|
+
import { ARCHIVE_HEIGHT, type L1_TO_L2_MSG_TREE_HEIGHT, type NOTE_HASH_TREE_HEIGHT } from '@aztec/constants';
|
|
11
7
|
import { EpochCache, type EpochCacheInterface } from '@aztec/epoch-cache';
|
|
12
8
|
import { createEthereumChain } from '@aztec/ethereum/chain';
|
|
13
|
-
import { getPublicClient } from '@aztec/ethereum/client';
|
|
9
|
+
import { getPublicClient, makeL1HttpTransport } from '@aztec/ethereum/client';
|
|
14
10
|
import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
15
11
|
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
|
|
12
|
+
import type { L1TxUtils } from '@aztec/ethereum/l1-tx-utils';
|
|
16
13
|
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
17
|
-
import { compactArray, pick } from '@aztec/foundation/collection';
|
|
14
|
+
import { chunkBy, compactArray, pick, unique } from '@aztec/foundation/collection';
|
|
18
15
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
19
16
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
20
17
|
import { BadRequestError } from '@aztec/foundation/json-rpc';
|
|
@@ -22,14 +19,19 @@ import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
|
22
19
|
import { count } from '@aztec/foundation/string';
|
|
23
20
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
24
21
|
import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
|
|
25
|
-
import { KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
|
|
22
|
+
import { type KeyStore, KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
|
|
26
23
|
import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
|
|
24
|
+
import { createForwarderL1TxUtilsFromSigners, createL1TxUtilsFromSigners } from '@aztec/node-lib/factories';
|
|
27
25
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
26
|
+
type P2P,
|
|
27
|
+
type P2PClientDeps,
|
|
28
|
+
createP2PClient,
|
|
29
|
+
createTxValidatorForAcceptingTxsOverRPC,
|
|
30
|
+
getDefaultAllowedSetupFunctions,
|
|
31
|
+
} from '@aztec/p2p';
|
|
32
32
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
33
|
+
import { type ProverNode, type ProverNodeDeps, createProverNode } from '@aztec/prover-node';
|
|
34
|
+
import { createKeyStoreForProver } from '@aztec/prover-node/config';
|
|
33
35
|
import { GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client';
|
|
34
36
|
import { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
35
37
|
import {
|
|
@@ -41,7 +43,14 @@ import {
|
|
|
41
43
|
} from '@aztec/slasher';
|
|
42
44
|
import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
|
|
43
45
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
44
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
type BlockData,
|
|
48
|
+
BlockHash,
|
|
49
|
+
type BlockParameter,
|
|
50
|
+
type DataInBlock,
|
|
51
|
+
L2Block,
|
|
52
|
+
type L2BlockSource,
|
|
53
|
+
} from '@aztec/stdlib/block';
|
|
45
54
|
import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
46
55
|
import type {
|
|
47
56
|
ContractClassPublic,
|
|
@@ -69,9 +78,9 @@ import {
|
|
|
69
78
|
type WorldStateSynchronizer,
|
|
70
79
|
tryStop,
|
|
71
80
|
} from '@aztec/stdlib/interfaces/server';
|
|
72
|
-
import type { LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
|
|
81
|
+
import type { DebugLogStore, LogFilter, SiloedTag, Tag, TxScopedL2Log } from '@aztec/stdlib/logs';
|
|
82
|
+
import { InMemoryDebugLogStore, NullDebugLogStore } from '@aztec/stdlib/logs';
|
|
73
83
|
import { InboxLeaf, type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
74
|
-
import { P2PClientType } from '@aztec/stdlib/p2p';
|
|
75
84
|
import type { Offense, SlashPayloadRound } from '@aztec/stdlib/slashing';
|
|
76
85
|
import type { NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
|
|
77
86
|
import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
|
|
@@ -103,11 +112,11 @@ import {
|
|
|
103
112
|
ValidatorClient,
|
|
104
113
|
createBlockProposalHandler,
|
|
105
114
|
createValidatorClient,
|
|
106
|
-
createValidatorForAcceptingTxs,
|
|
107
115
|
} from '@aztec/validator-client';
|
|
116
|
+
import type { SlashingProtectionDatabase } from '@aztec/validator-ha-signer/types';
|
|
108
117
|
import { createWorldStateSynchronizer } from '@aztec/world-state';
|
|
109
118
|
|
|
110
|
-
import { createPublicClient
|
|
119
|
+
import { createPublicClient } from 'viem';
|
|
111
120
|
|
|
112
121
|
import { createSentinel } from '../sentinel/factory.js';
|
|
113
122
|
import { Sentinel } from '../sentinel/sentinel.js';
|
|
@@ -135,6 +144,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
135
144
|
protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
|
|
136
145
|
protected readonly worldStateSynchronizer: WorldStateSynchronizer,
|
|
137
146
|
protected readonly sequencer: SequencerClient | undefined,
|
|
147
|
+
protected readonly proverNode: ProverNode | undefined,
|
|
138
148
|
protected readonly slasherClient: SlasherClientInterface | undefined,
|
|
139
149
|
protected readonly validatorsSentinel: Sentinel | undefined,
|
|
140
150
|
protected readonly epochPruneWatcher: EpochPruneWatcher | undefined,
|
|
@@ -143,16 +153,32 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
143
153
|
protected readonly globalVariableBuilder: GlobalVariableBuilderInterface,
|
|
144
154
|
protected readonly epochCache: EpochCacheInterface,
|
|
145
155
|
protected readonly packageVersion: string,
|
|
146
|
-
private
|
|
156
|
+
private peerProofVerifier: ClientProtocolCircuitVerifier,
|
|
157
|
+
private rpcProofVerifier: ClientProtocolCircuitVerifier,
|
|
147
158
|
private telemetry: TelemetryClient = getTelemetryClient(),
|
|
148
159
|
private log = createLogger('node'),
|
|
149
160
|
private blobClient?: BlobClientInterface,
|
|
161
|
+
private validatorClient?: ValidatorClient,
|
|
162
|
+
private keyStoreManager?: KeystoreManager,
|
|
163
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
150
164
|
) {
|
|
151
165
|
this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
|
|
152
166
|
this.tracer = telemetry.getTracer('AztecNodeService');
|
|
153
167
|
|
|
154
168
|
this.log.info(`Aztec Node version: ${this.packageVersion}`);
|
|
155
169
|
this.log.info(`Aztec Node started on chain 0x${l1ChainId.toString(16)}`, config.l1Contracts);
|
|
170
|
+
|
|
171
|
+
// A defensive check that protects us against introducing a bug in the complex `createAndSync` function. We must
|
|
172
|
+
// never have debugLogStore enabled when not in test mode because then we would be accumulating debug logs in
|
|
173
|
+
// memory which could be a DoS vector on the sequencer (since no fees are paid for debug logs).
|
|
174
|
+
if (debugLogStore.isEnabled && config.realProofs) {
|
|
175
|
+
throw new Error('debugLogStore should never be enabled when realProofs are set');
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** @internal Exposed for testing — returns the RPC proof verifier. */
|
|
180
|
+
public getProofVerifier(): ClientProtocolCircuitVerifier {
|
|
181
|
+
return this.rpcProofVerifier;
|
|
156
182
|
}
|
|
157
183
|
|
|
158
184
|
public async getWorldStateSyncStatus(): Promise<WorldStateSyncStatus> {
|
|
@@ -176,11 +202,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
176
202
|
logger?: Logger;
|
|
177
203
|
publisher?: SequencerPublisher;
|
|
178
204
|
dateProvider?: DateProvider;
|
|
179
|
-
p2pClientDeps?: P2PClientDeps
|
|
205
|
+
p2pClientDeps?: P2PClientDeps;
|
|
206
|
+
proverNodeDeps?: Partial<ProverNodeDeps>;
|
|
207
|
+
slashingProtectionDb?: SlashingProtectionDatabase;
|
|
180
208
|
} = {},
|
|
181
209
|
options: {
|
|
182
210
|
prefilledPublicData?: PublicDataTreeLeaf[];
|
|
183
211
|
dontStartSequencer?: boolean;
|
|
212
|
+
dontStartProverNode?: boolean;
|
|
184
213
|
} = {},
|
|
185
214
|
): Promise<AztecNodeService> {
|
|
186
215
|
const config = { ...inputConfig }; // Copy the config so we dont mutate the input object
|
|
@@ -190,16 +219,29 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
190
219
|
const dateProvider = deps.dateProvider ?? new DateProvider();
|
|
191
220
|
const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
192
221
|
|
|
193
|
-
// Build a key store from file if given or from environment otherwise
|
|
222
|
+
// Build a key store from file if given or from environment otherwise.
|
|
223
|
+
// We keep the raw KeyStore available so we can merge with prover keys if enableProverNode is set.
|
|
194
224
|
let keyStoreManager: KeystoreManager | undefined;
|
|
195
225
|
const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
|
|
196
226
|
if (keyStoreProvided) {
|
|
197
227
|
const keyStores = loadKeystores(config.keyStoreDirectory!);
|
|
198
228
|
keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
199
229
|
} else {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
230
|
+
const rawKeyStores: KeyStore[] = [];
|
|
231
|
+
const validatorKeyStore = createKeyStoreForValidator(config);
|
|
232
|
+
if (validatorKeyStore) {
|
|
233
|
+
rawKeyStores.push(validatorKeyStore);
|
|
234
|
+
}
|
|
235
|
+
if (config.enableProverNode) {
|
|
236
|
+
const proverKeyStore = createKeyStoreForProver(config);
|
|
237
|
+
if (proverKeyStore) {
|
|
238
|
+
rawKeyStores.push(proverKeyStore);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
if (rawKeyStores.length > 0) {
|
|
242
|
+
keyStoreManager = new KeystoreManager(
|
|
243
|
+
rawKeyStores.length === 1 ? rawKeyStores[0] : mergeKeystores(rawKeyStores),
|
|
244
|
+
);
|
|
203
245
|
}
|
|
204
246
|
}
|
|
205
247
|
|
|
@@ -210,10 +252,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
210
252
|
if (keyStoreManager === undefined) {
|
|
211
253
|
throw new Error('Failed to create key store, a requirement for running a validator');
|
|
212
254
|
}
|
|
213
|
-
if (!keyStoreProvided) {
|
|
214
|
-
log.warn(
|
|
215
|
-
'KEY STORE CREATED FROM ENVIRONMENT, IT IS RECOMMENDED TO USE A FILE-BASED KEY STORE IN PRODUCTION ENVIRONMENTS',
|
|
216
|
-
);
|
|
255
|
+
if (!keyStoreProvided && process.env.NODE_ENV !== 'test') {
|
|
256
|
+
log.warn("Keystore created from env: it's recommended to use a file-based key store for production");
|
|
217
257
|
}
|
|
218
258
|
ValidatorClient.validateKeyStoreConfiguration(keyStoreManager, log);
|
|
219
259
|
}
|
|
@@ -227,7 +267,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
227
267
|
|
|
228
268
|
const publicClient = createPublicClient({
|
|
229
269
|
chain: ethereumChain.chainInfo,
|
|
230
|
-
transport:
|
|
270
|
+
transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: config.l1HttpTimeoutMS }),
|
|
231
271
|
pollingInterval: config.viemPollingIntervalMS,
|
|
232
272
|
});
|
|
233
273
|
|
|
@@ -241,10 +281,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
241
281
|
config.l1Contracts = { ...config.l1Contracts, ...l1ContractsAddresses };
|
|
242
282
|
|
|
243
283
|
const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
|
|
244
|
-
const [l1GenesisTime, slotDuration, rollupVersionFromRollup] = await Promise.all([
|
|
284
|
+
const [l1GenesisTime, slotDuration, rollupVersionFromRollup, rollupManaLimit] = await Promise.all([
|
|
245
285
|
rollupContract.getL1GenesisTime(),
|
|
246
286
|
rollupContract.getSlotDuration(),
|
|
247
287
|
rollupContract.getVersion(),
|
|
288
|
+
rollupContract.getManaLimit().then(Number),
|
|
248
289
|
] as const);
|
|
249
290
|
|
|
250
291
|
config.rollupVersion ??= Number(rollupVersionFromRollup);
|
|
@@ -255,7 +296,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
255
296
|
);
|
|
256
297
|
}
|
|
257
298
|
|
|
258
|
-
const blobClient = await createBlobClientWithFileStores(config,
|
|
299
|
+
const blobClient = await createBlobClientWithFileStores(config, log.createChild('blob-client'));
|
|
259
300
|
|
|
260
301
|
// attempt snapshot sync if possible
|
|
261
302
|
await trySnapshotSync(config, log);
|
|
@@ -275,21 +316,40 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
275
316
|
options.prefilledPublicData,
|
|
276
317
|
telemetry,
|
|
277
318
|
);
|
|
278
|
-
const
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
319
|
+
const useRealVerifiers = config.realProofs || config.debugForceTxProofVerification;
|
|
320
|
+
let peerProofVerifier: ClientProtocolCircuitVerifier;
|
|
321
|
+
let rpcProofVerifier: ClientProtocolCircuitVerifier;
|
|
322
|
+
if (useRealVerifiers) {
|
|
323
|
+
peerProofVerifier = await BatchChonkVerifier.new(config, config.bbChonkVerifyMaxBatch, 'peer');
|
|
324
|
+
const rpcVerifier = await BBCircuitVerifier.new(config);
|
|
325
|
+
rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, config.numConcurrentIVCVerifiers);
|
|
326
|
+
} else {
|
|
327
|
+
peerProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
|
|
328
|
+
rpcProofVerifier = new TestCircuitVerifier(config.proverTestVerificationDelayMs);
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
let debugLogStore: DebugLogStore;
|
|
282
332
|
if (!config.realProofs) {
|
|
283
333
|
log.warn(`Aztec node is accepting fake proofs`);
|
|
334
|
+
|
|
335
|
+
debugLogStore = new InMemoryDebugLogStore();
|
|
336
|
+
log.info(
|
|
337
|
+
'Aztec node started in test mode (realProofs set to false) hence debug logs from public functions will be collected and served',
|
|
338
|
+
);
|
|
339
|
+
} else {
|
|
340
|
+
debugLogStore = new NullDebugLogStore();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const proverOnly = config.enableProverNode && config.disableValidator;
|
|
344
|
+
if (proverOnly) {
|
|
345
|
+
log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
|
|
284
346
|
}
|
|
285
|
-
const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier);
|
|
286
347
|
|
|
287
348
|
// create the tx pool and the p2p client, which will need the l2 block source
|
|
288
349
|
const p2pClient = await createP2PClient(
|
|
289
|
-
P2PClientType.Full,
|
|
290
350
|
config,
|
|
291
351
|
archiver,
|
|
292
|
-
|
|
352
|
+
peerProofVerifier,
|
|
293
353
|
worldStateSynchronizer,
|
|
294
354
|
epochCache,
|
|
295
355
|
packageVersion,
|
|
@@ -298,49 +358,60 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
298
358
|
deps.p2pClientDeps,
|
|
299
359
|
);
|
|
300
360
|
|
|
301
|
-
// We
|
|
302
|
-
|
|
361
|
+
// We'll accumulate sentinel watchers here
|
|
362
|
+
const watchers: Watcher[] = [];
|
|
303
363
|
|
|
304
|
-
// Create FullNodeCheckpointsBuilder for
|
|
364
|
+
// Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
|
|
365
|
+
// Override maxTxsPerCheckpoint with the validator-specific limit if set.
|
|
305
366
|
const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
|
|
306
|
-
{
|
|
367
|
+
{
|
|
368
|
+
...config,
|
|
369
|
+
l1GenesisTime,
|
|
370
|
+
slotDuration: Number(slotDuration),
|
|
371
|
+
rollupManaLimit,
|
|
372
|
+
maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
|
|
373
|
+
},
|
|
307
374
|
worldStateSynchronizer,
|
|
308
375
|
archiver,
|
|
309
376
|
dateProvider,
|
|
310
377
|
telemetry,
|
|
311
378
|
);
|
|
312
379
|
|
|
313
|
-
|
|
314
|
-
const watchers: Watcher[] = [];
|
|
380
|
+
let validatorClient: ValidatorClient | undefined;
|
|
315
381
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
382
|
+
if (!proverOnly) {
|
|
383
|
+
// Create validator client if required
|
|
384
|
+
validatorClient = await createValidatorClient(config, {
|
|
385
|
+
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
386
|
+
worldState: worldStateSynchronizer,
|
|
387
|
+
p2pClient,
|
|
388
|
+
telemetry,
|
|
389
|
+
dateProvider,
|
|
390
|
+
epochCache,
|
|
391
|
+
blockSource: archiver,
|
|
392
|
+
l1ToL2MessageSource: archiver,
|
|
393
|
+
keyStoreManager,
|
|
394
|
+
blobClient,
|
|
395
|
+
slashingProtectionDb: deps.slashingProtectionDb,
|
|
396
|
+
});
|
|
329
397
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
398
|
+
// If we have a validator client, register it as a source of offenses for the slasher,
|
|
399
|
+
// and have it register callbacks on the p2p client *before* we start it, otherwise messages
|
|
400
|
+
// like attestations or auths will fail.
|
|
401
|
+
if (validatorClient) {
|
|
402
|
+
watchers.push(validatorClient);
|
|
403
|
+
if (!options.dontStartSequencer) {
|
|
404
|
+
await validatorClient.registerHandlers();
|
|
405
|
+
}
|
|
337
406
|
}
|
|
338
407
|
}
|
|
339
408
|
|
|
340
|
-
// If there's no validator client
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
409
|
+
// If there's no validator client, create a BlockProposalHandler to handle block proposals
|
|
410
|
+
// for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
|
|
411
|
+
// while non-reexecution is used for validating the proposals and collecting their txs.
|
|
412
|
+
if (!validatorClient) {
|
|
413
|
+
const reexecute = !!config.alwaysReexecuteBlockProposals;
|
|
414
|
+
log.info(`Setting up block proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
|
|
344
415
|
createBlockProposalHandler(config, {
|
|
345
416
|
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
346
417
|
worldState: worldStateSynchronizer,
|
|
@@ -350,7 +421,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
350
421
|
p2pClient,
|
|
351
422
|
dateProvider,
|
|
352
423
|
telemetry,
|
|
353
|
-
}).
|
|
424
|
+
}).register(p2pClient, reexecute);
|
|
354
425
|
}
|
|
355
426
|
|
|
356
427
|
// Start world state and wait for it to sync to the archiver.
|
|
@@ -359,29 +430,33 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
359
430
|
// Start p2p. Note that it depends on world state to be running.
|
|
360
431
|
await p2pClient.start();
|
|
361
432
|
|
|
362
|
-
|
|
363
|
-
if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
|
|
364
|
-
watchers.push(validatorsSentinel);
|
|
365
|
-
}
|
|
366
|
-
|
|
433
|
+
let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
|
|
367
434
|
let epochPruneWatcher: EpochPruneWatcher | undefined;
|
|
368
|
-
if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
|
|
369
|
-
epochPruneWatcher = new EpochPruneWatcher(
|
|
370
|
-
archiver,
|
|
371
|
-
archiver,
|
|
372
|
-
epochCache,
|
|
373
|
-
p2pClient.getTxProvider(),
|
|
374
|
-
validatorCheckpointsBuilder,
|
|
375
|
-
config,
|
|
376
|
-
);
|
|
377
|
-
watchers.push(epochPruneWatcher);
|
|
378
|
-
}
|
|
379
|
-
|
|
380
|
-
// We assume we want to slash for invalid attestations unless all max penalties are set to 0
|
|
381
435
|
let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
436
|
+
|
|
437
|
+
if (!proverOnly) {
|
|
438
|
+
validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
|
|
439
|
+
if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
|
|
440
|
+
watchers.push(validatorsSentinel);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
|
|
444
|
+
epochPruneWatcher = new EpochPruneWatcher(
|
|
445
|
+
archiver,
|
|
446
|
+
archiver,
|
|
447
|
+
epochCache,
|
|
448
|
+
p2pClient.getTxProvider(),
|
|
449
|
+
validatorCheckpointsBuilder,
|
|
450
|
+
config,
|
|
451
|
+
);
|
|
452
|
+
watchers.push(epochPruneWatcher);
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// We assume we want to slash for invalid attestations unless all max penalties are set to 0
|
|
456
|
+
if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
|
|
457
|
+
attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
|
|
458
|
+
watchers.push(attestationsBlockWatcher);
|
|
459
|
+
}
|
|
385
460
|
}
|
|
386
461
|
|
|
387
462
|
// Start p2p-related services once the archiver has completed sync
|
|
@@ -396,6 +471,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
396
471
|
})
|
|
397
472
|
.catch(err => log.error('Failed to start p2p services after archiver sync', err));
|
|
398
473
|
|
|
474
|
+
const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, {
|
|
475
|
+
l1Contracts: config.l1Contracts,
|
|
476
|
+
ethereumSlotDuration: config.ethereumSlotDuration,
|
|
477
|
+
rollupVersion: BigInt(config.rollupVersion),
|
|
478
|
+
l1GenesisTime,
|
|
479
|
+
slotDuration: Number(slotDuration),
|
|
480
|
+
});
|
|
481
|
+
|
|
399
482
|
// Validator enabled, create/start relevant service
|
|
400
483
|
let sequencer: SequencerClient | undefined;
|
|
401
484
|
let slasherClient: SlasherClientInterface | undefined;
|
|
@@ -418,34 +501,49 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
418
501
|
);
|
|
419
502
|
await slasherClient.start();
|
|
420
503
|
|
|
421
|
-
const l1TxUtils = config.
|
|
422
|
-
? await
|
|
504
|
+
const l1TxUtils = config.sequencerPublisherForwarderAddress
|
|
505
|
+
? await createForwarderL1TxUtilsFromSigners(
|
|
423
506
|
publicClient,
|
|
424
507
|
keyStoreManager!.createAllValidatorPublisherSigners(),
|
|
425
|
-
config.
|
|
508
|
+
config.sequencerPublisherForwarderAddress,
|
|
426
509
|
{ ...config, scope: 'sequencer' },
|
|
427
|
-
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
|
|
510
|
+
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
|
|
428
511
|
)
|
|
429
|
-
: await
|
|
512
|
+
: await createL1TxUtilsFromSigners(
|
|
430
513
|
publicClient,
|
|
431
514
|
keyStoreManager!.createAllValidatorPublisherSigners(),
|
|
432
515
|
{ ...config, scope: 'sequencer' },
|
|
433
|
-
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
|
|
516
|
+
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
|
|
434
517
|
);
|
|
435
518
|
|
|
519
|
+
// Create a funder L1TxUtils from the keystore funding account (if configured)
|
|
520
|
+
const fundingSigner = keyStoreManager?.createFundingSigner();
|
|
521
|
+
let funderL1TxUtils: L1TxUtils | undefined;
|
|
522
|
+
if (fundingSigner) {
|
|
523
|
+
const [funder] = await createL1TxUtilsFromSigners(
|
|
524
|
+
publicClient,
|
|
525
|
+
[fundingSigner],
|
|
526
|
+
{ ...config, scope: 'sequencer' },
|
|
527
|
+
{ telemetry, logger: log.createChild('l1-tx-utils:funder'), dateProvider },
|
|
528
|
+
);
|
|
529
|
+
funderL1TxUtils = funder;
|
|
530
|
+
}
|
|
531
|
+
|
|
436
532
|
// Create and start the sequencer client
|
|
437
533
|
const checkpointsBuilder = new CheckpointsBuilder(
|
|
438
|
-
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
|
|
534
|
+
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
|
|
439
535
|
worldStateSynchronizer,
|
|
440
536
|
archiver,
|
|
441
537
|
dateProvider,
|
|
442
538
|
telemetry,
|
|
539
|
+
debugLogStore,
|
|
443
540
|
);
|
|
444
541
|
|
|
445
542
|
sequencer = await SequencerClient.new(config, {
|
|
446
543
|
...deps,
|
|
447
544
|
epochCache,
|
|
448
545
|
l1TxUtils,
|
|
546
|
+
funderL1TxUtils,
|
|
449
547
|
validatorClient,
|
|
450
548
|
p2pClient,
|
|
451
549
|
worldStateSynchronizer,
|
|
@@ -457,6 +555,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
457
555
|
dateProvider,
|
|
458
556
|
blobClient,
|
|
459
557
|
nodeKeyStore: keyStoreManager!,
|
|
558
|
+
globalVariableBuilder,
|
|
460
559
|
});
|
|
461
560
|
}
|
|
462
561
|
|
|
@@ -467,14 +566,30 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
467
566
|
log.warn(`Sequencer created but not started`);
|
|
468
567
|
}
|
|
469
568
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
569
|
+
// Create prover node subsystem if enabled
|
|
570
|
+
let proverNode: ProverNode | undefined;
|
|
571
|
+
if (config.enableProverNode) {
|
|
572
|
+
proverNode = await createProverNode(config, {
|
|
573
|
+
...deps.proverNodeDeps,
|
|
574
|
+
telemetry,
|
|
575
|
+
dateProvider,
|
|
576
|
+
archiver,
|
|
577
|
+
worldStateSynchronizer,
|
|
578
|
+
p2pClient,
|
|
579
|
+
epochCache,
|
|
580
|
+
blobClient,
|
|
581
|
+
keyStoreManager,
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
if (!options.dontStartProverNode) {
|
|
585
|
+
await proverNode.start();
|
|
586
|
+
log.info(`Prover node subsystem started`);
|
|
587
|
+
} else {
|
|
588
|
+
log.info(`Prover node subsystem created but not started`);
|
|
589
|
+
}
|
|
590
|
+
}
|
|
476
591
|
|
|
477
|
-
|
|
592
|
+
const node = new AztecNodeService(
|
|
478
593
|
config,
|
|
479
594
|
p2pClient,
|
|
480
595
|
archiver,
|
|
@@ -483,6 +598,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
483
598
|
archiver,
|
|
484
599
|
worldStateSynchronizer,
|
|
485
600
|
sequencer,
|
|
601
|
+
proverNode,
|
|
486
602
|
slasherClient,
|
|
487
603
|
validatorsSentinel,
|
|
488
604
|
epochPruneWatcher,
|
|
@@ -491,11 +607,17 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
491
607
|
globalVariableBuilder,
|
|
492
608
|
epochCache,
|
|
493
609
|
packageVersion,
|
|
494
|
-
|
|
610
|
+
peerProofVerifier,
|
|
611
|
+
rpcProofVerifier,
|
|
495
612
|
telemetry,
|
|
496
613
|
log,
|
|
497
614
|
blobClient,
|
|
615
|
+
validatorClient,
|
|
616
|
+
keyStoreManager,
|
|
617
|
+
debugLogStore,
|
|
498
618
|
);
|
|
619
|
+
|
|
620
|
+
return node;
|
|
499
621
|
}
|
|
500
622
|
|
|
501
623
|
/**
|
|
@@ -506,6 +628,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
506
628
|
return this.sequencer;
|
|
507
629
|
}
|
|
508
630
|
|
|
631
|
+
/** Returns the prover node subsystem, if enabled. */
|
|
632
|
+
public getProverNode(): ProverNode | undefined {
|
|
633
|
+
return this.proverNode;
|
|
634
|
+
}
|
|
635
|
+
|
|
509
636
|
public getBlockSource(): L2BlockSource {
|
|
510
637
|
return this.blockSource;
|
|
511
638
|
}
|
|
@@ -531,7 +658,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
531
658
|
}
|
|
532
659
|
|
|
533
660
|
public async getAllowedPublicSetup(): Promise<AllowedElement[]> {
|
|
534
|
-
return this.config.
|
|
661
|
+
return [...(await getDefaultAllowedSetupFunctions()), ...(this.config.txPublicSetupAllowListExtend ?? [])];
|
|
535
662
|
}
|
|
536
663
|
|
|
537
664
|
/**
|
|
@@ -559,6 +686,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
559
686
|
enr,
|
|
560
687
|
l1ContractAddresses: contractAddresses,
|
|
561
688
|
protocolContractAddresses: protocolContractAddresses,
|
|
689
|
+
realProofs: !!this.config.realProofs,
|
|
562
690
|
};
|
|
563
691
|
|
|
564
692
|
return nodeInfo;
|
|
@@ -625,6 +753,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
625
753
|
return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
|
|
626
754
|
}
|
|
627
755
|
|
|
756
|
+
public getCheckpointsDataForEpoch(epochNumber: EpochNumber) {
|
|
757
|
+
return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
|
|
758
|
+
}
|
|
759
|
+
|
|
628
760
|
/**
|
|
629
761
|
* Method to fetch the current min L2 fees.
|
|
630
762
|
* @returns The current min L2 fees.
|
|
@@ -657,6 +789,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
657
789
|
return await this.blockSource.getCheckpointedL2BlockNumber();
|
|
658
790
|
}
|
|
659
791
|
|
|
792
|
+
public getCheckpointNumber(): Promise<CheckpointNumber> {
|
|
793
|
+
return this.blockSource.getCheckpointNumber();
|
|
794
|
+
}
|
|
795
|
+
|
|
660
796
|
/**
|
|
661
797
|
* Method to fetch the version of the package.
|
|
662
798
|
* @returns The node package version
|
|
@@ -694,18 +830,22 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
694
830
|
page?: number,
|
|
695
831
|
referenceBlock?: BlockHash,
|
|
696
832
|
): Promise<TxScopedL2Log[][]> {
|
|
833
|
+
let upToBlockNumber: BlockNumber | undefined;
|
|
697
834
|
if (referenceBlock) {
|
|
698
835
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
699
|
-
if (
|
|
836
|
+
if (referenceBlock.equals(initialBlockHash)) {
|
|
837
|
+
upToBlockNumber = BlockNumber(0);
|
|
838
|
+
} else {
|
|
700
839
|
const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
|
|
701
840
|
if (!header) {
|
|
702
841
|
throw new Error(
|
|
703
842
|
`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
|
|
704
843
|
);
|
|
705
844
|
}
|
|
845
|
+
upToBlockNumber = header.globalVariables.blockNumber;
|
|
706
846
|
}
|
|
707
847
|
}
|
|
708
|
-
return this.logsSource.getPrivateLogsByTags(tags, page);
|
|
848
|
+
return this.logsSource.getPrivateLogsByTags(tags, page, upToBlockNumber);
|
|
709
849
|
}
|
|
710
850
|
|
|
711
851
|
public async getPublicLogsByTagsFromContract(
|
|
@@ -714,18 +854,22 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
714
854
|
page?: number,
|
|
715
855
|
referenceBlock?: BlockHash,
|
|
716
856
|
): Promise<TxScopedL2Log[][]> {
|
|
857
|
+
let upToBlockNumber: BlockNumber | undefined;
|
|
717
858
|
if (referenceBlock) {
|
|
718
859
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
719
|
-
if (
|
|
860
|
+
if (referenceBlock.equals(initialBlockHash)) {
|
|
861
|
+
upToBlockNumber = BlockNumber(0);
|
|
862
|
+
} else {
|
|
720
863
|
const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
|
|
721
864
|
if (!header) {
|
|
722
865
|
throw new Error(
|
|
723
866
|
`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
|
|
724
867
|
);
|
|
725
868
|
}
|
|
869
|
+
upToBlockNumber = header.globalVariables.blockNumber;
|
|
726
870
|
}
|
|
727
871
|
}
|
|
728
|
-
return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page);
|
|
872
|
+
return this.logsSource.getPublicLogsByTagsFromContract(contractAddress, tags, page, upToBlockNumber);
|
|
729
873
|
}
|
|
730
874
|
|
|
731
875
|
/**
|
|
@@ -767,8 +911,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
767
911
|
}
|
|
768
912
|
|
|
769
913
|
await this.p2pClient!.sendTx(tx);
|
|
770
|
-
|
|
771
|
-
this.
|
|
914
|
+
const duration = timer.ms();
|
|
915
|
+
this.metrics.receivedTx(duration, true);
|
|
916
|
+
this.log.info(`Received tx ${txHash} in ${duration}ms`, { txHash });
|
|
772
917
|
}
|
|
773
918
|
|
|
774
919
|
public async getTxReceipt(txHash: TxHash): Promise<TxReceipt> {
|
|
@@ -780,18 +925,22 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
780
925
|
// Then get the actual tx from the archiver, which tracks every tx in a mined block.
|
|
781
926
|
const settledTxReceipt = await this.blockSource.getSettledTxReceipt(txHash);
|
|
782
927
|
|
|
928
|
+
let receipt: TxReceipt;
|
|
783
929
|
if (settledTxReceipt) {
|
|
784
|
-
|
|
785
|
-
return settledTxReceipt;
|
|
930
|
+
receipt = settledTxReceipt;
|
|
786
931
|
} else if (isKnownToPool) {
|
|
787
932
|
// If the tx is in the pool but not in the archiver, it's pending.
|
|
788
933
|
// This handles race conditions between archiver and p2p, where the archiver
|
|
789
934
|
// has pruned the block in which a tx was mined, but p2p has not caught up yet.
|
|
790
|
-
|
|
935
|
+
receipt = new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
|
|
791
936
|
} else {
|
|
792
937
|
// Otherwise, if we don't know the tx, we consider it dropped.
|
|
793
|
-
|
|
938
|
+
receipt = new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
|
|
794
939
|
}
|
|
940
|
+
|
|
941
|
+
this.debugLogStore.decorateReceiptWithLogs(txHash.toString(), receipt);
|
|
942
|
+
|
|
943
|
+
return receipt;
|
|
795
944
|
}
|
|
796
945
|
|
|
797
946
|
public getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
|
|
@@ -806,8 +955,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
806
955
|
await tryStop(this.validatorsSentinel);
|
|
807
956
|
await tryStop(this.epochPruneWatcher);
|
|
808
957
|
await tryStop(this.slasherClient);
|
|
809
|
-
await tryStop(this.
|
|
958
|
+
await Promise.all([tryStop(this.peerProofVerifier), tryStop(this.rpcProofVerifier)]);
|
|
810
959
|
await tryStop(this.sequencer);
|
|
960
|
+
await tryStop(this.proverNode);
|
|
811
961
|
await tryStop(this.p2pClient);
|
|
812
962
|
await tryStop(this.worldStateSynchronizer);
|
|
813
963
|
await tryStop(this.blockSource);
|
|
@@ -857,100 +1007,98 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
857
1007
|
}
|
|
858
1008
|
|
|
859
1009
|
public async findLeavesIndexes(
|
|
860
|
-
|
|
1010
|
+
referenceBlock: BlockParameter,
|
|
861
1011
|
treeId: MerkleTreeId,
|
|
862
1012
|
leafValues: Fr[],
|
|
863
1013
|
): Promise<(DataInBlock<bigint> | undefined)[]> {
|
|
864
|
-
const committedDb = await this
|
|
1014
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
865
1015
|
const maybeIndices = await committedDb.findLeafIndices(
|
|
866
1016
|
treeId,
|
|
867
1017
|
leafValues.map(x => x.toBuffer()),
|
|
868
1018
|
);
|
|
869
|
-
//
|
|
870
|
-
const
|
|
1019
|
+
// Filter out undefined values to query block numbers only for found leaves
|
|
1020
|
+
const definedIndices = maybeIndices.filter(x => x !== undefined);
|
|
871
1021
|
|
|
872
|
-
// Now we find the block numbers for the indices
|
|
873
|
-
const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId,
|
|
1022
|
+
// Now we find the block numbers for the defined indices
|
|
1023
|
+
const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, definedIndices);
|
|
874
1024
|
|
|
875
|
-
//
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1025
|
+
// Build a map from leaf index to block number
|
|
1026
|
+
const indexToBlockNumber = new Map<bigint, BlockNumber>();
|
|
1027
|
+
for (let i = 0; i < definedIndices.length; i++) {
|
|
1028
|
+
const blockNumber = blockNumbers[i];
|
|
1029
|
+
if (blockNumber === undefined) {
|
|
1030
|
+
throw new Error(
|
|
1031
|
+
`Block number is undefined for leaf index ${definedIndices[i]} in tree ${MerkleTreeId[treeId]}`,
|
|
1032
|
+
);
|
|
879
1033
|
}
|
|
1034
|
+
indexToBlockNumber.set(definedIndices[i], blockNumber);
|
|
880
1035
|
}
|
|
881
1036
|
|
|
882
1037
|
// Get unique block numbers in order to optimize num calls to getLeafValue function.
|
|
883
|
-
const uniqueBlockNumbers = [...new Set(
|
|
1038
|
+
const uniqueBlockNumbers = [...new Set(indexToBlockNumber.values())];
|
|
884
1039
|
|
|
885
|
-
// Now we obtain the block hashes from the archive tree
|
|
886
|
-
// (note that block number corresponds to the leaf index in the archive tree).
|
|
1040
|
+
// Now we obtain the block hashes from the archive tree (block number = leaf index in archive tree).
|
|
887
1041
|
const blockHashes = await Promise.all(
|
|
888
1042
|
uniqueBlockNumbers.map(blockNumber => {
|
|
889
1043
|
return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
|
|
890
1044
|
}),
|
|
891
1045
|
);
|
|
892
1046
|
|
|
893
|
-
//
|
|
1047
|
+
// Build a map from block number to block hash
|
|
1048
|
+
const blockNumberToHash = new Map<BlockNumber, Fr>();
|
|
894
1049
|
for (let i = 0; i < uniqueBlockNumbers.length; i++) {
|
|
895
|
-
|
|
1050
|
+
const blockHash = blockHashes[i];
|
|
1051
|
+
if (blockHash === undefined) {
|
|
896
1052
|
throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
|
|
897
1053
|
}
|
|
1054
|
+
blockNumberToHash.set(uniqueBlockNumbers[i], blockHash);
|
|
898
1055
|
}
|
|
899
1056
|
|
|
900
1057
|
// Create DataInBlock objects by combining indices, blockNumbers and blockHashes and return them.
|
|
901
|
-
return maybeIndices.map(
|
|
1058
|
+
return maybeIndices.map(index => {
|
|
902
1059
|
if (index === undefined) {
|
|
903
1060
|
return undefined;
|
|
904
1061
|
}
|
|
905
|
-
const blockNumber =
|
|
1062
|
+
const blockNumber = indexToBlockNumber.get(index);
|
|
906
1063
|
if (blockNumber === undefined) {
|
|
907
|
-
|
|
1064
|
+
throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
|
|
908
1065
|
}
|
|
909
|
-
const
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
return undefined;
|
|
1066
|
+
const blockHash = blockNumberToHash.get(blockNumber);
|
|
1067
|
+
if (blockHash === undefined) {
|
|
1068
|
+
throw new Error(`Block hash not found for block number ${blockNumber}`);
|
|
913
1069
|
}
|
|
914
1070
|
return {
|
|
915
|
-
l2BlockNumber:
|
|
1071
|
+
l2BlockNumber: blockNumber,
|
|
916
1072
|
l2BlockHash: new BlockHash(blockHash),
|
|
917
1073
|
data: index,
|
|
918
1074
|
};
|
|
919
1075
|
});
|
|
920
1076
|
}
|
|
921
1077
|
|
|
922
|
-
public async
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
): Promise<SiblingPath<typeof NULLIFIER_TREE_HEIGHT>> {
|
|
926
|
-
const committedDb = await this.#getWorldState(block);
|
|
927
|
-
return committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex);
|
|
928
|
-
}
|
|
929
|
-
|
|
930
|
-
public async getNoteHashSiblingPath(
|
|
931
|
-
block: BlockParameter,
|
|
932
|
-
leafIndex: bigint,
|
|
933
|
-
): Promise<SiblingPath<typeof NOTE_HASH_TREE_HEIGHT>> {
|
|
934
|
-
const committedDb = await this.#getWorldState(block);
|
|
935
|
-
return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
|
|
936
|
-
}
|
|
937
|
-
|
|
938
|
-
public async getArchiveMembershipWitness(
|
|
939
|
-
block: BlockParameter,
|
|
940
|
-
archive: Fr,
|
|
1078
|
+
public async getBlockHashMembershipWitness(
|
|
1079
|
+
referenceBlock: BlockParameter,
|
|
1080
|
+
blockHash: BlockHash,
|
|
941
1081
|
): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
|
|
942
|
-
|
|
943
|
-
|
|
1082
|
+
// The Noir circuit checks the archive membership proof against `anchor_block_header.last_archive.root`,
|
|
1083
|
+
// which is the archive tree root BEFORE the anchor block was added (i.e. the state after block N-1).
|
|
1084
|
+
// So we need the world state at block N-1, not block N, to produce a sibling path matching that root.
|
|
1085
|
+
const referenceBlockNumber = await this.resolveBlockNumber(referenceBlock);
|
|
1086
|
+
if (referenceBlockNumber === BlockNumber.ZERO) {
|
|
1087
|
+
// Block 0 (the initial block) has an empty archive, so no membership witness can exist.
|
|
1088
|
+
return undefined;
|
|
1089
|
+
}
|
|
1090
|
+
const committedDb = await this.getWorldState(BlockNumber(referenceBlockNumber - 1));
|
|
1091
|
+
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [blockHash]);
|
|
944
1092
|
return pathAndIndex === undefined
|
|
945
1093
|
? undefined
|
|
946
1094
|
: MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
|
|
947
1095
|
}
|
|
948
1096
|
|
|
949
1097
|
public async getNoteHashMembershipWitness(
|
|
950
|
-
|
|
1098
|
+
referenceBlock: BlockParameter,
|
|
951
1099
|
noteHash: Fr,
|
|
952
1100
|
): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
|
|
953
|
-
const committedDb = await this
|
|
1101
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
954
1102
|
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
|
|
955
1103
|
MerkleTreeId.NOTE_HASH_TREE,
|
|
956
1104
|
[noteHash],
|
|
@@ -961,10 +1109,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
961
1109
|
}
|
|
962
1110
|
|
|
963
1111
|
public async getL1ToL2MessageMembershipWitness(
|
|
964
|
-
|
|
1112
|
+
referenceBlock: BlockParameter,
|
|
965
1113
|
l1ToL2Message: Fr,
|
|
966
1114
|
): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
|
|
967
|
-
const db = await this
|
|
1115
|
+
const db = await this.getWorldState(referenceBlock);
|
|
968
1116
|
const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
|
|
969
1117
|
if (!witness) {
|
|
970
1118
|
return undefined;
|
|
@@ -974,11 +1122,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
974
1122
|
return [witness.index, witness.path];
|
|
975
1123
|
}
|
|
976
1124
|
|
|
977
|
-
public async
|
|
1125
|
+
public async getL1ToL2MessageCheckpoint(l1ToL2Message: Fr): Promise<CheckpointNumber | undefined> {
|
|
978
1126
|
const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
|
|
979
|
-
return messageIndex
|
|
980
|
-
? BlockNumber.fromCheckpointNumber(InboxLeaf.checkpointNumberFromIndex(messageIndex))
|
|
981
|
-
: undefined;
|
|
1127
|
+
return messageIndex ? InboxLeaf.checkpointNumberFromIndex(messageIndex) : undefined;
|
|
982
1128
|
}
|
|
983
1129
|
|
|
984
1130
|
/**
|
|
@@ -999,45 +1145,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
999
1145
|
public async getL2ToL1Messages(epoch: EpochNumber): Promise<Fr[][][][]> {
|
|
1000
1146
|
// Assumes `getCheckpointedBlocksForEpoch` returns blocks in ascending order of block number.
|
|
1001
1147
|
const checkpointedBlocks = await this.blockSource.getCheckpointedBlocksForEpoch(epoch);
|
|
1002
|
-
const blocksInCheckpoints
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
for (const checkpointedBlock of checkpointedBlocks) {
|
|
1006
|
-
const block = checkpointedBlock.block;
|
|
1007
|
-
const slotNumber = block.header.globalVariables.slotNumber;
|
|
1008
|
-
if (slotNumber !== previousSlotNumber) {
|
|
1009
|
-
checkpointIndex++;
|
|
1010
|
-
blocksInCheckpoints.push([]);
|
|
1011
|
-
previousSlotNumber = slotNumber;
|
|
1012
|
-
}
|
|
1013
|
-
blocksInCheckpoints[checkpointIndex].push(block);
|
|
1014
|
-
}
|
|
1148
|
+
const blocksInCheckpoints = chunkBy(checkpointedBlocks, cb => cb.block.header.globalVariables.slotNumber).map(
|
|
1149
|
+
group => group.map(cb => cb.block),
|
|
1150
|
+
);
|
|
1015
1151
|
return blocksInCheckpoints.map(blocks =>
|
|
1016
1152
|
blocks.map(block => block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs)),
|
|
1017
1153
|
);
|
|
1018
1154
|
}
|
|
1019
1155
|
|
|
1020
|
-
public async getArchiveSiblingPath(
|
|
1021
|
-
block: BlockParameter,
|
|
1022
|
-
leafIndex: bigint,
|
|
1023
|
-
): Promise<SiblingPath<typeof ARCHIVE_HEIGHT>> {
|
|
1024
|
-
const committedDb = await this.#getWorldState(block);
|
|
1025
|
-
return committedDb.getSiblingPath(MerkleTreeId.ARCHIVE, leafIndex);
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
public async getPublicDataSiblingPath(
|
|
1029
|
-
block: BlockParameter,
|
|
1030
|
-
leafIndex: bigint,
|
|
1031
|
-
): Promise<SiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>> {
|
|
1032
|
-
const committedDb = await this.#getWorldState(block);
|
|
1033
|
-
return committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
|
|
1034
|
-
}
|
|
1035
|
-
|
|
1036
1156
|
public async getNullifierMembershipWitness(
|
|
1037
|
-
|
|
1157
|
+
referenceBlock: BlockParameter,
|
|
1038
1158
|
nullifier: Fr,
|
|
1039
1159
|
): Promise<NullifierMembershipWitness | undefined> {
|
|
1040
|
-
const db = await this
|
|
1160
|
+
const db = await this.getWorldState(referenceBlock);
|
|
1041
1161
|
const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
|
|
1042
1162
|
if (!witness) {
|
|
1043
1163
|
return undefined;
|
|
@@ -1052,32 +1172,20 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1052
1172
|
return new NullifierMembershipWitness(index, leafPreimage as NullifierLeafPreimage, path);
|
|
1053
1173
|
}
|
|
1054
1174
|
|
|
1055
|
-
/**
|
|
1056
|
-
* Returns a low nullifier membership witness for a given nullifier at a given block.
|
|
1057
|
-
* @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
|
|
1058
|
-
* @param nullifier - Nullifier we try to find the low nullifier witness for.
|
|
1059
|
-
* @returns The low nullifier membership witness (if found).
|
|
1060
|
-
* @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
|
|
1061
|
-
* list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier
|
|
1062
|
-
* we are trying to prove non-inclusion for.
|
|
1063
|
-
*
|
|
1064
|
-
* Note: This function returns the membership witness of the nullifier itself and not the low nullifier when
|
|
1065
|
-
* the nullifier already exists in the tree. This is because the `getPreviousValueIndex` function returns the
|
|
1066
|
-
* index of the nullifier itself when it already exists in the tree.
|
|
1067
|
-
* TODO: This is a confusing behavior and we should eventually address that.
|
|
1068
|
-
*/
|
|
1069
1175
|
public async getLowNullifierMembershipWitness(
|
|
1070
|
-
|
|
1176
|
+
referenceBlock: BlockParameter,
|
|
1071
1177
|
nullifier: Fr,
|
|
1072
1178
|
): Promise<NullifierMembershipWitness | undefined> {
|
|
1073
|
-
const committedDb = await this
|
|
1179
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1074
1180
|
const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
|
|
1075
1181
|
if (!findResult) {
|
|
1076
1182
|
return undefined;
|
|
1077
1183
|
}
|
|
1078
1184
|
const { index, alreadyPresent } = findResult;
|
|
1079
1185
|
if (alreadyPresent) {
|
|
1080
|
-
|
|
1186
|
+
throw new Error(
|
|
1187
|
+
`Cannot prove nullifier non-inclusion: nullifier ${nullifier.toBigInt()} already exists in the tree`,
|
|
1188
|
+
);
|
|
1081
1189
|
}
|
|
1082
1190
|
const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
|
|
1083
1191
|
|
|
@@ -1085,8 +1193,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1085
1193
|
return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
|
|
1086
1194
|
}
|
|
1087
1195
|
|
|
1088
|
-
async getPublicDataWitness(
|
|
1089
|
-
const committedDb = await this
|
|
1196
|
+
async getPublicDataWitness(referenceBlock: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
|
|
1197
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1090
1198
|
const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
|
|
1091
1199
|
if (!lowLeafResult) {
|
|
1092
1200
|
return undefined;
|
|
@@ -1100,8 +1208,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1100
1208
|
}
|
|
1101
1209
|
}
|
|
1102
1210
|
|
|
1103
|
-
public async getPublicStorageAt(
|
|
1104
|
-
const committedDb = await this
|
|
1211
|
+
public async getPublicStorageAt(referenceBlock: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
|
|
1212
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1105
1213
|
const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
|
|
1106
1214
|
|
|
1107
1215
|
const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
|
|
@@ -1142,6 +1250,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1142
1250
|
return await this.blockSource.getBlockHeaderByArchive(archive);
|
|
1143
1251
|
}
|
|
1144
1252
|
|
|
1253
|
+
public getBlockData(number: BlockNumber): Promise<BlockData | undefined> {
|
|
1254
|
+
return this.blockSource.getBlockData(number);
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
public getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
|
|
1258
|
+
return this.blockSource.getBlockDataByArchive(archive);
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1145
1261
|
/**
|
|
1146
1262
|
* Simulates the public part of a transaction with the current state.
|
|
1147
1263
|
* @param tx - The transaction to simulate.
|
|
@@ -1165,7 +1281,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1165
1281
|
}
|
|
1166
1282
|
|
|
1167
1283
|
const txHash = tx.getTxHash();
|
|
1168
|
-
const
|
|
1284
|
+
const latestBlockNumber = await this.blockSource.getBlockNumber();
|
|
1285
|
+
const blockNumber = BlockNumber.add(latestBlockNumber, 1);
|
|
1169
1286
|
|
|
1170
1287
|
// If sequencer is not initialized, we just set these values to zero for simulation.
|
|
1171
1288
|
const coinbase = EthAddress.ZERO;
|
|
@@ -1189,6 +1306,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1189
1306
|
blockNumber,
|
|
1190
1307
|
});
|
|
1191
1308
|
|
|
1309
|
+
// Ensure world-state has caught up with the latest block we loaded from the archiver
|
|
1310
|
+
await this.worldStateSynchronizer.syncImmediate(latestBlockNumber);
|
|
1192
1311
|
const merkleTreeFork = await this.worldStateSynchronizer.fork();
|
|
1193
1312
|
try {
|
|
1194
1313
|
const config = PublicSimulatorConfig.from({
|
|
@@ -1204,7 +1323,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1204
1323
|
const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
|
|
1205
1324
|
|
|
1206
1325
|
// REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
|
|
1207
|
-
const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([tx]);
|
|
1326
|
+
const [processedTxs, failedTxs, _usedTxs, returns, debugLogs] = await processor.process([tx]);
|
|
1208
1327
|
// REFACTOR: Consider returning the error rather than throwing
|
|
1209
1328
|
if (failedTxs.length) {
|
|
1210
1329
|
this.log.warn(`Simulated tx ${txHash} fails: ${failedTxs[0].error}`, { txHash });
|
|
@@ -1218,6 +1337,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1218
1337
|
processedTx.txEffect,
|
|
1219
1338
|
returns,
|
|
1220
1339
|
processedTx.gasUsed,
|
|
1340
|
+
debugLogs,
|
|
1221
1341
|
);
|
|
1222
1342
|
} finally {
|
|
1223
1343
|
await merkleTreeFork.close();
|
|
@@ -1229,12 +1349,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1229
1349
|
{ isSimulation, skipFeeEnforcement }: { isSimulation?: boolean; skipFeeEnforcement?: boolean } = {},
|
|
1230
1350
|
): Promise<TxValidationResult> {
|
|
1231
1351
|
const db = this.worldStateSynchronizer.getCommitted();
|
|
1232
|
-
const verifier = isSimulation ? undefined : this.
|
|
1352
|
+
const verifier = isSimulation ? undefined : this.rpcProofVerifier;
|
|
1233
1353
|
|
|
1234
|
-
// We accept transactions if they are not expired by the next slot (checked based on the
|
|
1354
|
+
// We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
|
|
1235
1355
|
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1236
1356
|
const blockNumber = BlockNumber((await this.blockSource.getBlockNumber()) + 1);
|
|
1237
|
-
const
|
|
1357
|
+
const l1Constants = await this.blockSource.getL1Constants();
|
|
1358
|
+
const validator = createTxValidatorForAcceptingTxsOverRPC(
|
|
1238
1359
|
db,
|
|
1239
1360
|
this.contractDataSource,
|
|
1240
1361
|
verifier,
|
|
@@ -1243,10 +1364,16 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1243
1364
|
blockNumber,
|
|
1244
1365
|
l1ChainId: this.l1ChainId,
|
|
1245
1366
|
rollupVersion: this.version,
|
|
1246
|
-
setupAllowList:
|
|
1367
|
+
setupAllowList: [
|
|
1368
|
+
...(await getDefaultAllowedSetupFunctions()),
|
|
1369
|
+
...(this.config.txPublicSetupAllowListExtend ?? []),
|
|
1370
|
+
],
|
|
1247
1371
|
gasFees: await this.getCurrentMinFees(),
|
|
1248
1372
|
skipFeeEnforcement,
|
|
1249
1373
|
txsPermitted: !this.config.disableTransactions,
|
|
1374
|
+
rollupManaLimit: l1Constants.rollupManaLimit,
|
|
1375
|
+
maxBlockL2Gas: this.config.validateMaxL2BlockGas,
|
|
1376
|
+
maxBlockDAGas: this.config.validateMaxDABlockGas,
|
|
1250
1377
|
},
|
|
1251
1378
|
this.log.getBindings(),
|
|
1252
1379
|
);
|
|
@@ -1271,7 +1398,15 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1271
1398
|
archiver.updateConfig(config);
|
|
1272
1399
|
}
|
|
1273
1400
|
if (newConfig.realProofs !== this.config.realProofs) {
|
|
1274
|
-
|
|
1401
|
+
await Promise.all([tryStop(this.peerProofVerifier), tryStop(this.rpcProofVerifier)]);
|
|
1402
|
+
if (newConfig.realProofs) {
|
|
1403
|
+
this.peerProofVerifier = await BatchChonkVerifier.new(newConfig, newConfig.bbChonkVerifyMaxBatch, 'peer');
|
|
1404
|
+
const rpcVerifier = await BBCircuitVerifier.new(newConfig);
|
|
1405
|
+
this.rpcProofVerifier = new QueuedIVCVerifier(rpcVerifier, newConfig.numConcurrentIVCVerifiers);
|
|
1406
|
+
} else {
|
|
1407
|
+
this.peerProofVerifier = new TestCircuitVerifier();
|
|
1408
|
+
this.rpcProofVerifier = new TestCircuitVerifier();
|
|
1409
|
+
}
|
|
1275
1410
|
}
|
|
1276
1411
|
|
|
1277
1412
|
this.config = newConfig;
|
|
@@ -1413,6 +1548,94 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1413
1548
|
}
|
|
1414
1549
|
}
|
|
1415
1550
|
|
|
1551
|
+
public async reloadKeystore(): Promise<void> {
|
|
1552
|
+
if (!this.config.keyStoreDirectory?.length) {
|
|
1553
|
+
throw new BadRequestError(
|
|
1554
|
+
'Cannot reload keystore: node is not using a file-based keystore. ' +
|
|
1555
|
+
'Set KEY_STORE_DIRECTORY to use file-based keystores.',
|
|
1556
|
+
);
|
|
1557
|
+
}
|
|
1558
|
+
if (!this.validatorClient) {
|
|
1559
|
+
throw new BadRequestError('Cannot reload keystore: validator is not enabled.');
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
this.log.info('Reloading keystore from disk');
|
|
1563
|
+
|
|
1564
|
+
// Re-read and validate keystore files
|
|
1565
|
+
const keyStores = loadKeystores(this.config.keyStoreDirectory);
|
|
1566
|
+
const newManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
1567
|
+
await newManager.validateSigners();
|
|
1568
|
+
ValidatorClient.validateKeyStoreConfiguration(newManager, this.log);
|
|
1569
|
+
|
|
1570
|
+
// Validate that every validator's publisher keys overlap with the L1 signers
|
|
1571
|
+
// that were initialized at startup. Publishers cannot be hot-reloaded, so a
|
|
1572
|
+
// validator with a publisher key that doesn't match any existing L1 signer
|
|
1573
|
+
// would silently fail on every proposer slot.
|
|
1574
|
+
if (this.keyStoreManager && this.sequencer) {
|
|
1575
|
+
const oldAdapter = NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager);
|
|
1576
|
+
const availablePublishers = new Set(
|
|
1577
|
+
oldAdapter
|
|
1578
|
+
.getAttesterAddresses()
|
|
1579
|
+
.flatMap(a => oldAdapter.getPublisherAddresses(a).map(p => p.toString().toLowerCase())),
|
|
1580
|
+
);
|
|
1581
|
+
|
|
1582
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
1583
|
+
for (const attester of newAdapter.getAttesterAddresses()) {
|
|
1584
|
+
const pubs = newAdapter.getPublisherAddresses(attester);
|
|
1585
|
+
if (pubs.length > 0 && !pubs.some(p => availablePublishers.has(p.toString().toLowerCase()))) {
|
|
1586
|
+
throw new BadRequestError(
|
|
1587
|
+
`Cannot reload keystore: validator ${attester} has publisher keys ` +
|
|
1588
|
+
`[${pubs.map(p => p.toString()).join(', ')}] but none match the L1 signers initialized at startup ` +
|
|
1589
|
+
`[${[...availablePublishers].join(', ')}]. Publishers cannot be hot-reloaded — ` +
|
|
1590
|
+
`use an existing publisher key or restart the node.`,
|
|
1591
|
+
);
|
|
1592
|
+
}
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
|
|
1596
|
+
// Build adapters for old and new keystores to compute diff
|
|
1597
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
1598
|
+
const newAddresses = newAdapter.getAttesterAddresses();
|
|
1599
|
+
const oldAddresses = this.keyStoreManager
|
|
1600
|
+
? NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager).getAttesterAddresses()
|
|
1601
|
+
: [];
|
|
1602
|
+
|
|
1603
|
+
const oldSet = new Set(oldAddresses.map(a => a.toString()));
|
|
1604
|
+
const newSet = new Set(newAddresses.map(a => a.toString()));
|
|
1605
|
+
const added = newAddresses.filter(a => !oldSet.has(a.toString()));
|
|
1606
|
+
const removed = oldAddresses.filter(a => !newSet.has(a.toString()));
|
|
1607
|
+
|
|
1608
|
+
if (added.length > 0) {
|
|
1609
|
+
this.log.info(`Keystore reload: adding attester keys: ${added.map(a => a.toString()).join(', ')}`);
|
|
1610
|
+
}
|
|
1611
|
+
if (removed.length > 0) {
|
|
1612
|
+
this.log.info(`Keystore reload: removing attester keys: ${removed.map(a => a.toString()).join(', ')}`);
|
|
1613
|
+
}
|
|
1614
|
+
if (added.length === 0 && removed.length === 0) {
|
|
1615
|
+
this.log.info('Keystore reload: attester keys unchanged');
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
// Update the validator client (coinbase, feeRecipient, attester keys)
|
|
1619
|
+
this.validatorClient.reloadKeystore(newManager);
|
|
1620
|
+
|
|
1621
|
+
// Update the publisher factory's keystore so newly-added validators
|
|
1622
|
+
// can be matched to existing publisher keys when proposing blocks.
|
|
1623
|
+
if (this.sequencer) {
|
|
1624
|
+
this.sequencer.updatePublisherNodeKeyStore(newAdapter);
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
// Update slasher's "don't-slash-self" list with new validator addresses
|
|
1628
|
+
if (this.slasherClient && !this.config.slashSelfAllowed) {
|
|
1629
|
+
const slashValidatorsNever = unique(
|
|
1630
|
+
[...(this.config.slashValidatorsNever ?? []), ...newAddresses].map(a => a.toString()),
|
|
1631
|
+
).map(EthAddress.fromString);
|
|
1632
|
+
this.slasherClient.updateConfig({ slashValidatorsNever });
|
|
1633
|
+
}
|
|
1634
|
+
|
|
1635
|
+
this.keyStoreManager = newManager;
|
|
1636
|
+
this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1416
1639
|
#getInitialHeaderHash(): Promise<BlockHash> {
|
|
1417
1640
|
if (!this.initialHeaderHashPromise) {
|
|
1418
1641
|
this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
|
|
@@ -1425,7 +1648,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1425
1648
|
* @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
|
|
1426
1649
|
* @returns An instance of a committed MerkleTreeOperations
|
|
1427
1650
|
*/
|
|
1428
|
-
async
|
|
1651
|
+
protected async getWorldState(block: BlockParameter) {
|
|
1429
1652
|
let blockSyncedTo: BlockNumber = BlockNumber.ZERO;
|
|
1430
1653
|
try {
|
|
1431
1654
|
// Attempt to sync the world state if necessary
|
|
@@ -1439,6 +1662,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1439
1662
|
return this.worldStateSynchronizer.getCommitted();
|
|
1440
1663
|
}
|
|
1441
1664
|
|
|
1665
|
+
// Get the block number, either directly from the parameter or by quering the archiver with the block hash
|
|
1666
|
+
let blockNumber: BlockNumber;
|
|
1442
1667
|
if (BlockHash.isBlockHash(block)) {
|
|
1443
1668
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
1444
1669
|
if (block.equals(initialBlockHash)) {
|
|
@@ -1452,22 +1677,50 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1452
1677
|
`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.`,
|
|
1453
1678
|
);
|
|
1454
1679
|
}
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1680
|
+
|
|
1681
|
+
blockNumber = header.getBlockNumber();
|
|
1682
|
+
} else {
|
|
1683
|
+
blockNumber = block as BlockNumber;
|
|
1684
|
+
}
|
|
1685
|
+
|
|
1686
|
+
// Check it's within world state sync range
|
|
1687
|
+
if (blockNumber > blockSyncedTo) {
|
|
1688
|
+
throw new Error(`Queried block ${block} not yet synced by the node (node is synced upto ${blockSyncedTo}).`);
|
|
1458
1689
|
}
|
|
1690
|
+
this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
|
|
1459
1691
|
|
|
1460
|
-
|
|
1461
|
-
{
|
|
1462
|
-
const blockNumber = block as BlockNumber;
|
|
1692
|
+
const snapshot = this.worldStateSynchronizer.getSnapshot(blockNumber);
|
|
1463
1693
|
|
|
1464
|
-
|
|
1465
|
-
|
|
1694
|
+
// Double-check world-state synced to the same block hash as was requested
|
|
1695
|
+
if (BlockHash.isBlockHash(block)) {
|
|
1696
|
+
const blockHash = await snapshot.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
|
|
1697
|
+
if (!blockHash || !new BlockHash(blockHash).equals(block)) {
|
|
1698
|
+
throw new Error(
|
|
1699
|
+
`Block hash ${block.toString()} not found in world state at block number ${blockNumber}. If the node API has been queried with anchor block hash possibly a reorg has occurred.`,
|
|
1700
|
+
);
|
|
1466
1701
|
}
|
|
1702
|
+
}
|
|
1467
1703
|
|
|
1468
|
-
|
|
1469
|
-
|
|
1704
|
+
return snapshot;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1707
|
+
/** Resolves a block parameter to a block number. */
|
|
1708
|
+
protected async resolveBlockNumber(block: BlockParameter): Promise<BlockNumber> {
|
|
1709
|
+
if (block === 'latest') {
|
|
1710
|
+
return BlockNumber(await this.blockSource.getBlockNumber());
|
|
1711
|
+
}
|
|
1712
|
+
if (BlockHash.isBlockHash(block)) {
|
|
1713
|
+
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
1714
|
+
if (block.equals(initialBlockHash)) {
|
|
1715
|
+
return BlockNumber.ZERO;
|
|
1716
|
+
}
|
|
1717
|
+
const header = await this.blockSource.getBlockHeaderByHash(block);
|
|
1718
|
+
if (!header) {
|
|
1719
|
+
throw new Error(`Block hash ${block.toString()} not found.`);
|
|
1720
|
+
}
|
|
1721
|
+
return header.getBlockNumber();
|
|
1470
1722
|
}
|
|
1723
|
+
return block as BlockNumber;
|
|
1471
1724
|
}
|
|
1472
1725
|
|
|
1473
1726
|
/**
|