@aztec/aztec-node 0.0.1-commit.fffb133c → 0.0.1-dev
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 +8 -4
- package/dest/aztec-node/server.d.ts +47 -39
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +410 -208
- 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 +2 -2
- package/dest/sentinel/sentinel.d.ts.map +1 -1
- package/dest/sentinel/sentinel.js +56 -30
- 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 +27 -25
- package/src/aztec-node/config.ts +24 -8
- package/src/aztec-node/node_metrics.ts +12 -5
- package/src/aztec-node/server.ts +512 -256
- package/src/sentinel/factory.ts +1 -6
- package/src/sentinel/sentinel.ts +59 -26
- package/src/sentinel/store.ts +12 -12
package/src/aztec-node/server.ts
CHANGED
|
@@ -1,35 +1,36 @@
|
|
|
1
1
|
import { Archiver, createArchiver } from '@aztec/archiver';
|
|
2
2
|
import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
|
|
3
3
|
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';
|
|
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';
|
|
11
6
|
import { EpochCache, type EpochCacheInterface } from '@aztec/epoch-cache';
|
|
12
7
|
import { createEthereumChain } from '@aztec/ethereum/chain';
|
|
13
|
-
import { getPublicClient } from '@aztec/ethereum/client';
|
|
8
|
+
import { getPublicClient, makeL1HttpTransport } from '@aztec/ethereum/client';
|
|
14
9
|
import { RegistryContract, RollupContract } from '@aztec/ethereum/contracts';
|
|
15
10
|
import type { L1ContractAddresses } from '@aztec/ethereum/l1-contract-addresses';
|
|
16
11
|
import { BlockNumber, CheckpointNumber, EpochNumber, SlotNumber } from '@aztec/foundation/branded-types';
|
|
17
|
-
import { compactArray, pick } from '@aztec/foundation/collection';
|
|
12
|
+
import { chunkBy, compactArray, pick, unique } from '@aztec/foundation/collection';
|
|
18
13
|
import { Fr } from '@aztec/foundation/curves/bn254';
|
|
19
14
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
20
15
|
import { BadRequestError } from '@aztec/foundation/json-rpc';
|
|
21
16
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
17
|
+
import { retryUntil } from '@aztec/foundation/retry';
|
|
22
18
|
import { count } from '@aztec/foundation/string';
|
|
23
19
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
24
20
|
import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
|
|
25
|
-
import { KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
|
|
21
|
+
import { type KeyStore, KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
|
|
26
22
|
import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
|
|
23
|
+
import { createForwarderL1TxUtilsFromSigners, createL1TxUtilsFromSigners } from '@aztec/node-lib/factories';
|
|
27
24
|
import {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
25
|
+
type P2P,
|
|
26
|
+
type P2PClientDeps,
|
|
27
|
+
createP2PClient,
|
|
28
|
+
createTxValidatorForAcceptingTxsOverRPC,
|
|
29
|
+
getDefaultAllowedSetupFunctions,
|
|
30
|
+
} from '@aztec/p2p';
|
|
32
31
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
32
|
+
import { type ProverNode, type ProverNodeDeps, createProverNode } from '@aztec/prover-node';
|
|
33
|
+
import { createKeyStoreForProver } from '@aztec/prover-node/config';
|
|
33
34
|
import { GlobalVariableBuilder, SequencerClient, type SequencerPublisher } from '@aztec/sequencer-client';
|
|
34
35
|
import { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
35
36
|
import {
|
|
@@ -41,7 +42,14 @@ import {
|
|
|
41
42
|
} from '@aztec/slasher';
|
|
42
43
|
import { CollectionLimitsConfig, PublicSimulatorConfig } from '@aztec/stdlib/avm';
|
|
43
44
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
44
|
-
import {
|
|
45
|
+
import {
|
|
46
|
+
type BlockData,
|
|
47
|
+
BlockHash,
|
|
48
|
+
type BlockParameter,
|
|
49
|
+
type DataInBlock,
|
|
50
|
+
L2Block,
|
|
51
|
+
type L2BlockSource,
|
|
52
|
+
} from '@aztec/stdlib/block';
|
|
45
53
|
import type { PublishedCheckpoint } from '@aztec/stdlib/checkpoint';
|
|
46
54
|
import type {
|
|
47
55
|
ContractClassPublic,
|
|
@@ -57,6 +65,7 @@ import {
|
|
|
57
65
|
type AztecNodeAdmin,
|
|
58
66
|
type AztecNodeAdminConfig,
|
|
59
67
|
AztecNodeAdminConfigSchema,
|
|
68
|
+
type AztecNodeDebug,
|
|
60
69
|
type GetContractClassLogsResponse,
|
|
61
70
|
type GetPublicLogsResponse,
|
|
62
71
|
} from '@aztec/stdlib/interfaces/client';
|
|
@@ -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';
|
|
@@ -101,13 +110,13 @@ import {
|
|
|
101
110
|
FullNodeCheckpointsBuilder,
|
|
102
111
|
NodeKeystoreAdapter,
|
|
103
112
|
ValidatorClient,
|
|
104
|
-
|
|
113
|
+
createProposalHandler,
|
|
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';
|
|
@@ -117,9 +126,9 @@ import { NodeMetrics } from './node_metrics.js';
|
|
|
117
126
|
/**
|
|
118
127
|
* The aztec node.
|
|
119
128
|
*/
|
|
120
|
-
export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
129
|
+
export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDebug, Traceable {
|
|
121
130
|
private metrics: NodeMetrics;
|
|
122
|
-
private initialHeaderHashPromise: Promise<
|
|
131
|
+
private initialHeaderHashPromise: Promise<BlockHash> | undefined = undefined;
|
|
123
132
|
|
|
124
133
|
// Prevent two snapshot operations to happen simultaneously
|
|
125
134
|
private isUploadingSnapshot = false;
|
|
@@ -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,
|
|
@@ -147,12 +157,22 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
147
157
|
private telemetry: TelemetryClient = getTelemetryClient(),
|
|
148
158
|
private log = createLogger('node'),
|
|
149
159
|
private blobClient?: BlobClientInterface,
|
|
160
|
+
private validatorClient?: ValidatorClient,
|
|
161
|
+
private keyStoreManager?: KeystoreManager,
|
|
162
|
+
private debugLogStore: DebugLogStore = new NullDebugLogStore(),
|
|
150
163
|
) {
|
|
151
164
|
this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
|
|
152
165
|
this.tracer = telemetry.getTracer('AztecNodeService');
|
|
153
166
|
|
|
154
167
|
this.log.info(`Aztec Node version: ${this.packageVersion}`);
|
|
155
168
|
this.log.info(`Aztec Node started on chain 0x${l1ChainId.toString(16)}`, config.l1Contracts);
|
|
169
|
+
|
|
170
|
+
// A defensive check that protects us against introducing a bug in the complex `createAndSync` function. We must
|
|
171
|
+
// never have debugLogStore enabled when not in test mode because then we would be accumulating debug logs in
|
|
172
|
+
// memory which could be a DoS vector on the sequencer (since no fees are paid for debug logs).
|
|
173
|
+
if (debugLogStore.isEnabled && config.realProofs) {
|
|
174
|
+
throw new Error('debugLogStore should never be enabled when realProofs are set');
|
|
175
|
+
}
|
|
156
176
|
}
|
|
157
177
|
|
|
158
178
|
public async getWorldStateSyncStatus(): Promise<WorldStateSyncStatus> {
|
|
@@ -176,30 +196,46 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
176
196
|
logger?: Logger;
|
|
177
197
|
publisher?: SequencerPublisher;
|
|
178
198
|
dateProvider?: DateProvider;
|
|
179
|
-
p2pClientDeps?: P2PClientDeps
|
|
199
|
+
p2pClientDeps?: P2PClientDeps;
|
|
200
|
+
proverNodeDeps?: Partial<ProverNodeDeps>;
|
|
201
|
+
slashingProtectionDb?: SlashingProtectionDatabase;
|
|
180
202
|
} = {},
|
|
181
203
|
options: {
|
|
182
204
|
prefilledPublicData?: PublicDataTreeLeaf[];
|
|
183
205
|
dontStartSequencer?: boolean;
|
|
206
|
+
dontStartProverNode?: boolean;
|
|
184
207
|
} = {},
|
|
185
208
|
): Promise<AztecNodeService> {
|
|
186
209
|
const config = { ...inputConfig }; // Copy the config so we dont mutate the input object
|
|
187
210
|
const log = deps.logger ?? createLogger('node');
|
|
188
|
-
const packageVersion = getPackageVersion()
|
|
211
|
+
const packageVersion = getPackageVersion();
|
|
189
212
|
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
190
213
|
const dateProvider = deps.dateProvider ?? new DateProvider();
|
|
191
214
|
const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
192
215
|
|
|
193
|
-
// Build a key store from file if given or from environment otherwise
|
|
216
|
+
// Build a key store from file if given or from environment otherwise.
|
|
217
|
+
// We keep the raw KeyStore available so we can merge with prover keys if enableProverNode is set.
|
|
194
218
|
let keyStoreManager: KeystoreManager | undefined;
|
|
195
219
|
const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
|
|
196
220
|
if (keyStoreProvided) {
|
|
197
221
|
const keyStores = loadKeystores(config.keyStoreDirectory!);
|
|
198
222
|
keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
199
223
|
} else {
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
224
|
+
const rawKeyStores: KeyStore[] = [];
|
|
225
|
+
const validatorKeyStore = createKeyStoreForValidator(config);
|
|
226
|
+
if (validatorKeyStore) {
|
|
227
|
+
rawKeyStores.push(validatorKeyStore);
|
|
228
|
+
}
|
|
229
|
+
if (config.enableProverNode) {
|
|
230
|
+
const proverKeyStore = createKeyStoreForProver(config);
|
|
231
|
+
if (proverKeyStore) {
|
|
232
|
+
rawKeyStores.push(proverKeyStore);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (rawKeyStores.length > 0) {
|
|
236
|
+
keyStoreManager = new KeystoreManager(
|
|
237
|
+
rawKeyStores.length === 1 ? rawKeyStores[0] : mergeKeystores(rawKeyStores),
|
|
238
|
+
);
|
|
203
239
|
}
|
|
204
240
|
}
|
|
205
241
|
|
|
@@ -210,10 +246,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
210
246
|
if (keyStoreManager === undefined) {
|
|
211
247
|
throw new Error('Failed to create key store, a requirement for running a validator');
|
|
212
248
|
}
|
|
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
|
-
);
|
|
249
|
+
if (!keyStoreProvided && process.env.NODE_ENV !== 'test') {
|
|
250
|
+
log.warn("Keystore created from env: it's recommended to use a file-based key store for production");
|
|
217
251
|
}
|
|
218
252
|
ValidatorClient.validateKeyStoreConfiguration(keyStoreManager, log);
|
|
219
253
|
}
|
|
@@ -227,7 +261,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
227
261
|
|
|
228
262
|
const publicClient = createPublicClient({
|
|
229
263
|
chain: ethereumChain.chainInfo,
|
|
230
|
-
transport:
|
|
264
|
+
transport: makeL1HttpTransport(config.l1RpcUrls, { timeout: config.l1HttpTimeoutMS }),
|
|
231
265
|
pollingInterval: config.viemPollingIntervalMS,
|
|
232
266
|
});
|
|
233
267
|
|
|
@@ -241,10 +275,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
241
275
|
config.l1Contracts = { ...config.l1Contracts, ...l1ContractsAddresses };
|
|
242
276
|
|
|
243
277
|
const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
|
|
244
|
-
const [l1GenesisTime, slotDuration, rollupVersionFromRollup] = await Promise.all([
|
|
278
|
+
const [l1GenesisTime, slotDuration, rollupVersionFromRollup, rollupManaLimit] = await Promise.all([
|
|
245
279
|
rollupContract.getL1GenesisTime(),
|
|
246
280
|
rollupContract.getSlotDuration(),
|
|
247
281
|
rollupContract.getVersion(),
|
|
282
|
+
rollupContract.getManaLimit().then(Number),
|
|
248
283
|
] as const);
|
|
249
284
|
|
|
250
285
|
config.rollupVersion ??= Number(rollupVersionFromRollup);
|
|
@@ -255,7 +290,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
255
290
|
);
|
|
256
291
|
}
|
|
257
292
|
|
|
258
|
-
const blobClient = await createBlobClientWithFileStores(config,
|
|
293
|
+
const blobClient = await createBlobClientWithFileStores(config, log.createChild('blob-client'));
|
|
259
294
|
|
|
260
295
|
// attempt snapshot sync if possible
|
|
261
296
|
await trySnapshotSync(config, log);
|
|
@@ -279,14 +314,28 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
279
314
|
config.realProofs || config.debugForceTxProofVerification
|
|
280
315
|
? await BBCircuitVerifier.new(config)
|
|
281
316
|
: new TestCircuitVerifier(config.proverTestVerificationDelayMs);
|
|
317
|
+
|
|
318
|
+
let debugLogStore: DebugLogStore;
|
|
282
319
|
if (!config.realProofs) {
|
|
283
320
|
log.warn(`Aztec node is accepting fake proofs`);
|
|
321
|
+
|
|
322
|
+
debugLogStore = new InMemoryDebugLogStore();
|
|
323
|
+
log.info(
|
|
324
|
+
'Aztec node started in test mode (realProofs set to false) hence debug logs from public functions will be collected and served',
|
|
325
|
+
);
|
|
326
|
+
} else {
|
|
327
|
+
debugLogStore = new NullDebugLogStore();
|
|
284
328
|
}
|
|
329
|
+
|
|
285
330
|
const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier);
|
|
286
331
|
|
|
332
|
+
const proverOnly = config.enableProverNode && config.disableValidator;
|
|
333
|
+
if (proverOnly) {
|
|
334
|
+
log.info('Starting in prover-only mode: skipping validator, sequencer, sentinel, and slasher subsystems');
|
|
335
|
+
}
|
|
336
|
+
|
|
287
337
|
// create the tx pool and the p2p client, which will need the l2 block source
|
|
288
338
|
const p2pClient = await createP2PClient(
|
|
289
|
-
P2PClientType.Full,
|
|
290
339
|
config,
|
|
291
340
|
archiver,
|
|
292
341
|
proofVerifier,
|
|
@@ -298,59 +347,72 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
298
347
|
deps.p2pClientDeps,
|
|
299
348
|
);
|
|
300
349
|
|
|
301
|
-
// We
|
|
302
|
-
|
|
350
|
+
// We'll accumulate sentinel watchers here
|
|
351
|
+
const watchers: Watcher[] = [];
|
|
303
352
|
|
|
304
|
-
// Create FullNodeCheckpointsBuilder for
|
|
353
|
+
// Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
|
|
354
|
+
// Override maxTxsPerCheckpoint with the validator-specific limit if set.
|
|
305
355
|
const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
|
|
306
|
-
{
|
|
356
|
+
{
|
|
357
|
+
...config,
|
|
358
|
+
l1GenesisTime,
|
|
359
|
+
slotDuration: Number(slotDuration),
|
|
360
|
+
rollupManaLimit,
|
|
361
|
+
maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
|
|
362
|
+
},
|
|
307
363
|
worldStateSynchronizer,
|
|
308
364
|
archiver,
|
|
309
365
|
dateProvider,
|
|
310
366
|
telemetry,
|
|
311
367
|
);
|
|
312
368
|
|
|
313
|
-
|
|
314
|
-
const watchers: Watcher[] = [];
|
|
369
|
+
let validatorClient: ValidatorClient | undefined;
|
|
315
370
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
371
|
+
if (!proverOnly) {
|
|
372
|
+
// Create validator client if required
|
|
373
|
+
validatorClient = await createValidatorClient(config, {
|
|
374
|
+
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
375
|
+
worldState: worldStateSynchronizer,
|
|
376
|
+
p2pClient,
|
|
377
|
+
telemetry,
|
|
378
|
+
dateProvider,
|
|
379
|
+
epochCache,
|
|
380
|
+
blockSource: archiver,
|
|
381
|
+
l1ToL2MessageSource: archiver,
|
|
382
|
+
keyStoreManager,
|
|
383
|
+
blobClient,
|
|
384
|
+
slashingProtectionDb: deps.slashingProtectionDb,
|
|
385
|
+
});
|
|
329
386
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
387
|
+
// If we have a validator client, register it as a source of offenses for the slasher,
|
|
388
|
+
// and have it register callbacks on the p2p client *before* we start it, otherwise messages
|
|
389
|
+
// like attestations or auths will fail.
|
|
390
|
+
if (validatorClient) {
|
|
391
|
+
watchers.push(validatorClient);
|
|
392
|
+
if (!options.dontStartSequencer) {
|
|
393
|
+
await validatorClient.registerHandlers();
|
|
394
|
+
}
|
|
337
395
|
}
|
|
338
396
|
}
|
|
339
397
|
|
|
340
|
-
// If there's no validator client
|
|
341
|
-
//
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
398
|
+
// If there's no validator client, create a ProposalHandler to handle block and checkpoint proposals
|
|
399
|
+
// for monitoring or reexecution. Reexecution (default) allows us to follow the pending chain,
|
|
400
|
+
// while non-reexecution is used for validating the proposals and collecting their txs.
|
|
401
|
+
// Checkpoint proposals are handled if the blob client can upload blobs.
|
|
402
|
+
if (!validatorClient) {
|
|
403
|
+
const reexecute = !!config.alwaysReexecuteBlockProposals;
|
|
404
|
+
log.info(`Setting up proposal handler` + (reexecute ? ' with reexecution of proposals' : ''));
|
|
405
|
+
createProposalHandler(config, {
|
|
345
406
|
checkpointsBuilder: validatorCheckpointsBuilder,
|
|
346
407
|
worldState: worldStateSynchronizer,
|
|
347
408
|
epochCache,
|
|
348
409
|
blockSource: archiver,
|
|
349
410
|
l1ToL2MessageSource: archiver,
|
|
350
411
|
p2pClient,
|
|
412
|
+
blobClient,
|
|
351
413
|
dateProvider,
|
|
352
414
|
telemetry,
|
|
353
|
-
}).
|
|
415
|
+
}).register(p2pClient, reexecute);
|
|
354
416
|
}
|
|
355
417
|
|
|
356
418
|
// Start world state and wait for it to sync to the archiver.
|
|
@@ -359,29 +421,33 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
359
421
|
// Start p2p. Note that it depends on world state to be running.
|
|
360
422
|
await p2pClient.start();
|
|
361
423
|
|
|
362
|
-
|
|
363
|
-
if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
|
|
364
|
-
watchers.push(validatorsSentinel);
|
|
365
|
-
}
|
|
366
|
-
|
|
424
|
+
let validatorsSentinel: Awaited<ReturnType<typeof createSentinel>> | undefined;
|
|
367
425
|
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
426
|
let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
427
|
+
|
|
428
|
+
if (!proverOnly) {
|
|
429
|
+
validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
|
|
430
|
+
if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
|
|
431
|
+
watchers.push(validatorsSentinel);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
|
|
435
|
+
epochPruneWatcher = new EpochPruneWatcher(
|
|
436
|
+
archiver,
|
|
437
|
+
archiver,
|
|
438
|
+
epochCache,
|
|
439
|
+
p2pClient.getTxProvider(),
|
|
440
|
+
validatorCheckpointsBuilder,
|
|
441
|
+
config,
|
|
442
|
+
);
|
|
443
|
+
watchers.push(epochPruneWatcher);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// We assume we want to slash for invalid attestations unless all max penalties are set to 0
|
|
447
|
+
if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
|
|
448
|
+
attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
|
|
449
|
+
watchers.push(attestationsBlockWatcher);
|
|
450
|
+
}
|
|
385
451
|
}
|
|
386
452
|
|
|
387
453
|
// Start p2p-related services once the archiver has completed sync
|
|
@@ -396,6 +462,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
396
462
|
})
|
|
397
463
|
.catch(err => log.error('Failed to start p2p services after archiver sync', err));
|
|
398
464
|
|
|
465
|
+
const globalVariableBuilder = new GlobalVariableBuilder(dateProvider, publicClient, {
|
|
466
|
+
l1Contracts: config.l1Contracts,
|
|
467
|
+
ethereumSlotDuration: config.ethereumSlotDuration,
|
|
468
|
+
rollupVersion: BigInt(config.rollupVersion),
|
|
469
|
+
l1GenesisTime,
|
|
470
|
+
slotDuration: Number(slotDuration),
|
|
471
|
+
});
|
|
472
|
+
|
|
399
473
|
// Validator enabled, create/start relevant service
|
|
400
474
|
let sequencer: SequencerClient | undefined;
|
|
401
475
|
let slasherClient: SlasherClientInterface | undefined;
|
|
@@ -418,28 +492,29 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
418
492
|
);
|
|
419
493
|
await slasherClient.start();
|
|
420
494
|
|
|
421
|
-
const l1TxUtils = config.
|
|
422
|
-
? await
|
|
495
|
+
const l1TxUtils = config.sequencerPublisherForwarderAddress
|
|
496
|
+
? await createForwarderL1TxUtilsFromSigners(
|
|
423
497
|
publicClient,
|
|
424
498
|
keyStoreManager!.createAllValidatorPublisherSigners(),
|
|
425
|
-
config.
|
|
499
|
+
config.sequencerPublisherForwarderAddress,
|
|
426
500
|
{ ...config, scope: 'sequencer' },
|
|
427
|
-
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
|
|
501
|
+
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
|
|
428
502
|
)
|
|
429
|
-
: await
|
|
503
|
+
: await createL1TxUtilsFromSigners(
|
|
430
504
|
publicClient,
|
|
431
505
|
keyStoreManager!.createAllValidatorPublisherSigners(),
|
|
432
506
|
{ ...config, scope: 'sequencer' },
|
|
433
|
-
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
|
|
507
|
+
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider, kzg: Blob.getViemKzgInstance() },
|
|
434
508
|
);
|
|
435
509
|
|
|
436
510
|
// Create and start the sequencer client
|
|
437
511
|
const checkpointsBuilder = new CheckpointsBuilder(
|
|
438
|
-
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
|
|
512
|
+
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
|
|
439
513
|
worldStateSynchronizer,
|
|
440
514
|
archiver,
|
|
441
515
|
dateProvider,
|
|
442
516
|
telemetry,
|
|
517
|
+
debugLogStore,
|
|
443
518
|
);
|
|
444
519
|
|
|
445
520
|
sequencer = await SequencerClient.new(config, {
|
|
@@ -457,6 +532,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
457
532
|
dateProvider,
|
|
458
533
|
blobClient,
|
|
459
534
|
nodeKeyStore: keyStoreManager!,
|
|
535
|
+
globalVariableBuilder,
|
|
460
536
|
});
|
|
461
537
|
}
|
|
462
538
|
|
|
@@ -467,14 +543,30 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
467
543
|
log.warn(`Sequencer created but not started`);
|
|
468
544
|
}
|
|
469
545
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
546
|
+
// Create prover node subsystem if enabled
|
|
547
|
+
let proverNode: ProverNode | undefined;
|
|
548
|
+
if (config.enableProverNode) {
|
|
549
|
+
proverNode = await createProverNode(config, {
|
|
550
|
+
...deps.proverNodeDeps,
|
|
551
|
+
telemetry,
|
|
552
|
+
dateProvider,
|
|
553
|
+
archiver,
|
|
554
|
+
worldStateSynchronizer,
|
|
555
|
+
p2pClient,
|
|
556
|
+
epochCache,
|
|
557
|
+
blobClient,
|
|
558
|
+
keyStoreManager,
|
|
559
|
+
});
|
|
476
560
|
|
|
477
|
-
|
|
561
|
+
if (!options.dontStartProverNode) {
|
|
562
|
+
await proverNode.start();
|
|
563
|
+
log.info(`Prover node subsystem started`);
|
|
564
|
+
} else {
|
|
565
|
+
log.info(`Prover node subsystem created but not started`);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
const node = new AztecNodeService(
|
|
478
570
|
config,
|
|
479
571
|
p2pClient,
|
|
480
572
|
archiver,
|
|
@@ -483,6 +575,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
483
575
|
archiver,
|
|
484
576
|
worldStateSynchronizer,
|
|
485
577
|
sequencer,
|
|
578
|
+
proverNode,
|
|
486
579
|
slasherClient,
|
|
487
580
|
validatorsSentinel,
|
|
488
581
|
epochPruneWatcher,
|
|
@@ -495,7 +588,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
495
588
|
telemetry,
|
|
496
589
|
log,
|
|
497
590
|
blobClient,
|
|
591
|
+
validatorClient,
|
|
592
|
+
keyStoreManager,
|
|
593
|
+
debugLogStore,
|
|
498
594
|
);
|
|
595
|
+
|
|
596
|
+
return node;
|
|
499
597
|
}
|
|
500
598
|
|
|
501
599
|
/**
|
|
@@ -506,6 +604,11 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
506
604
|
return this.sequencer;
|
|
507
605
|
}
|
|
508
606
|
|
|
607
|
+
/** Returns the prover node subsystem, if enabled. */
|
|
608
|
+
public getProverNode(): ProverNode | undefined {
|
|
609
|
+
return this.proverNode;
|
|
610
|
+
}
|
|
611
|
+
|
|
509
612
|
public getBlockSource(): L2BlockSource {
|
|
510
613
|
return this.blockSource;
|
|
511
614
|
}
|
|
@@ -531,7 +634,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
531
634
|
}
|
|
532
635
|
|
|
533
636
|
public async getAllowedPublicSetup(): Promise<AllowedElement[]> {
|
|
534
|
-
return this.config.
|
|
637
|
+
return [...(await getDefaultAllowedSetupFunctions()), ...(this.config.txPublicSetupAllowListExtend ?? [])];
|
|
535
638
|
}
|
|
536
639
|
|
|
537
640
|
/**
|
|
@@ -559,6 +662,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
559
662
|
enr,
|
|
560
663
|
l1ContractAddresses: contractAddresses,
|
|
561
664
|
protocolContractAddresses: protocolContractAddresses,
|
|
665
|
+
realProofs: !!this.config.realProofs,
|
|
562
666
|
};
|
|
563
667
|
|
|
564
668
|
return nodeInfo;
|
|
@@ -570,8 +674,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
570
674
|
* @returns The requested block.
|
|
571
675
|
*/
|
|
572
676
|
public async getBlock(block: BlockParameter): Promise<L2Block | undefined> {
|
|
573
|
-
if (
|
|
574
|
-
return this.getBlockByHash(
|
|
677
|
+
if (BlockHash.isBlockHash(block)) {
|
|
678
|
+
return this.getBlockByHash(block);
|
|
575
679
|
}
|
|
576
680
|
const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
|
|
577
681
|
if (blockNumber === BlockNumber.ZERO) {
|
|
@@ -585,9 +689,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
585
689
|
* @param blockHash - The block hash being requested.
|
|
586
690
|
* @returns The requested block.
|
|
587
691
|
*/
|
|
588
|
-
public async getBlockByHash(blockHash:
|
|
692
|
+
public async getBlockByHash(blockHash: BlockHash): Promise<L2Block | undefined> {
|
|
589
693
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
590
|
-
if (blockHash.equals(
|
|
694
|
+
if (blockHash.equals(initialBlockHash)) {
|
|
591
695
|
return this.buildInitialBlock();
|
|
592
696
|
}
|
|
593
697
|
return await this.blockSource.getL2BlockByHash(blockHash);
|
|
@@ -625,6 +729,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
625
729
|
return (await this.blockSource.getCheckpointedBlocks(from, limit)) ?? [];
|
|
626
730
|
}
|
|
627
731
|
|
|
732
|
+
public getCheckpointsDataForEpoch(epochNumber: EpochNumber) {
|
|
733
|
+
return this.blockSource.getCheckpointsDataForEpoch(epochNumber);
|
|
734
|
+
}
|
|
735
|
+
|
|
628
736
|
/**
|
|
629
737
|
* Method to fetch the current min L2 fees.
|
|
630
738
|
* @returns The current min L2 fees.
|
|
@@ -657,6 +765,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
657
765
|
return await this.blockSource.getCheckpointedL2BlockNumber();
|
|
658
766
|
}
|
|
659
767
|
|
|
768
|
+
public getCheckpointNumber(): Promise<CheckpointNumber> {
|
|
769
|
+
return this.blockSource.getCheckpointNumber();
|
|
770
|
+
}
|
|
771
|
+
|
|
660
772
|
/**
|
|
661
773
|
* Method to fetch the version of the package.
|
|
662
774
|
* @returns The node package version
|
|
@@ -692,13 +804,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
692
804
|
public async getPrivateLogsByTags(
|
|
693
805
|
tags: SiloedTag[],
|
|
694
806
|
page?: number,
|
|
695
|
-
referenceBlock?:
|
|
807
|
+
referenceBlock?: BlockHash,
|
|
696
808
|
): Promise<TxScopedL2Log[][]> {
|
|
697
809
|
if (referenceBlock) {
|
|
698
810
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
699
811
|
if (!referenceBlock.equals(initialBlockHash)) {
|
|
700
|
-
const
|
|
701
|
-
const header = await this.blockSource.getBlockHeaderByHash(blockHashFr);
|
|
812
|
+
const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
|
|
702
813
|
if (!header) {
|
|
703
814
|
throw new Error(
|
|
704
815
|
`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
|
|
@@ -713,13 +824,12 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
713
824
|
contractAddress: AztecAddress,
|
|
714
825
|
tags: Tag[],
|
|
715
826
|
page?: number,
|
|
716
|
-
referenceBlock?:
|
|
827
|
+
referenceBlock?: BlockHash,
|
|
717
828
|
): Promise<TxScopedL2Log[][]> {
|
|
718
829
|
if (referenceBlock) {
|
|
719
830
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
720
831
|
if (!referenceBlock.equals(initialBlockHash)) {
|
|
721
|
-
const
|
|
722
|
-
const header = await this.blockSource.getBlockHeaderByHash(blockHashFr);
|
|
832
|
+
const header = await this.blockSource.getBlockHeaderByHash(referenceBlock);
|
|
723
833
|
if (!header) {
|
|
724
834
|
throw new Error(
|
|
725
835
|
`Block ${referenceBlock.toString()} not found in the node. This might indicate a reorg has occurred.`,
|
|
@@ -769,8 +879,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
769
879
|
}
|
|
770
880
|
|
|
771
881
|
await this.p2pClient!.sendTx(tx);
|
|
772
|
-
|
|
773
|
-
this.
|
|
882
|
+
const duration = timer.ms();
|
|
883
|
+
this.metrics.receivedTx(duration, true);
|
|
884
|
+
this.log.info(`Received tx ${txHash} in ${duration}ms`, { txHash });
|
|
774
885
|
}
|
|
775
886
|
|
|
776
887
|
public async getTxReceipt(txHash: TxHash): Promise<TxReceipt> {
|
|
@@ -782,18 +893,22 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
782
893
|
// Then get the actual tx from the archiver, which tracks every tx in a mined block.
|
|
783
894
|
const settledTxReceipt = await this.blockSource.getSettledTxReceipt(txHash);
|
|
784
895
|
|
|
896
|
+
let receipt: TxReceipt;
|
|
785
897
|
if (settledTxReceipt) {
|
|
786
|
-
|
|
787
|
-
return settledTxReceipt;
|
|
898
|
+
receipt = settledTxReceipt;
|
|
788
899
|
} else if (isKnownToPool) {
|
|
789
900
|
// If the tx is in the pool but not in the archiver, it's pending.
|
|
790
901
|
// This handles race conditions between archiver and p2p, where the archiver
|
|
791
902
|
// has pruned the block in which a tx was mined, but p2p has not caught up yet.
|
|
792
|
-
|
|
903
|
+
receipt = new TxReceipt(txHash, TxStatus.PENDING, undefined, undefined);
|
|
793
904
|
} else {
|
|
794
905
|
// Otherwise, if we don't know the tx, we consider it dropped.
|
|
795
|
-
|
|
906
|
+
receipt = new TxReceipt(txHash, TxStatus.DROPPED, undefined, 'Tx dropped by P2P node');
|
|
796
907
|
}
|
|
908
|
+
|
|
909
|
+
this.debugLogStore.decorateReceiptWithLogs(txHash.toString(), receipt);
|
|
910
|
+
|
|
911
|
+
return receipt;
|
|
797
912
|
}
|
|
798
913
|
|
|
799
914
|
public getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
|
|
@@ -810,6 +925,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
810
925
|
await tryStop(this.slasherClient);
|
|
811
926
|
await tryStop(this.proofVerifier);
|
|
812
927
|
await tryStop(this.sequencer);
|
|
928
|
+
await tryStop(this.proverNode);
|
|
813
929
|
await tryStop(this.p2pClient);
|
|
814
930
|
await tryStop(this.worldStateSynchronizer);
|
|
815
931
|
await tryStop(this.blockSource);
|
|
@@ -859,100 +975,104 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
859
975
|
}
|
|
860
976
|
|
|
861
977
|
public async findLeavesIndexes(
|
|
862
|
-
|
|
978
|
+
referenceBlock: BlockParameter,
|
|
863
979
|
treeId: MerkleTreeId,
|
|
864
980
|
leafValues: Fr[],
|
|
865
981
|
): Promise<(DataInBlock<bigint> | undefined)[]> {
|
|
866
|
-
const committedDb = await this
|
|
982
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
867
983
|
const maybeIndices = await committedDb.findLeafIndices(
|
|
868
984
|
treeId,
|
|
869
985
|
leafValues.map(x => x.toBuffer()),
|
|
870
986
|
);
|
|
871
|
-
//
|
|
872
|
-
const
|
|
987
|
+
// Filter out undefined values to query block numbers only for found leaves
|
|
988
|
+
const definedIndices = maybeIndices.filter(x => x !== undefined);
|
|
873
989
|
|
|
874
|
-
// Now we find the block numbers for the indices
|
|
875
|
-
const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId,
|
|
990
|
+
// Now we find the block numbers for the defined indices
|
|
991
|
+
const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, definedIndices);
|
|
876
992
|
|
|
877
|
-
//
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
993
|
+
// Build a map from leaf index to block number
|
|
994
|
+
const indexToBlockNumber = new Map<bigint, BlockNumber>();
|
|
995
|
+
for (let i = 0; i < definedIndices.length; i++) {
|
|
996
|
+
const blockNumber = blockNumbers[i];
|
|
997
|
+
if (blockNumber === undefined) {
|
|
998
|
+
throw new Error(
|
|
999
|
+
`Block number is undefined for leaf index ${definedIndices[i]} in tree ${MerkleTreeId[treeId]}`,
|
|
1000
|
+
);
|
|
881
1001
|
}
|
|
1002
|
+
indexToBlockNumber.set(definedIndices[i], blockNumber);
|
|
882
1003
|
}
|
|
883
1004
|
|
|
884
1005
|
// Get unique block numbers in order to optimize num calls to getLeafValue function.
|
|
885
|
-
const uniqueBlockNumbers = [...new Set(
|
|
1006
|
+
const uniqueBlockNumbers = [...new Set(indexToBlockNumber.values())];
|
|
886
1007
|
|
|
887
|
-
// Now we obtain the block hashes from the archive tree
|
|
888
|
-
// (note that block number corresponds to the leaf index in the archive tree).
|
|
1008
|
+
// Now we obtain the block hashes from the archive tree (block number = leaf index in archive tree).
|
|
889
1009
|
const blockHashes = await Promise.all(
|
|
890
1010
|
uniqueBlockNumbers.map(blockNumber => {
|
|
891
1011
|
return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, BigInt(blockNumber));
|
|
892
1012
|
}),
|
|
893
1013
|
);
|
|
894
1014
|
|
|
895
|
-
//
|
|
1015
|
+
// Build a map from block number to block hash
|
|
1016
|
+
const blockNumberToHash = new Map<BlockNumber, Fr>();
|
|
896
1017
|
for (let i = 0; i < uniqueBlockNumbers.length; i++) {
|
|
897
|
-
|
|
1018
|
+
const blockHash = blockHashes[i];
|
|
1019
|
+
if (blockHash === undefined) {
|
|
898
1020
|
throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
|
|
899
1021
|
}
|
|
1022
|
+
blockNumberToHash.set(uniqueBlockNumbers[i], blockHash);
|
|
900
1023
|
}
|
|
901
1024
|
|
|
902
1025
|
// Create DataInBlock objects by combining indices, blockNumbers and blockHashes and return them.
|
|
903
|
-
return maybeIndices.map(
|
|
1026
|
+
return maybeIndices.map(index => {
|
|
904
1027
|
if (index === undefined) {
|
|
905
1028
|
return undefined;
|
|
906
1029
|
}
|
|
907
|
-
const blockNumber =
|
|
1030
|
+
const blockNumber = indexToBlockNumber.get(index);
|
|
908
1031
|
if (blockNumber === undefined) {
|
|
909
|
-
|
|
1032
|
+
throw new Error(`Block number not found for leaf index ${index} in tree ${MerkleTreeId[treeId]}`);
|
|
910
1033
|
}
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
return undefined;
|
|
1034
|
+
const blockHash = blockNumberToHash.get(blockNumber);
|
|
1035
|
+
if (blockHash === undefined) {
|
|
1036
|
+
throw new Error(`Block hash not found for block number ${blockNumber}`);
|
|
915
1037
|
}
|
|
916
1038
|
return {
|
|
917
|
-
l2BlockNumber:
|
|
918
|
-
l2BlockHash:
|
|
1039
|
+
l2BlockNumber: blockNumber,
|
|
1040
|
+
l2BlockHash: new BlockHash(blockHash),
|
|
919
1041
|
data: index,
|
|
920
1042
|
};
|
|
921
1043
|
});
|
|
922
1044
|
}
|
|
923
1045
|
|
|
924
|
-
public async
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
): Promise<SiblingPath<typeof NULLIFIER_TREE_HEIGHT>> {
|
|
928
|
-
const committedDb = await this.#getWorldState(block);
|
|
929
|
-
return committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, leafIndex);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
|
-
public async getNoteHashSiblingPath(
|
|
933
|
-
block: BlockParameter,
|
|
934
|
-
leafIndex: bigint,
|
|
935
|
-
): Promise<SiblingPath<typeof NOTE_HASH_TREE_HEIGHT>> {
|
|
936
|
-
const committedDb = await this.#getWorldState(block);
|
|
937
|
-
return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
|
|
938
|
-
}
|
|
939
|
-
|
|
940
|
-
public async getArchiveMembershipWitness(
|
|
941
|
-
block: BlockParameter,
|
|
942
|
-
archive: Fr,
|
|
1046
|
+
public async getBlockHashMembershipWitness(
|
|
1047
|
+
referenceBlock: BlockParameter,
|
|
1048
|
+
blockHash: BlockHash,
|
|
943
1049
|
): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
|
|
944
|
-
|
|
945
|
-
|
|
1050
|
+
// Block 0 (the initial block) has an empty archive, so no membership witness can exist.
|
|
1051
|
+
if (referenceBlock === BlockNumber.ZERO) {
|
|
1052
|
+
return undefined;
|
|
1053
|
+
}
|
|
1054
|
+
if (BlockHash.isBlockHash(referenceBlock)) {
|
|
1055
|
+
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
1056
|
+
if (referenceBlock.equals(initialBlockHash)) {
|
|
1057
|
+
return undefined;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
// The Noir circuit checks the archive membership proof against `anchor_block_header.last_archive.root`,
|
|
1061
|
+
// which is the archive tree root BEFORE the anchor block was added (i.e. the state after block N-1).
|
|
1062
|
+
// So we need the world state at block N-1, not block N, to produce a sibling path matching that root.
|
|
1063
|
+
const referenceBlockNumber = await this.resolveBlockNumber(referenceBlock);
|
|
1064
|
+
const committedDb = await this.getWorldState(BlockNumber(referenceBlockNumber - 1));
|
|
1065
|
+
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [blockHash]);
|
|
946
1066
|
return pathAndIndex === undefined
|
|
947
1067
|
? undefined
|
|
948
1068
|
: MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
|
|
949
1069
|
}
|
|
950
1070
|
|
|
951
1071
|
public async getNoteHashMembershipWitness(
|
|
952
|
-
|
|
1072
|
+
referenceBlock: BlockParameter,
|
|
953
1073
|
noteHash: Fr,
|
|
954
1074
|
): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
|
|
955
|
-
const committedDb = await this
|
|
1075
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
956
1076
|
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
|
|
957
1077
|
MerkleTreeId.NOTE_HASH_TREE,
|
|
958
1078
|
[noteHash],
|
|
@@ -963,10 +1083,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
963
1083
|
}
|
|
964
1084
|
|
|
965
1085
|
public async getL1ToL2MessageMembershipWitness(
|
|
966
|
-
|
|
1086
|
+
referenceBlock: BlockParameter,
|
|
967
1087
|
l1ToL2Message: Fr,
|
|
968
1088
|
): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
|
|
969
|
-
const db = await this
|
|
1089
|
+
const db = await this.getWorldState(referenceBlock);
|
|
970
1090
|
const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
|
|
971
1091
|
if (!witness) {
|
|
972
1092
|
return undefined;
|
|
@@ -976,11 +1096,9 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
976
1096
|
return [witness.index, witness.path];
|
|
977
1097
|
}
|
|
978
1098
|
|
|
979
|
-
public async
|
|
1099
|
+
public async getL1ToL2MessageCheckpoint(l1ToL2Message: Fr): Promise<CheckpointNumber | undefined> {
|
|
980
1100
|
const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
|
|
981
|
-
return messageIndex
|
|
982
|
-
? BlockNumber.fromCheckpointNumber(InboxLeaf.checkpointNumberFromIndex(messageIndex))
|
|
983
|
-
: undefined;
|
|
1101
|
+
return messageIndex ? InboxLeaf.checkpointNumberFromIndex(messageIndex) : undefined;
|
|
984
1102
|
}
|
|
985
1103
|
|
|
986
1104
|
/**
|
|
@@ -1001,45 +1119,19 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1001
1119
|
public async getL2ToL1Messages(epoch: EpochNumber): Promise<Fr[][][][]> {
|
|
1002
1120
|
// Assumes `getCheckpointedBlocksForEpoch` returns blocks in ascending order of block number.
|
|
1003
1121
|
const checkpointedBlocks = await this.blockSource.getCheckpointedBlocksForEpoch(epoch);
|
|
1004
|
-
const blocksInCheckpoints
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
for (const checkpointedBlock of checkpointedBlocks) {
|
|
1008
|
-
const block = checkpointedBlock.block;
|
|
1009
|
-
const slotNumber = block.header.globalVariables.slotNumber;
|
|
1010
|
-
if (slotNumber !== previousSlotNumber) {
|
|
1011
|
-
checkpointIndex++;
|
|
1012
|
-
blocksInCheckpoints.push([]);
|
|
1013
|
-
previousSlotNumber = slotNumber;
|
|
1014
|
-
}
|
|
1015
|
-
blocksInCheckpoints[checkpointIndex].push(block);
|
|
1016
|
-
}
|
|
1122
|
+
const blocksInCheckpoints = chunkBy(checkpointedBlocks, cb => cb.block.header.globalVariables.slotNumber).map(
|
|
1123
|
+
group => group.map(cb => cb.block),
|
|
1124
|
+
);
|
|
1017
1125
|
return blocksInCheckpoints.map(blocks =>
|
|
1018
1126
|
blocks.map(block => block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs)),
|
|
1019
1127
|
);
|
|
1020
1128
|
}
|
|
1021
1129
|
|
|
1022
|
-
public async getArchiveSiblingPath(
|
|
1023
|
-
block: BlockParameter,
|
|
1024
|
-
leafIndex: bigint,
|
|
1025
|
-
): Promise<SiblingPath<typeof ARCHIVE_HEIGHT>> {
|
|
1026
|
-
const committedDb = await this.#getWorldState(block);
|
|
1027
|
-
return committedDb.getSiblingPath(MerkleTreeId.ARCHIVE, leafIndex);
|
|
1028
|
-
}
|
|
1029
|
-
|
|
1030
|
-
public async getPublicDataSiblingPath(
|
|
1031
|
-
block: BlockParameter,
|
|
1032
|
-
leafIndex: bigint,
|
|
1033
|
-
): Promise<SiblingPath<typeof PUBLIC_DATA_TREE_HEIGHT>> {
|
|
1034
|
-
const committedDb = await this.#getWorldState(block);
|
|
1035
|
-
return committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, leafIndex);
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
1130
|
public async getNullifierMembershipWitness(
|
|
1039
|
-
|
|
1131
|
+
referenceBlock: BlockParameter,
|
|
1040
1132
|
nullifier: Fr,
|
|
1041
1133
|
): Promise<NullifierMembershipWitness | undefined> {
|
|
1042
|
-
const db = await this
|
|
1134
|
+
const db = await this.getWorldState(referenceBlock);
|
|
1043
1135
|
const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
|
|
1044
1136
|
if (!witness) {
|
|
1045
1137
|
return undefined;
|
|
@@ -1054,32 +1146,20 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1054
1146
|
return new NullifierMembershipWitness(index, leafPreimage as NullifierLeafPreimage, path);
|
|
1055
1147
|
}
|
|
1056
1148
|
|
|
1057
|
-
/**
|
|
1058
|
-
* Returns a low nullifier membership witness for a given nullifier at a given block.
|
|
1059
|
-
* @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
|
|
1060
|
-
* @param nullifier - Nullifier we try to find the low nullifier witness for.
|
|
1061
|
-
* @returns The low nullifier membership witness (if found).
|
|
1062
|
-
* @remarks Low nullifier witness can be used to perform a nullifier non-inclusion proof by leveraging the "linked
|
|
1063
|
-
* list structure" of leaves and proving that a lower nullifier is pointing to a bigger next value than the nullifier
|
|
1064
|
-
* we are trying to prove non-inclusion for.
|
|
1065
|
-
*
|
|
1066
|
-
* Note: This function returns the membership witness of the nullifier itself and not the low nullifier when
|
|
1067
|
-
* the nullifier already exists in the tree. This is because the `getPreviousValueIndex` function returns the
|
|
1068
|
-
* index of the nullifier itself when it already exists in the tree.
|
|
1069
|
-
* TODO: This is a confusing behavior and we should eventually address that.
|
|
1070
|
-
*/
|
|
1071
1149
|
public async getLowNullifierMembershipWitness(
|
|
1072
|
-
|
|
1150
|
+
referenceBlock: BlockParameter,
|
|
1073
1151
|
nullifier: Fr,
|
|
1074
1152
|
): Promise<NullifierMembershipWitness | undefined> {
|
|
1075
|
-
const committedDb = await this
|
|
1153
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1076
1154
|
const findResult = await committedDb.getPreviousValueIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBigInt());
|
|
1077
1155
|
if (!findResult) {
|
|
1078
1156
|
return undefined;
|
|
1079
1157
|
}
|
|
1080
1158
|
const { index, alreadyPresent } = findResult;
|
|
1081
1159
|
if (alreadyPresent) {
|
|
1082
|
-
|
|
1160
|
+
throw new Error(
|
|
1161
|
+
`Cannot prove nullifier non-inclusion: nullifier ${nullifier.toBigInt()} already exists in the tree`,
|
|
1162
|
+
);
|
|
1083
1163
|
}
|
|
1084
1164
|
const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
|
|
1085
1165
|
|
|
@@ -1087,8 +1167,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1087
1167
|
return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
|
|
1088
1168
|
}
|
|
1089
1169
|
|
|
1090
|
-
async getPublicDataWitness(
|
|
1091
|
-
const committedDb = await this
|
|
1170
|
+
async getPublicDataWitness(referenceBlock: BlockParameter, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
|
|
1171
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1092
1172
|
const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
|
|
1093
1173
|
if (!lowLeafResult) {
|
|
1094
1174
|
return undefined;
|
|
@@ -1102,8 +1182,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1102
1182
|
}
|
|
1103
1183
|
}
|
|
1104
1184
|
|
|
1105
|
-
public async getPublicStorageAt(
|
|
1106
|
-
const committedDb = await this
|
|
1185
|
+
public async getPublicStorageAt(referenceBlock: BlockParameter, contract: AztecAddress, slot: Fr): Promise<Fr> {
|
|
1186
|
+
const committedDb = await this.getWorldState(referenceBlock);
|
|
1107
1187
|
const leafSlot = await computePublicDataTreeLeafSlot(contract, slot);
|
|
1108
1188
|
|
|
1109
1189
|
const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
|
|
@@ -1118,14 +1198,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1118
1198
|
}
|
|
1119
1199
|
|
|
1120
1200
|
public async getBlockHeader(block: BlockParameter = 'latest'): Promise<BlockHeader | undefined> {
|
|
1121
|
-
if (
|
|
1201
|
+
if (BlockHash.isBlockHash(block)) {
|
|
1122
1202
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
1123
1203
|
if (block.equals(initialBlockHash)) {
|
|
1124
1204
|
// Block source doesn't handle initial header so we need to handle the case separately.
|
|
1125
1205
|
return this.worldStateSynchronizer.getCommitted().getInitialHeader();
|
|
1126
1206
|
}
|
|
1127
|
-
|
|
1128
|
-
return this.blockSource.getBlockHeaderByHash(blockHashFr);
|
|
1207
|
+
return this.blockSource.getBlockHeaderByHash(block);
|
|
1129
1208
|
} else {
|
|
1130
1209
|
// Block source doesn't handle initial header so we need to handle the case separately.
|
|
1131
1210
|
const blockNumber = block === 'latest' ? await this.getBlockNumber() : (block as BlockNumber);
|
|
@@ -1145,6 +1224,14 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1145
1224
|
return await this.blockSource.getBlockHeaderByArchive(archive);
|
|
1146
1225
|
}
|
|
1147
1226
|
|
|
1227
|
+
public getBlockData(number: BlockNumber): Promise<BlockData | undefined> {
|
|
1228
|
+
return this.blockSource.getBlockData(number);
|
|
1229
|
+
}
|
|
1230
|
+
|
|
1231
|
+
public getBlockDataByArchive(archive: Fr): Promise<BlockData | undefined> {
|
|
1232
|
+
return this.blockSource.getBlockDataByArchive(archive);
|
|
1233
|
+
}
|
|
1234
|
+
|
|
1148
1235
|
/**
|
|
1149
1236
|
* Simulates the public part of a transaction with the current state.
|
|
1150
1237
|
* @param tx - The transaction to simulate.
|
|
@@ -1168,7 +1255,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1168
1255
|
}
|
|
1169
1256
|
|
|
1170
1257
|
const txHash = tx.getTxHash();
|
|
1171
|
-
const
|
|
1258
|
+
const latestBlockNumber = await this.blockSource.getBlockNumber();
|
|
1259
|
+
const blockNumber = BlockNumber.add(latestBlockNumber, 1);
|
|
1172
1260
|
|
|
1173
1261
|
// If sequencer is not initialized, we just set these values to zero for simulation.
|
|
1174
1262
|
const coinbase = EthAddress.ZERO;
|
|
@@ -1183,6 +1271,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1183
1271
|
this.contractDataSource,
|
|
1184
1272
|
new DateProvider(),
|
|
1185
1273
|
this.telemetry,
|
|
1274
|
+
this.log.getBindings(),
|
|
1186
1275
|
);
|
|
1187
1276
|
|
|
1188
1277
|
this.log.verbose(`Simulating public calls for tx ${txHash}`, {
|
|
@@ -1191,6 +1280,8 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1191
1280
|
blockNumber,
|
|
1192
1281
|
});
|
|
1193
1282
|
|
|
1283
|
+
// Ensure world-state has caught up with the latest block we loaded from the archiver
|
|
1284
|
+
await this.worldStateSynchronizer.syncImmediate(latestBlockNumber);
|
|
1194
1285
|
const merkleTreeFork = await this.worldStateSynchronizer.fork();
|
|
1195
1286
|
try {
|
|
1196
1287
|
const config = PublicSimulatorConfig.from({
|
|
@@ -1206,7 +1297,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1206
1297
|
const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
|
|
1207
1298
|
|
|
1208
1299
|
// REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
|
|
1209
|
-
const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([tx]);
|
|
1300
|
+
const [processedTxs, failedTxs, _usedTxs, returns, debugLogs] = await processor.process([tx]);
|
|
1210
1301
|
// REFACTOR: Consider returning the error rather than throwing
|
|
1211
1302
|
if (failedTxs.length) {
|
|
1212
1303
|
this.log.warn(`Simulated tx ${txHash} fails: ${failedTxs[0].error}`, { txHash });
|
|
@@ -1220,6 +1311,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1220
1311
|
processedTx.txEffect,
|
|
1221
1312
|
returns,
|
|
1222
1313
|
processedTx.gasUsed,
|
|
1314
|
+
debugLogs,
|
|
1223
1315
|
);
|
|
1224
1316
|
} finally {
|
|
1225
1317
|
await merkleTreeFork.close();
|
|
@@ -1233,19 +1325,32 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1233
1325
|
const db = this.worldStateSynchronizer.getCommitted();
|
|
1234
1326
|
const verifier = isSimulation ? undefined : this.proofVerifier;
|
|
1235
1327
|
|
|
1236
|
-
// We accept transactions if they are not expired by the next slot (checked based on the
|
|
1328
|
+
// We accept transactions if they are not expired by the next slot (checked based on the ExpirationTimestamp field)
|
|
1237
1329
|
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1238
1330
|
const blockNumber = BlockNumber((await this.blockSource.getBlockNumber()) + 1);
|
|
1239
|
-
const
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1331
|
+
const l1Constants = await this.blockSource.getL1Constants();
|
|
1332
|
+
const validator = createTxValidatorForAcceptingTxsOverRPC(
|
|
1333
|
+
db,
|
|
1334
|
+
this.contractDataSource,
|
|
1335
|
+
verifier,
|
|
1336
|
+
{
|
|
1337
|
+
timestamp: nextSlotTimestamp,
|
|
1338
|
+
blockNumber,
|
|
1339
|
+
l1ChainId: this.l1ChainId,
|
|
1340
|
+
rollupVersion: this.version,
|
|
1341
|
+
setupAllowList: [
|
|
1342
|
+
...(await getDefaultAllowedSetupFunctions()),
|
|
1343
|
+
...(this.config.txPublicSetupAllowListExtend ?? []),
|
|
1344
|
+
],
|
|
1345
|
+
gasFees: await this.getCurrentMinFees(),
|
|
1346
|
+
skipFeeEnforcement,
|
|
1347
|
+
txsPermitted: !this.config.disableTransactions,
|
|
1348
|
+
rollupManaLimit: l1Constants.rollupManaLimit,
|
|
1349
|
+
maxBlockL2Gas: this.config.validateMaxL2BlockGas,
|
|
1350
|
+
maxBlockDAGas: this.config.validateMaxDABlockGas,
|
|
1351
|
+
},
|
|
1352
|
+
this.log.getBindings(),
|
|
1353
|
+
);
|
|
1249
1354
|
|
|
1250
1355
|
return await validator.validateTx(tx);
|
|
1251
1356
|
}
|
|
@@ -1409,7 +1514,129 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1409
1514
|
}
|
|
1410
1515
|
}
|
|
1411
1516
|
|
|
1412
|
-
|
|
1517
|
+
public async reloadKeystore(): Promise<void> {
|
|
1518
|
+
if (!this.config.keyStoreDirectory?.length) {
|
|
1519
|
+
throw new BadRequestError(
|
|
1520
|
+
'Cannot reload keystore: node is not using a file-based keystore. ' +
|
|
1521
|
+
'Set KEY_STORE_DIRECTORY to use file-based keystores.',
|
|
1522
|
+
);
|
|
1523
|
+
}
|
|
1524
|
+
if (!this.validatorClient) {
|
|
1525
|
+
throw new BadRequestError('Cannot reload keystore: validator is not enabled.');
|
|
1526
|
+
}
|
|
1527
|
+
|
|
1528
|
+
this.log.info('Reloading keystore from disk');
|
|
1529
|
+
|
|
1530
|
+
// Re-read and validate keystore files
|
|
1531
|
+
const keyStores = loadKeystores(this.config.keyStoreDirectory);
|
|
1532
|
+
const newManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
1533
|
+
await newManager.validateSigners();
|
|
1534
|
+
ValidatorClient.validateKeyStoreConfiguration(newManager, this.log);
|
|
1535
|
+
|
|
1536
|
+
// Validate that every validator's publisher keys overlap with the L1 signers
|
|
1537
|
+
// that were initialized at startup. Publishers cannot be hot-reloaded, so a
|
|
1538
|
+
// validator with a publisher key that doesn't match any existing L1 signer
|
|
1539
|
+
// would silently fail on every proposer slot.
|
|
1540
|
+
if (this.keyStoreManager && this.sequencer) {
|
|
1541
|
+
const oldAdapter = NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager);
|
|
1542
|
+
const availablePublishers = new Set(
|
|
1543
|
+
oldAdapter
|
|
1544
|
+
.getAttesterAddresses()
|
|
1545
|
+
.flatMap(a => oldAdapter.getPublisherAddresses(a).map(p => p.toString().toLowerCase())),
|
|
1546
|
+
);
|
|
1547
|
+
|
|
1548
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
1549
|
+
for (const attester of newAdapter.getAttesterAddresses()) {
|
|
1550
|
+
const pubs = newAdapter.getPublisherAddresses(attester);
|
|
1551
|
+
if (pubs.length > 0 && !pubs.some(p => availablePublishers.has(p.toString().toLowerCase()))) {
|
|
1552
|
+
throw new BadRequestError(
|
|
1553
|
+
`Cannot reload keystore: validator ${attester} has publisher keys ` +
|
|
1554
|
+
`[${pubs.map(p => p.toString()).join(', ')}] but none match the L1 signers initialized at startup ` +
|
|
1555
|
+
`[${[...availablePublishers].join(', ')}]. Publishers cannot be hot-reloaded — ` +
|
|
1556
|
+
`use an existing publisher key or restart the node.`,
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
|
|
1562
|
+
// Build adapters for old and new keystores to compute diff
|
|
1563
|
+
const newAdapter = NodeKeystoreAdapter.fromKeyStoreManager(newManager);
|
|
1564
|
+
const newAddresses = newAdapter.getAttesterAddresses();
|
|
1565
|
+
const oldAddresses = this.keyStoreManager
|
|
1566
|
+
? NodeKeystoreAdapter.fromKeyStoreManager(this.keyStoreManager).getAttesterAddresses()
|
|
1567
|
+
: [];
|
|
1568
|
+
|
|
1569
|
+
const oldSet = new Set(oldAddresses.map(a => a.toString()));
|
|
1570
|
+
const newSet = new Set(newAddresses.map(a => a.toString()));
|
|
1571
|
+
const added = newAddresses.filter(a => !oldSet.has(a.toString()));
|
|
1572
|
+
const removed = oldAddresses.filter(a => !newSet.has(a.toString()));
|
|
1573
|
+
|
|
1574
|
+
if (added.length > 0) {
|
|
1575
|
+
this.log.info(`Keystore reload: adding attester keys: ${added.map(a => a.toString()).join(', ')}`);
|
|
1576
|
+
}
|
|
1577
|
+
if (removed.length > 0) {
|
|
1578
|
+
this.log.info(`Keystore reload: removing attester keys: ${removed.map(a => a.toString()).join(', ')}`);
|
|
1579
|
+
}
|
|
1580
|
+
if (added.length === 0 && removed.length === 0) {
|
|
1581
|
+
this.log.info('Keystore reload: attester keys unchanged');
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
// Update the validator client (coinbase, feeRecipient, attester keys)
|
|
1585
|
+
this.validatorClient.reloadKeystore(newManager);
|
|
1586
|
+
|
|
1587
|
+
// Update the publisher factory's keystore so newly-added validators
|
|
1588
|
+
// can be matched to existing publisher keys when proposing blocks.
|
|
1589
|
+
if (this.sequencer) {
|
|
1590
|
+
this.sequencer.updatePublisherNodeKeyStore(newAdapter);
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
// Update slasher's "don't-slash-self" list with new validator addresses
|
|
1594
|
+
if (this.slasherClient && !this.config.slashSelfAllowed) {
|
|
1595
|
+
const slashValidatorsNever = unique(
|
|
1596
|
+
[...(this.config.slashValidatorsNever ?? []), ...newAddresses].map(a => a.toString()),
|
|
1597
|
+
).map(EthAddress.fromString);
|
|
1598
|
+
this.slasherClient.updateConfig({ slashValidatorsNever });
|
|
1599
|
+
}
|
|
1600
|
+
|
|
1601
|
+
this.keyStoreManager = newManager;
|
|
1602
|
+
this.log.info('Keystore reloaded: coinbase, feeRecipient, and attester keys updated');
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
public async mineBlock(): Promise<void> {
|
|
1606
|
+
if (!this.sequencer) {
|
|
1607
|
+
throw new BadRequestError('Cannot mine block: no sequencer is running');
|
|
1608
|
+
}
|
|
1609
|
+
|
|
1610
|
+
const currentBlockNumber = await this.getBlockNumber();
|
|
1611
|
+
|
|
1612
|
+
// Use slot duration + 50% buffer as the timeout so this works on running networks too
|
|
1613
|
+
const { slotDuration } = await this.blockSource.getL1Constants();
|
|
1614
|
+
const timeoutSeconds = Math.ceil(slotDuration * 1.5);
|
|
1615
|
+
|
|
1616
|
+
// Temporarily set minTxsPerBlock to 0 so the sequencer produces a block even with no txs
|
|
1617
|
+
const originalMinTxsPerBlock = this.sequencer.getSequencer().getConfig().minTxsPerBlock;
|
|
1618
|
+
this.sequencer.updateConfig({ minTxsPerBlock: 0 });
|
|
1619
|
+
|
|
1620
|
+
try {
|
|
1621
|
+
// Trigger the sequencer to produce a block immediately
|
|
1622
|
+
void this.sequencer.trigger();
|
|
1623
|
+
|
|
1624
|
+
// Wait for the new L2 block to appear
|
|
1625
|
+
await retryUntil(
|
|
1626
|
+
async () => {
|
|
1627
|
+
const newBlockNumber = await this.getBlockNumber();
|
|
1628
|
+
return newBlockNumber > currentBlockNumber ? true : undefined;
|
|
1629
|
+
},
|
|
1630
|
+
'mineBlock',
|
|
1631
|
+
timeoutSeconds,
|
|
1632
|
+
0.1,
|
|
1633
|
+
);
|
|
1634
|
+
} finally {
|
|
1635
|
+
this.sequencer.updateConfig({ minTxsPerBlock: originalMinTxsPerBlock });
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
#getInitialHeaderHash(): Promise<BlockHash> {
|
|
1413
1640
|
if (!this.initialHeaderHashPromise) {
|
|
1414
1641
|
this.initialHeaderHashPromise = this.worldStateSynchronizer.getCommitted().getInitialHeader().hash();
|
|
1415
1642
|
}
|
|
@@ -1421,7 +1648,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1421
1648
|
* @param block - The block parameter (block number, block hash, or 'latest') at which to get the data.
|
|
1422
1649
|
* @returns An instance of a committed MerkleTreeOperations
|
|
1423
1650
|
*/
|
|
1424
|
-
async
|
|
1651
|
+
protected async getWorldState(block: BlockParameter) {
|
|
1425
1652
|
let blockSyncedTo: BlockNumber = BlockNumber.ZERO;
|
|
1426
1653
|
try {
|
|
1427
1654
|
// Attempt to sync the world state if necessary
|
|
@@ -1435,36 +1662,65 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
|
1435
1662
|
return this.worldStateSynchronizer.getCommitted();
|
|
1436
1663
|
}
|
|
1437
1664
|
|
|
1438
|
-
|
|
1665
|
+
// Get the block number, either directly from the parameter or by quering the archiver with the block hash
|
|
1666
|
+
let blockNumber: BlockNumber;
|
|
1667
|
+
if (BlockHash.isBlockHash(block)) {
|
|
1439
1668
|
const initialBlockHash = await this.#getInitialHeaderHash();
|
|
1440
1669
|
if (block.equals(initialBlockHash)) {
|
|
1441
1670
|
// Block source doesn't handle initial header so we need to handle the case separately.
|
|
1442
1671
|
return this.worldStateSynchronizer.getSnapshot(BlockNumber.ZERO);
|
|
1443
1672
|
}
|
|
1444
1673
|
|
|
1445
|
-
const
|
|
1446
|
-
const header = await this.blockSource.getBlockHeaderByHash(blockHashFr);
|
|
1674
|
+
const header = await this.blockSource.getBlockHeaderByHash(block);
|
|
1447
1675
|
if (!header) {
|
|
1448
1676
|
throw new Error(
|
|
1449
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.`,
|
|
1450
1678
|
);
|
|
1451
1679
|
}
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1680
|
+
|
|
1681
|
+
blockNumber = header.getBlockNumber();
|
|
1682
|
+
} else {
|
|
1683
|
+
blockNumber = block as BlockNumber;
|
|
1455
1684
|
}
|
|
1456
1685
|
|
|
1457
|
-
//
|
|
1458
|
-
{
|
|
1459
|
-
|
|
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}).`);
|
|
1689
|
+
}
|
|
1690
|
+
this.log.debug(`Using snapshot for block ${blockNumber}, world state synced upto ${blockSyncedTo}`);
|
|
1460
1691
|
|
|
1461
|
-
|
|
1462
|
-
|
|
1692
|
+
const snapshot = this.worldStateSynchronizer.getSnapshot(blockNumber);
|
|
1693
|
+
|
|
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
|
+
);
|
|
1463
1701
|
}
|
|
1702
|
+
}
|
|
1703
|
+
|
|
1704
|
+
return snapshot;
|
|
1705
|
+
}
|
|
1464
1706
|
|
|
1465
|
-
|
|
1466
|
-
|
|
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();
|
|
1467
1722
|
}
|
|
1723
|
+
return block as BlockNumber;
|
|
1468
1724
|
}
|
|
1469
1725
|
|
|
1470
1726
|
/**
|