@aztec/aztec-node 0.0.0-test.1 → 0.0.1-commit.21caa21
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 +15 -10
- package/dest/aztec-node/config.d.ts.map +1 -1
- package/dest/aztec-node/config.js +75 -14
- package/dest/aztec-node/node_metrics.d.ts +5 -1
- package/dest/aztec-node/node_metrics.d.ts.map +1 -1
- package/dest/aztec-node/node_metrics.js +21 -0
- package/dest/aztec-node/server.d.ts +91 -51
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +525 -218
- package/dest/bin/index.d.ts +1 -1
- package/dest/bin/index.js +4 -2
- package/dest/index.d.ts +1 -2
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +0 -1
- package/dest/sentinel/config.d.ts +8 -0
- package/dest/sentinel/config.d.ts.map +1 -0
- package/dest/sentinel/config.js +29 -0
- package/dest/sentinel/factory.d.ts +9 -0
- package/dest/sentinel/factory.d.ts.map +1 -0
- package/dest/sentinel/factory.js +17 -0
- package/dest/sentinel/index.d.ts +3 -0
- package/dest/sentinel/index.d.ts.map +1 -0
- package/dest/sentinel/index.js +1 -0
- package/dest/sentinel/sentinel.d.ts +92 -0
- package/dest/sentinel/sentinel.d.ts.map +1 -0
- package/dest/sentinel/sentinel.js +398 -0
- package/dest/sentinel/store.d.ts +35 -0
- package/dest/sentinel/store.d.ts.map +1 -0
- package/dest/sentinel/store.js +170 -0
- package/dest/test/index.d.ts +31 -0
- package/dest/test/index.d.ts.map +1 -0
- package/dest/test/index.js +1 -0
- package/package.json +45 -34
- package/src/aztec-node/config.ts +128 -25
- package/src/aztec-node/node_metrics.ts +28 -0
- package/src/aztec-node/server.ts +686 -278
- package/src/bin/index.ts +4 -2
- package/src/index.ts +0 -1
- package/src/sentinel/config.ts +37 -0
- package/src/sentinel/factory.ts +36 -0
- package/src/sentinel/index.ts +8 -0
- package/src/sentinel/sentinel.ts +501 -0
- package/src/sentinel/store.ts +185 -0
- package/src/test/index.ts +32 -0
- package/dest/aztec-node/http_rpc_server.d.ts +0 -8
- package/dest/aztec-node/http_rpc_server.d.ts.map +0 -1
- package/dest/aztec-node/http_rpc_server.js +0 -9
- package/src/aztec-node/http_rpc_server.ts +0 -11
package/src/aztec-node/server.ts
CHANGED
|
@@ -1,39 +1,61 @@
|
|
|
1
|
-
import { createArchiver } from '@aztec/archiver';
|
|
2
|
-
import { BBCircuitVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
|
|
1
|
+
import { Archiver, createArchiver } from '@aztec/archiver';
|
|
2
|
+
import { BBCircuitVerifier, QueuedIVCVerifier, TestCircuitVerifier } from '@aztec/bb-prover';
|
|
3
3
|
import { type BlobSinkClientInterface, createBlobSinkClient } from '@aztec/blob-sink/client';
|
|
4
4
|
import {
|
|
5
|
-
|
|
5
|
+
ARCHIVE_HEIGHT,
|
|
6
6
|
INITIAL_L2_BLOCK_NUM,
|
|
7
7
|
type L1_TO_L2_MSG_TREE_HEIGHT,
|
|
8
8
|
type NOTE_HASH_TREE_HEIGHT,
|
|
9
9
|
type NULLIFIER_TREE_HEIGHT,
|
|
10
10
|
type PUBLIC_DATA_TREE_HEIGHT,
|
|
11
|
-
REGISTERER_CONTRACT_ADDRESS,
|
|
12
11
|
} from '@aztec/constants';
|
|
13
|
-
import { EpochCache } from '@aztec/epoch-cache';
|
|
14
|
-
import {
|
|
15
|
-
|
|
12
|
+
import { EpochCache, type EpochCacheInterface } from '@aztec/epoch-cache';
|
|
13
|
+
import {
|
|
14
|
+
type L1ContractAddresses,
|
|
15
|
+
RegistryContract,
|
|
16
|
+
RollupContract,
|
|
17
|
+
createEthereumChain,
|
|
18
|
+
getPublicClient,
|
|
19
|
+
} from '@aztec/ethereum';
|
|
20
|
+
import { SlotNumber } from '@aztec/foundation/branded-types';
|
|
21
|
+
import { compactArray, pick } from '@aztec/foundation/collection';
|
|
16
22
|
import { EthAddress } from '@aztec/foundation/eth-address';
|
|
17
23
|
import { Fr } from '@aztec/foundation/fields';
|
|
24
|
+
import { BadRequestError } from '@aztec/foundation/json-rpc';
|
|
18
25
|
import { type Logger, createLogger } from '@aztec/foundation/log';
|
|
26
|
+
import { count } from '@aztec/foundation/string';
|
|
19
27
|
import { DateProvider, Timer } from '@aztec/foundation/timer';
|
|
20
|
-
import { SiblingPath } from '@aztec/foundation/trees';
|
|
21
|
-
import
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import { type P2P, createP2PClient } from '@aztec/p2p';
|
|
28
|
+
import { MembershipWitness, SiblingPath } from '@aztec/foundation/trees';
|
|
29
|
+
import { KeystoreManager, loadKeystores, mergeKeystores } from '@aztec/node-keystore';
|
|
30
|
+
import { trySnapshotSync, uploadSnapshot } from '@aztec/node-lib/actions';
|
|
31
|
+
import { createL1TxUtilsWithBlobsFromEthSigner } from '@aztec/node-lib/factories';
|
|
32
|
+
import { type P2P, type P2PClientDeps, createP2PClient, getDefaultAllowedSetupFunctions } from '@aztec/p2p';
|
|
25
33
|
import { ProtocolContractAddress } from '@aztec/protocol-contracts';
|
|
26
34
|
import {
|
|
35
|
+
BlockBuilder,
|
|
27
36
|
GlobalVariableBuilder,
|
|
28
37
|
SequencerClient,
|
|
29
38
|
type SequencerPublisher,
|
|
30
|
-
createSlasherClient,
|
|
31
39
|
createValidatorForAcceptingTxs,
|
|
32
|
-
getDefaultAllowedSetupFunctions,
|
|
33
40
|
} from '@aztec/sequencer-client';
|
|
34
41
|
import { PublicProcessorFactory } from '@aztec/simulator/server';
|
|
42
|
+
import {
|
|
43
|
+
AttestationsBlockWatcher,
|
|
44
|
+
EpochPruneWatcher,
|
|
45
|
+
type SlasherClientInterface,
|
|
46
|
+
type Watcher,
|
|
47
|
+
createSlasher,
|
|
48
|
+
} from '@aztec/slasher';
|
|
49
|
+
import { PublicSimulatorConfig } from '@aztec/stdlib/avm';
|
|
35
50
|
import { AztecAddress } from '@aztec/stdlib/aztec-address';
|
|
36
|
-
import
|
|
51
|
+
import {
|
|
52
|
+
type InBlock,
|
|
53
|
+
type L2Block,
|
|
54
|
+
L2BlockHash,
|
|
55
|
+
type L2BlockNumber,
|
|
56
|
+
type L2BlockSource,
|
|
57
|
+
type PublishedL2Block,
|
|
58
|
+
} from '@aztec/stdlib/block';
|
|
37
59
|
import type {
|
|
38
60
|
ContractClassPublic,
|
|
39
61
|
ContractDataSource,
|
|
@@ -42,33 +64,43 @@ import type {
|
|
|
42
64
|
ProtocolContractAddresses,
|
|
43
65
|
} from '@aztec/stdlib/contract';
|
|
44
66
|
import type { GasFees } from '@aztec/stdlib/gas';
|
|
45
|
-
import { computePublicDataTreeLeafSlot
|
|
46
|
-
import
|
|
67
|
+
import { computePublicDataTreeLeafSlot } from '@aztec/stdlib/hash';
|
|
68
|
+
import {
|
|
69
|
+
type AztecNode,
|
|
70
|
+
type AztecNodeAdmin,
|
|
71
|
+
type AztecNodeAdminConfig,
|
|
72
|
+
AztecNodeAdminConfigSchema,
|
|
73
|
+
type GetContractClassLogsResponse,
|
|
74
|
+
type GetPublicLogsResponse,
|
|
75
|
+
} from '@aztec/stdlib/interfaces/client';
|
|
47
76
|
import {
|
|
77
|
+
type AllowedElement,
|
|
48
78
|
type ClientProtocolCircuitVerifier,
|
|
49
79
|
type L2LogsSource,
|
|
50
|
-
type ProverConfig,
|
|
51
|
-
type SequencerConfig,
|
|
52
80
|
type Service,
|
|
53
81
|
type WorldStateSyncStatus,
|
|
54
82
|
type WorldStateSynchronizer,
|
|
55
83
|
tryStop,
|
|
56
84
|
} from '@aztec/stdlib/interfaces/server';
|
|
57
85
|
import type { LogFilter, PrivateLog, TxScopedL2Log } from '@aztec/stdlib/logs';
|
|
58
|
-
import type
|
|
86
|
+
import { InboxLeaf, type L1ToL2MessageSource } from '@aztec/stdlib/messaging';
|
|
59
87
|
import { P2PClientType } from '@aztec/stdlib/p2p';
|
|
60
|
-
import {
|
|
88
|
+
import type { Offense, SlashPayloadRound } from '@aztec/stdlib/slashing';
|
|
61
89
|
import type { NullifierLeafPreimage, PublicDataTreeLeaf, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
|
|
90
|
+
import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
|
|
62
91
|
import {
|
|
63
92
|
type BlockHeader,
|
|
93
|
+
type GlobalVariableBuilder as GlobalVariableBuilderInterface,
|
|
94
|
+
type IndexedTxEffect,
|
|
64
95
|
PublicSimulationOutput,
|
|
65
96
|
Tx,
|
|
66
|
-
TxEffect,
|
|
67
97
|
type TxHash,
|
|
68
98
|
TxReceipt,
|
|
69
99
|
TxStatus,
|
|
70
100
|
type TxValidationResult,
|
|
71
101
|
} from '@aztec/stdlib/tx';
|
|
102
|
+
import { getPackageVersion } from '@aztec/stdlib/update-checker';
|
|
103
|
+
import type { SingleValidatorStats, ValidatorsStats } from '@aztec/stdlib/validators';
|
|
72
104
|
import {
|
|
73
105
|
Attributes,
|
|
74
106
|
type TelemetryClient,
|
|
@@ -77,19 +109,30 @@ import {
|
|
|
77
109
|
getTelemetryClient,
|
|
78
110
|
trackSpan,
|
|
79
111
|
} from '@aztec/telemetry-client';
|
|
80
|
-
import {
|
|
112
|
+
import {
|
|
113
|
+
NodeKeystoreAdapter,
|
|
114
|
+
ValidatorClient,
|
|
115
|
+
createBlockProposalHandler,
|
|
116
|
+
createValidatorClient,
|
|
117
|
+
} from '@aztec/validator-client';
|
|
81
118
|
import { createWorldStateSynchronizer } from '@aztec/world-state';
|
|
82
119
|
|
|
83
|
-
import {
|
|
120
|
+
import { createPublicClient, fallback, http } from 'viem';
|
|
121
|
+
|
|
122
|
+
import { createSentinel } from '../sentinel/factory.js';
|
|
123
|
+
import { Sentinel } from '../sentinel/sentinel.js';
|
|
124
|
+
import { type AztecNodeConfig, createKeyStoreForValidator } from './config.js';
|
|
84
125
|
import { NodeMetrics } from './node_metrics.js';
|
|
85
126
|
|
|
86
127
|
/**
|
|
87
128
|
* The aztec node.
|
|
88
129
|
*/
|
|
89
|
-
export class AztecNodeService implements AztecNode, Traceable {
|
|
90
|
-
private packageVersion: string;
|
|
130
|
+
export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
|
|
91
131
|
private metrics: NodeMetrics;
|
|
92
132
|
|
|
133
|
+
// Prevent two snapshot operations to happen simultaneously
|
|
134
|
+
private isUploadingSnapshot = false;
|
|
135
|
+
|
|
93
136
|
public readonly tracer: Tracer;
|
|
94
137
|
|
|
95
138
|
constructor(
|
|
@@ -99,17 +142,20 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
99
142
|
protected readonly logsSource: L2LogsSource,
|
|
100
143
|
protected readonly contractDataSource: ContractDataSource,
|
|
101
144
|
protected readonly l1ToL2MessageSource: L1ToL2MessageSource,
|
|
102
|
-
protected readonly nullifierSource: NullifierWithBlockSource,
|
|
103
145
|
protected readonly worldStateSynchronizer: WorldStateSynchronizer,
|
|
104
146
|
protected readonly sequencer: SequencerClient | undefined,
|
|
147
|
+
protected readonly slasherClient: SlasherClientInterface | undefined,
|
|
148
|
+
protected readonly validatorsSentinel: Sentinel | undefined,
|
|
149
|
+
protected readonly epochPruneWatcher: EpochPruneWatcher | undefined,
|
|
105
150
|
protected readonly l1ChainId: number,
|
|
106
151
|
protected readonly version: number,
|
|
107
|
-
protected readonly globalVariableBuilder:
|
|
152
|
+
protected readonly globalVariableBuilder: GlobalVariableBuilderInterface,
|
|
153
|
+
protected readonly epochCache: EpochCacheInterface,
|
|
154
|
+
protected readonly packageVersion: string,
|
|
108
155
|
private proofVerifier: ClientProtocolCircuitVerifier,
|
|
109
156
|
private telemetry: TelemetryClient = getTelemetryClient(),
|
|
110
157
|
private log = createLogger('node'),
|
|
111
158
|
) {
|
|
112
|
-
this.packageVersion = getPackageVersion();
|
|
113
159
|
this.metrics = new NodeMetrics(telemetry, 'AztecNodeService');
|
|
114
160
|
this.tracer = telemetry.getTracer('AztecNodeService');
|
|
115
161
|
|
|
@@ -132,31 +178,104 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
132
178
|
* @returns - A fully synced Aztec Node for use in development/testing.
|
|
133
179
|
*/
|
|
134
180
|
public static async createAndSync(
|
|
135
|
-
|
|
181
|
+
inputConfig: AztecNodeConfig,
|
|
136
182
|
deps: {
|
|
137
183
|
telemetry?: TelemetryClient;
|
|
138
184
|
logger?: Logger;
|
|
139
185
|
publisher?: SequencerPublisher;
|
|
140
186
|
dateProvider?: DateProvider;
|
|
141
187
|
blobSinkClient?: BlobSinkClientInterface;
|
|
188
|
+
p2pClientDeps?: P2PClientDeps<P2PClientType.Full>;
|
|
142
189
|
} = {},
|
|
143
190
|
options: {
|
|
144
191
|
prefilledPublicData?: PublicDataTreeLeaf[];
|
|
192
|
+
dontStartSequencer?: boolean;
|
|
145
193
|
} = {},
|
|
146
194
|
): Promise<AztecNodeService> {
|
|
147
|
-
const
|
|
195
|
+
const config = { ...inputConfig }; // Copy the config so we dont mutate the input object
|
|
148
196
|
const log = deps.logger ?? createLogger('node');
|
|
197
|
+
const packageVersion = getPackageVersion() ?? '';
|
|
198
|
+
const telemetry = deps.telemetry ?? getTelemetryClient();
|
|
149
199
|
const dateProvider = deps.dateProvider ?? new DateProvider();
|
|
150
|
-
const blobSinkClient =
|
|
200
|
+
const blobSinkClient =
|
|
201
|
+
deps.blobSinkClient ?? createBlobSinkClient(config, { logger: createLogger('node:blob-sink:client') });
|
|
151
202
|
const ethereumChain = createEthereumChain(config.l1RpcUrls, config.l1ChainId);
|
|
152
|
-
|
|
203
|
+
|
|
204
|
+
// Build a key store from file if given or from environment otherwise
|
|
205
|
+
let keyStoreManager: KeystoreManager | undefined;
|
|
206
|
+
const keyStoreProvided = config.keyStoreDirectory !== undefined && config.keyStoreDirectory.length > 0;
|
|
207
|
+
if (keyStoreProvided) {
|
|
208
|
+
const keyStores = loadKeystores(config.keyStoreDirectory!);
|
|
209
|
+
keyStoreManager = new KeystoreManager(mergeKeystores(keyStores));
|
|
210
|
+
} else {
|
|
211
|
+
const keyStore = createKeyStoreForValidator(config);
|
|
212
|
+
if (keyStore) {
|
|
213
|
+
keyStoreManager = new KeystoreManager(keyStore);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
await keyStoreManager?.validateSigners();
|
|
218
|
+
|
|
219
|
+
// If we are a validator, verify our configuration before doing too much more.
|
|
220
|
+
if (!config.disableValidator) {
|
|
221
|
+
if (keyStoreManager === undefined) {
|
|
222
|
+
throw new Error('Failed to create key store, a requirement for running a validator');
|
|
223
|
+
}
|
|
224
|
+
if (!keyStoreProvided) {
|
|
225
|
+
log.warn(
|
|
226
|
+
'KEY STORE CREATED FROM ENVIRONMENT, IT IS RECOMMENDED TO USE A FILE-BASED KEY STORE IN PRODUCTION ENVIRONMENTS',
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
ValidatorClient.validateKeyStoreConfiguration(keyStoreManager, log);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// validate that the actual chain id matches that specified in configuration
|
|
153
233
|
if (config.l1ChainId !== ethereumChain.chainInfo.id) {
|
|
154
234
|
throw new Error(
|
|
155
235
|
`RPC URL configured for chain id ${ethereumChain.chainInfo.id} but expected id ${config.l1ChainId}`,
|
|
156
236
|
);
|
|
157
237
|
}
|
|
158
238
|
|
|
159
|
-
const
|
|
239
|
+
const publicClient = createPublicClient({
|
|
240
|
+
chain: ethereumChain.chainInfo,
|
|
241
|
+
transport: fallback(config.l1RpcUrls.map((url: string) => http(url))),
|
|
242
|
+
pollingInterval: config.viemPollingIntervalMS,
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
const l1ContractsAddresses = await RegistryContract.collectAddresses(
|
|
246
|
+
publicClient,
|
|
247
|
+
config.l1Contracts.registryAddress,
|
|
248
|
+
config.rollupVersion ?? 'canonical',
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
// Overwrite the passed in vars.
|
|
252
|
+
config.l1Contracts = { ...config.l1Contracts, ...l1ContractsAddresses };
|
|
253
|
+
|
|
254
|
+
const rollupContract = new RollupContract(publicClient, config.l1Contracts.rollupAddress.toString());
|
|
255
|
+
const [l1GenesisTime, slotDuration, rollupVersionFromRollup] = await Promise.all([
|
|
256
|
+
rollupContract.getL1GenesisTime(),
|
|
257
|
+
rollupContract.getSlotDuration(),
|
|
258
|
+
rollupContract.getVersion(),
|
|
259
|
+
] as const);
|
|
260
|
+
|
|
261
|
+
config.rollupVersion ??= Number(rollupVersionFromRollup);
|
|
262
|
+
|
|
263
|
+
if (config.rollupVersion !== Number(rollupVersionFromRollup)) {
|
|
264
|
+
log.warn(
|
|
265
|
+
`Registry looked up and returned a rollup with version (${config.rollupVersion}), but this does not match with version detected from the rollup directly: (${rollupVersionFromRollup}).`,
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// attempt snapshot sync if possible
|
|
270
|
+
await trySnapshotSync(config, log);
|
|
271
|
+
|
|
272
|
+
const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider });
|
|
273
|
+
|
|
274
|
+
const archiver = await createArchiver(
|
|
275
|
+
config,
|
|
276
|
+
{ blobSinkClient, epochCache, telemetry, dateProvider },
|
|
277
|
+
{ blockUntilSync: !config.skipArchiverInitialSync },
|
|
278
|
+
);
|
|
160
279
|
|
|
161
280
|
// now create the merkle trees and the world state synchronizer
|
|
162
281
|
const worldStateSynchronizer = await createWorldStateSynchronizer(
|
|
@@ -165,12 +284,13 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
165
284
|
options.prefilledPublicData,
|
|
166
285
|
telemetry,
|
|
167
286
|
);
|
|
168
|
-
const
|
|
287
|
+
const circuitVerifier = config.realProofs
|
|
288
|
+
? await BBCircuitVerifier.new(config)
|
|
289
|
+
: new TestCircuitVerifier(config.proverTestVerificationDelayMs);
|
|
169
290
|
if (!config.realProofs) {
|
|
170
291
|
log.warn(`Aztec node is accepting fake proofs`);
|
|
171
292
|
}
|
|
172
|
-
|
|
173
|
-
const epochCache = await EpochCache.create(config.l1Contracts.rollupAddress, config, { dateProvider });
|
|
293
|
+
const proofVerifier = new QueuedIVCVerifier(config, circuitVerifier);
|
|
174
294
|
|
|
175
295
|
// create the tx pool and the p2p client, which will need the l2 block source
|
|
176
296
|
const p2pClient = await createP2PClient(
|
|
@@ -180,33 +300,160 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
180
300
|
proofVerifier,
|
|
181
301
|
worldStateSynchronizer,
|
|
182
302
|
epochCache,
|
|
303
|
+
packageVersion,
|
|
304
|
+
dateProvider,
|
|
183
305
|
telemetry,
|
|
306
|
+
deps.p2pClientDeps,
|
|
184
307
|
);
|
|
185
308
|
|
|
186
|
-
|
|
309
|
+
// We should really not be modifying the config object
|
|
310
|
+
config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
|
|
187
311
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
312
|
+
const blockBuilder = new BlockBuilder(
|
|
313
|
+
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
|
|
314
|
+
worldStateSynchronizer,
|
|
315
|
+
archiver,
|
|
316
|
+
dateProvider,
|
|
317
|
+
telemetry,
|
|
318
|
+
);
|
|
191
319
|
|
|
192
|
-
|
|
320
|
+
// We'll accumulate sentinel watchers here
|
|
321
|
+
const watchers: Watcher[] = [];
|
|
193
322
|
|
|
194
|
-
//
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
323
|
+
// Create validator client if required
|
|
324
|
+
const validatorClient = createValidatorClient(config, {
|
|
325
|
+
p2pClient,
|
|
326
|
+
telemetry,
|
|
327
|
+
dateProvider,
|
|
328
|
+
epochCache,
|
|
329
|
+
blockBuilder,
|
|
330
|
+
blockSource: archiver,
|
|
331
|
+
l1ToL2MessageSource: archiver,
|
|
332
|
+
keyStoreManager,
|
|
333
|
+
});
|
|
334
|
+
|
|
335
|
+
// If we have a validator client, register it as a source of offenses for the slasher,
|
|
336
|
+
// and have it register callbacks on the p2p client *before* we start it, otherwise messages
|
|
337
|
+
// like attestations or auths will fail.
|
|
338
|
+
if (validatorClient) {
|
|
339
|
+
watchers.push(validatorClient);
|
|
340
|
+
if (!options.dontStartSequencer) {
|
|
341
|
+
await validatorClient.registerHandlers();
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// If there's no validator client but alwaysReexecuteBlockProposals is enabled,
|
|
346
|
+
// create a BlockProposalHandler to reexecute block proposals for monitoring
|
|
347
|
+
if (!validatorClient && config.alwaysReexecuteBlockProposals) {
|
|
348
|
+
log.info('Setting up block proposal reexecution for monitoring');
|
|
349
|
+
createBlockProposalHandler(config, {
|
|
350
|
+
blockBuilder,
|
|
351
|
+
epochCache,
|
|
352
|
+
blockSource: archiver,
|
|
353
|
+
l1ToL2MessageSource: archiver,
|
|
354
|
+
p2pClient,
|
|
355
|
+
dateProvider,
|
|
356
|
+
telemetry,
|
|
357
|
+
}).registerForReexecution(p2pClient);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// Start world state and wait for it to sync to the archiver.
|
|
361
|
+
await worldStateSynchronizer.start();
|
|
362
|
+
|
|
363
|
+
// Start p2p. Note that it depends on world state to be running.
|
|
364
|
+
await p2pClient.start();
|
|
365
|
+
|
|
366
|
+
const validatorsSentinel = await createSentinel(epochCache, archiver, p2pClient, config);
|
|
367
|
+
if (validatorsSentinel && config.slashInactivityPenalty > 0n) {
|
|
368
|
+
watchers.push(validatorsSentinel);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
let epochPruneWatcher: EpochPruneWatcher | undefined;
|
|
372
|
+
if (config.slashPrunePenalty > 0n || config.slashDataWithholdingPenalty > 0n) {
|
|
373
|
+
epochPruneWatcher = new EpochPruneWatcher(
|
|
374
|
+
archiver,
|
|
375
|
+
archiver,
|
|
376
|
+
epochCache,
|
|
377
|
+
p2pClient.getTxProvider(),
|
|
378
|
+
blockBuilder,
|
|
379
|
+
config,
|
|
380
|
+
);
|
|
381
|
+
watchers.push(epochPruneWatcher);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// We assume we want to slash for invalid attestations unless all max penalties are set to 0
|
|
385
|
+
let attestationsBlockWatcher: AttestationsBlockWatcher | undefined;
|
|
386
|
+
if (config.slashProposeInvalidAttestationsPenalty > 0n || config.slashAttestDescendantOfInvalidPenalty > 0n) {
|
|
387
|
+
attestationsBlockWatcher = new AttestationsBlockWatcher(archiver, epochCache, config);
|
|
388
|
+
watchers.push(attestationsBlockWatcher);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// Start p2p-related services once the archiver has completed sync
|
|
392
|
+
void archiver
|
|
393
|
+
.waitForInitialSync()
|
|
394
|
+
.then(async () => {
|
|
395
|
+
await p2pClient.start();
|
|
396
|
+
await validatorsSentinel?.start();
|
|
397
|
+
await epochPruneWatcher?.start();
|
|
398
|
+
await attestationsBlockWatcher?.start();
|
|
399
|
+
log.info(`All p2p services started`);
|
|
400
|
+
})
|
|
401
|
+
.catch(err => log.error('Failed to start p2p services after archiver sync', err));
|
|
402
|
+
|
|
403
|
+
// Validator enabled, create/start relevant service
|
|
404
|
+
let sequencer: SequencerClient | undefined;
|
|
405
|
+
let slasherClient: SlasherClientInterface | undefined;
|
|
406
|
+
if (!config.disableValidator) {
|
|
407
|
+
// We create a slasher only if we have a sequencer, since all slashing actions go through the sequencer publisher
|
|
408
|
+
// as they are executed when the node is selected as proposer.
|
|
409
|
+
const validatorAddresses = keyStoreManager
|
|
410
|
+
? NodeKeystoreAdapter.fromKeyStoreManager(keyStoreManager).getAddresses()
|
|
411
|
+
: [];
|
|
412
|
+
|
|
413
|
+
slasherClient = await createSlasher(
|
|
414
|
+
config,
|
|
415
|
+
config.l1Contracts,
|
|
416
|
+
getPublicClient(config),
|
|
417
|
+
watchers,
|
|
418
|
+
dateProvider,
|
|
419
|
+
epochCache,
|
|
420
|
+
validatorAddresses,
|
|
421
|
+
undefined, // logger
|
|
422
|
+
);
|
|
423
|
+
await slasherClient.start();
|
|
424
|
+
|
|
425
|
+
const l1TxUtils = await createL1TxUtilsWithBlobsFromEthSigner(
|
|
426
|
+
publicClient,
|
|
427
|
+
keyStoreManager!.createAllValidatorPublisherSigners(),
|
|
428
|
+
{ ...config, scope: 'sequencer' },
|
|
429
|
+
{ telemetry, logger: log.createChild('l1-tx-utils'), dateProvider },
|
|
430
|
+
);
|
|
431
|
+
|
|
432
|
+
// Create and start the sequencer client
|
|
433
|
+
sequencer = await SequencerClient.new(config, {
|
|
434
|
+
...deps,
|
|
435
|
+
epochCache,
|
|
436
|
+
l1TxUtils,
|
|
437
|
+
validatorClient,
|
|
438
|
+
p2pClient,
|
|
439
|
+
worldStateSynchronizer,
|
|
440
|
+
slasherClient,
|
|
441
|
+
blockBuilder,
|
|
442
|
+
l2BlockSource: archiver,
|
|
443
|
+
l1ToL2MessageSource: archiver,
|
|
444
|
+
telemetry,
|
|
445
|
+
dateProvider,
|
|
446
|
+
blobSinkClient,
|
|
447
|
+
nodeKeyStore: keyStoreManager!,
|
|
448
|
+
});
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
if (!options.dontStartSequencer && sequencer) {
|
|
452
|
+
await sequencer.start();
|
|
453
|
+
log.verbose(`Sequencer started`);
|
|
454
|
+
} else if (sequencer) {
|
|
455
|
+
log.warn(`Sequencer created but not started`);
|
|
456
|
+
}
|
|
210
457
|
|
|
211
458
|
return new AztecNodeService(
|
|
212
459
|
config,
|
|
@@ -215,12 +462,16 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
215
462
|
archiver,
|
|
216
463
|
archiver,
|
|
217
464
|
archiver,
|
|
218
|
-
archiver,
|
|
219
465
|
worldStateSynchronizer,
|
|
220
466
|
sequencer,
|
|
467
|
+
slasherClient,
|
|
468
|
+
validatorsSentinel,
|
|
469
|
+
epochPruneWatcher,
|
|
221
470
|
ethereumChain.chainInfo.id,
|
|
222
|
-
config.
|
|
471
|
+
config.rollupVersion,
|
|
223
472
|
new GlobalVariableBuilder(config),
|
|
473
|
+
epochCache,
|
|
474
|
+
packageVersion,
|
|
224
475
|
proofVerifier,
|
|
225
476
|
telemetry,
|
|
226
477
|
log,
|
|
@@ -259,6 +510,10 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
259
510
|
return Promise.resolve(this.p2pClient.getEnr()?.encodeTxt());
|
|
260
511
|
}
|
|
261
512
|
|
|
513
|
+
public async getAllowedPublicSetup(): Promise<AllowedElement[]> {
|
|
514
|
+
return this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
|
|
515
|
+
}
|
|
516
|
+
|
|
262
517
|
/**
|
|
263
518
|
* Method to determine if the node is ready to accept transactions.
|
|
264
519
|
* @returns - Flag indicating the readiness for tx submission.
|
|
@@ -268,20 +523,19 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
268
523
|
}
|
|
269
524
|
|
|
270
525
|
public async getNodeInfo(): Promise<NodeInfo> {
|
|
271
|
-
const [nodeVersion,
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
]);
|
|
526
|
+
const [nodeVersion, rollupVersion, chainId, enr, contractAddresses, protocolContractAddresses] = await Promise.all([
|
|
527
|
+
this.getNodeVersion(),
|
|
528
|
+
this.getVersion(),
|
|
529
|
+
this.getChainId(),
|
|
530
|
+
this.getEncodedEnr(),
|
|
531
|
+
this.getL1ContractAddresses(),
|
|
532
|
+
this.getProtocolContractAddresses(),
|
|
533
|
+
]);
|
|
280
534
|
|
|
281
535
|
const nodeInfo: NodeInfo = {
|
|
282
536
|
nodeVersion,
|
|
283
537
|
l1ChainId: chainId,
|
|
284
|
-
|
|
538
|
+
rollupVersion,
|
|
285
539
|
enr,
|
|
286
540
|
l1ContractAddresses: contractAddresses,
|
|
287
541
|
protocolContractAddresses: protocolContractAddresses,
|
|
@@ -295,8 +549,29 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
295
549
|
* @param number - The block number being requested.
|
|
296
550
|
* @returns The requested block.
|
|
297
551
|
*/
|
|
298
|
-
public async getBlock(number:
|
|
299
|
-
|
|
552
|
+
public async getBlock(number: L2BlockNumber): Promise<L2Block | undefined> {
|
|
553
|
+
const blockNumber = number === 'latest' ? await this.getBlockNumber() : number;
|
|
554
|
+
return await this.blockSource.getBlock(blockNumber);
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Get a block specified by its hash.
|
|
559
|
+
* @param blockHash - The block hash being requested.
|
|
560
|
+
* @returns The requested block.
|
|
561
|
+
*/
|
|
562
|
+
public async getBlockByHash(blockHash: Fr): Promise<L2Block | undefined> {
|
|
563
|
+
const publishedBlock = await this.blockSource.getPublishedBlockByHash(blockHash);
|
|
564
|
+
return publishedBlock?.block;
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Get a block specified by its archive root.
|
|
569
|
+
* @param archive - The archive root being requested.
|
|
570
|
+
* @returns The requested block.
|
|
571
|
+
*/
|
|
572
|
+
public async getBlockByArchive(archive: Fr): Promise<L2Block | undefined> {
|
|
573
|
+
const publishedBlock = await this.blockSource.getPublishedBlockByArchive(archive);
|
|
574
|
+
return publishedBlock?.block;
|
|
300
575
|
}
|
|
301
576
|
|
|
302
577
|
/**
|
|
@@ -309,6 +584,10 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
309
584
|
return (await this.blockSource.getBlocks(from, limit)) ?? [];
|
|
310
585
|
}
|
|
311
586
|
|
|
587
|
+
public async getPublishedBlocks(from: number, limit: number): Promise<PublishedL2Block[]> {
|
|
588
|
+
return (await this.blockSource.getPublishedBlocks(from, limit)) ?? [];
|
|
589
|
+
}
|
|
590
|
+
|
|
312
591
|
/**
|
|
313
592
|
* Method to fetch the current base fees.
|
|
314
593
|
* @returns The current base fees.
|
|
@@ -318,7 +597,7 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
318
597
|
}
|
|
319
598
|
|
|
320
599
|
/**
|
|
321
|
-
* Method to fetch the
|
|
600
|
+
* Method to fetch the latest block number synchronized by the node.
|
|
322
601
|
* @returns The block number.
|
|
323
602
|
*/
|
|
324
603
|
public async getBlockNumber(): Promise<number> {
|
|
@@ -353,25 +632,8 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
353
632
|
return Promise.resolve(this.l1ChainId);
|
|
354
633
|
}
|
|
355
634
|
|
|
356
|
-
public
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
// TODO(#10007): Remove this check. This is needed only because we're manually registering
|
|
360
|
-
// some contracts in the archiver so they are available to all nodes (see `registerCommonContracts`
|
|
361
|
-
// in `archiver/src/factory.ts`), but we still want clients to send the registration tx in order
|
|
362
|
-
// to emit the corresponding nullifier, which is now being checked. Note that this method
|
|
363
|
-
// is only called by the PXE to check if a contract is publicly registered.
|
|
364
|
-
if (klazz) {
|
|
365
|
-
const classNullifier = await siloNullifier(AztecAddress.fromNumber(REGISTERER_CONTRACT_ADDRESS), id);
|
|
366
|
-
const worldState = await this.#getWorldState('latest');
|
|
367
|
-
const [index] = await worldState.findLeafIndices(MerkleTreeId.NULLIFIER_TREE, [classNullifier.toBuffer()]);
|
|
368
|
-
this.log.debug(`Registration nullifier ${classNullifier} for contract class ${id} found at index ${index}`);
|
|
369
|
-
if (index === undefined) {
|
|
370
|
-
return undefined;
|
|
371
|
-
}
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return klazz;
|
|
635
|
+
public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
|
|
636
|
+
return this.contractDataSource.getContractClass(id);
|
|
375
637
|
}
|
|
376
638
|
|
|
377
639
|
public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
|
|
@@ -391,11 +653,12 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
391
653
|
/**
|
|
392
654
|
* Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
|
|
393
655
|
* @param tags - The tags to filter the logs by.
|
|
656
|
+
* @param logsPerTag - The maximum number of logs to return for each tag. By default no limit is set
|
|
394
657
|
* @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
|
|
395
658
|
* that tag.
|
|
396
659
|
*/
|
|
397
|
-
public getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
|
|
398
|
-
return this.logsSource.getLogsByTags(tags);
|
|
660
|
+
public getLogsByTags(tags: Fr[], logsPerTag?: number): Promise<TxScopedL2Log[][]> {
|
|
661
|
+
return this.logsSource.getLogsByTags(tags, logsPerTag);
|
|
399
662
|
}
|
|
400
663
|
|
|
401
664
|
/**
|
|
@@ -421,17 +684,19 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
421
684
|
* @param tx - The transaction to be submitted.
|
|
422
685
|
*/
|
|
423
686
|
public async sendTx(tx: Tx) {
|
|
687
|
+
await this.#sendTx(tx);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
async #sendTx(tx: Tx) {
|
|
424
691
|
const timer = new Timer();
|
|
425
|
-
const txHash =
|
|
692
|
+
const txHash = tx.getTxHash().toString();
|
|
426
693
|
|
|
427
694
|
const valid = await this.isValidTx(tx);
|
|
428
695
|
if (valid.result !== 'valid') {
|
|
429
696
|
const reason = valid.reason.join(', ');
|
|
430
697
|
this.metrics.receivedTx(timer.ms(), false);
|
|
431
|
-
this.log.warn(`
|
|
432
|
-
|
|
433
|
-
// throw new Error(`Invalid tx: ${reason}`);
|
|
434
|
-
return;
|
|
698
|
+
this.log.warn(`Received invalid tx ${txHash}: ${reason}`, { txHash });
|
|
699
|
+
throw new Error(`Invalid tx: ${reason}`);
|
|
435
700
|
}
|
|
436
701
|
|
|
437
702
|
await this.p2pClient!.sendTx(tx);
|
|
@@ -457,7 +722,7 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
457
722
|
return txReceipt;
|
|
458
723
|
}
|
|
459
724
|
|
|
460
|
-
public getTxEffect(txHash: TxHash): Promise<
|
|
725
|
+
public getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
|
|
461
726
|
return this.blockSource.getTxEffect(txHash);
|
|
462
727
|
}
|
|
463
728
|
|
|
@@ -465,89 +730,120 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
465
730
|
* Method to stop the aztec node.
|
|
466
731
|
*/
|
|
467
732
|
public async stop() {
|
|
468
|
-
this.log.info(`Stopping`);
|
|
469
|
-
await this.
|
|
470
|
-
await this.
|
|
471
|
-
await this.
|
|
733
|
+
this.log.info(`Stopping Aztec Node`);
|
|
734
|
+
await tryStop(this.validatorsSentinel);
|
|
735
|
+
await tryStop(this.epochPruneWatcher);
|
|
736
|
+
await tryStop(this.slasherClient);
|
|
737
|
+
await tryStop(this.proofVerifier);
|
|
738
|
+
await tryStop(this.sequencer);
|
|
739
|
+
await tryStop(this.p2pClient);
|
|
740
|
+
await tryStop(this.worldStateSynchronizer);
|
|
472
741
|
await tryStop(this.blockSource);
|
|
473
|
-
await this.telemetry
|
|
474
|
-
this.log.info(`Stopped`);
|
|
742
|
+
await tryStop(this.telemetry);
|
|
743
|
+
this.log.info(`Stopped Aztec Node`);
|
|
475
744
|
}
|
|
476
745
|
|
|
477
746
|
/**
|
|
478
747
|
* Method to retrieve pending txs.
|
|
748
|
+
* @param limit - The number of items to returns
|
|
749
|
+
* @param after - The last known pending tx. Used for pagination
|
|
479
750
|
* @returns - The pending txs.
|
|
480
751
|
*/
|
|
481
|
-
public getPendingTxs() {
|
|
482
|
-
return this.p2pClient!.getPendingTxs();
|
|
752
|
+
public getPendingTxs(limit?: number, after?: TxHash): Promise<Tx[]> {
|
|
753
|
+
return this.p2pClient!.getPendingTxs(limit, after);
|
|
483
754
|
}
|
|
484
755
|
|
|
485
|
-
public
|
|
486
|
-
|
|
487
|
-
return pendingTxs.length;
|
|
756
|
+
public getPendingTxCount(): Promise<number> {
|
|
757
|
+
return this.p2pClient!.getPendingTxCount();
|
|
488
758
|
}
|
|
489
759
|
|
|
490
760
|
/**
|
|
491
|
-
* Method to retrieve a single tx from the mempool or
|
|
761
|
+
* Method to retrieve a single tx from the mempool or unfinalized chain.
|
|
492
762
|
* @param txHash - The transaction hash to return.
|
|
493
763
|
* @returns - The tx if it exists.
|
|
494
764
|
*/
|
|
495
|
-
public getTxByHash(txHash: TxHash) {
|
|
765
|
+
public getTxByHash(txHash: TxHash): Promise<Tx | undefined> {
|
|
496
766
|
return Promise.resolve(this.p2pClient!.getTxByHashFromPool(txHash));
|
|
497
767
|
}
|
|
498
768
|
|
|
499
769
|
/**
|
|
500
|
-
* Method to retrieve txs from the mempool or
|
|
770
|
+
* Method to retrieve txs from the mempool or unfinalized chain.
|
|
501
771
|
* @param txHash - The transaction hash to return.
|
|
502
772
|
* @returns - The txs if it exists.
|
|
503
773
|
*/
|
|
504
|
-
public async getTxsByHash(txHashes: TxHash[]) {
|
|
774
|
+
public async getTxsByHash(txHashes: TxHash[]): Promise<Tx[]> {
|
|
505
775
|
return compactArray(await Promise.all(txHashes.map(txHash => this.getTxByHash(txHash))));
|
|
506
776
|
}
|
|
507
777
|
|
|
508
778
|
/**
|
|
509
|
-
* Find the indexes of the given leaves in the given tree
|
|
510
|
-
*
|
|
779
|
+
* Find the indexes of the given leaves in the given tree along with a block metadata pointing to the block in which
|
|
780
|
+
* the leaves were inserted.
|
|
781
|
+
* @param blockNumber - The block number at which to get the data or 'latest' for latest data.
|
|
511
782
|
* @param treeId - The tree to search in.
|
|
512
|
-
* @param
|
|
513
|
-
* @returns The
|
|
783
|
+
* @param leafValues - The values to search for.
|
|
784
|
+
* @returns The indices of leaves and the block metadata of a block in which the leaves were inserted.
|
|
514
785
|
*/
|
|
515
786
|
public async findLeavesIndexes(
|
|
516
787
|
blockNumber: L2BlockNumber,
|
|
517
788
|
treeId: MerkleTreeId,
|
|
518
789
|
leafValues: Fr[],
|
|
519
|
-
): Promise<(bigint | undefined)[]> {
|
|
790
|
+
): Promise<(InBlock<bigint> | undefined)[]> {
|
|
520
791
|
const committedDb = await this.#getWorldState(blockNumber);
|
|
521
|
-
|
|
792
|
+
const maybeIndices = await committedDb.findLeafIndices(
|
|
522
793
|
treeId,
|
|
523
794
|
leafValues.map(x => x.toBuffer()),
|
|
524
795
|
);
|
|
525
|
-
|
|
796
|
+
// We filter out undefined values
|
|
797
|
+
const indices = maybeIndices.filter(x => x !== undefined) as bigint[];
|
|
526
798
|
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
* @param blockNumber - The block number at which to get the data or 'latest' for latest data
|
|
530
|
-
* @param treeId - The tree to search in.
|
|
531
|
-
* @param leafIndices - The values to search for
|
|
532
|
-
* @returns The indexes of the given leaves in the given tree or undefined if not found.
|
|
533
|
-
*/
|
|
534
|
-
public async findBlockNumbersForIndexes(
|
|
535
|
-
blockNumber: L2BlockNumber,
|
|
536
|
-
treeId: MerkleTreeId,
|
|
537
|
-
leafIndices: bigint[],
|
|
538
|
-
): Promise<(bigint | undefined)[]> {
|
|
539
|
-
const committedDb = await this.#getWorldState(blockNumber);
|
|
540
|
-
return await committedDb.getBlockNumbersForLeafIndices(treeId, leafIndices);
|
|
541
|
-
}
|
|
799
|
+
// Now we find the block numbers for the indices
|
|
800
|
+
const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, indices);
|
|
542
801
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
blockNumber = await this.getBlockNumber();
|
|
802
|
+
// If any of the block numbers are undefined, we throw an error.
|
|
803
|
+
for (let i = 0; i < indices.length; i++) {
|
|
804
|
+
if (blockNumbers[i] === undefined) {
|
|
805
|
+
throw new Error(`Block number is undefined for leaf index ${indices[i]} in tree ${MerkleTreeId[treeId]}`);
|
|
806
|
+
}
|
|
549
807
|
}
|
|
550
|
-
|
|
808
|
+
|
|
809
|
+
// Get unique block numbers in order to optimize num calls to getLeafValue function.
|
|
810
|
+
const uniqueBlockNumbers = [...new Set(blockNumbers.filter(x => x !== undefined))];
|
|
811
|
+
|
|
812
|
+
// Now we obtain the block hashes from the archive tree by calling await `committedDb.getLeafValue(treeId, index)`
|
|
813
|
+
// (note that block number corresponds to the leaf index in the archive tree).
|
|
814
|
+
const blockHashes = await Promise.all(
|
|
815
|
+
uniqueBlockNumbers.map(blockNumber => {
|
|
816
|
+
return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, blockNumber!);
|
|
817
|
+
}),
|
|
818
|
+
);
|
|
819
|
+
|
|
820
|
+
// If any of the block hashes are undefined, we throw an error.
|
|
821
|
+
for (let i = 0; i < uniqueBlockNumbers.length; i++) {
|
|
822
|
+
if (blockHashes[i] === undefined) {
|
|
823
|
+
throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
|
|
824
|
+
}
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
// Create InBlock objects by combining indices, blockNumbers and blockHashes and return them.
|
|
828
|
+
return maybeIndices.map((index, i) => {
|
|
829
|
+
if (index === undefined) {
|
|
830
|
+
return undefined;
|
|
831
|
+
}
|
|
832
|
+
const blockNumber = blockNumbers[i];
|
|
833
|
+
if (blockNumber === undefined) {
|
|
834
|
+
return undefined;
|
|
835
|
+
}
|
|
836
|
+
const blockHashIndex = uniqueBlockNumbers.indexOf(blockNumber);
|
|
837
|
+
const blockHash = blockHashes[blockHashIndex];
|
|
838
|
+
if (!blockHash) {
|
|
839
|
+
return undefined;
|
|
840
|
+
}
|
|
841
|
+
return {
|
|
842
|
+
l2BlockNumber: Number(blockNumber),
|
|
843
|
+
l2BlockHash: L2BlockHash.fromField(blockHash),
|
|
844
|
+
data: index,
|
|
845
|
+
};
|
|
846
|
+
});
|
|
551
847
|
}
|
|
552
848
|
|
|
553
849
|
/**
|
|
@@ -578,6 +874,31 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
578
874
|
return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
|
|
579
875
|
}
|
|
580
876
|
|
|
877
|
+
public async getArchiveMembershipWitness(
|
|
878
|
+
blockNumber: L2BlockNumber,
|
|
879
|
+
archive: Fr,
|
|
880
|
+
): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
|
|
881
|
+
const committedDb = await this.#getWorldState(blockNumber);
|
|
882
|
+
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [archive]);
|
|
883
|
+
return pathAndIndex === undefined
|
|
884
|
+
? undefined
|
|
885
|
+
: MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
public async getNoteHashMembershipWitness(
|
|
889
|
+
blockNumber: L2BlockNumber,
|
|
890
|
+
noteHash: Fr,
|
|
891
|
+
): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
|
|
892
|
+
const committedDb = await this.#getWorldState(blockNumber);
|
|
893
|
+
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
|
|
894
|
+
MerkleTreeId.NOTE_HASH_TREE,
|
|
895
|
+
[noteHash],
|
|
896
|
+
);
|
|
897
|
+
return pathAndIndex === undefined
|
|
898
|
+
? undefined
|
|
899
|
+
: MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
|
|
900
|
+
}
|
|
901
|
+
|
|
581
902
|
/**
|
|
582
903
|
* Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree.
|
|
583
904
|
* @param blockNumber - The block number at which to get the data.
|
|
@@ -588,16 +909,19 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
588
909
|
blockNumber: L2BlockNumber,
|
|
589
910
|
l1ToL2Message: Fr,
|
|
590
911
|
): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
|
|
591
|
-
const
|
|
592
|
-
|
|
912
|
+
const db = await this.#getWorldState(blockNumber);
|
|
913
|
+
const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
|
|
914
|
+
if (!witness) {
|
|
593
915
|
return undefined;
|
|
594
916
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
917
|
+
|
|
918
|
+
// REFACTOR: Return a MembershipWitness object
|
|
919
|
+
return [witness.index, witness.path];
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
public async getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise<number | undefined> {
|
|
923
|
+
const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
|
|
924
|
+
return messageIndex ? InboxLeaf.l2BlockFromIndex(messageIndex) : undefined;
|
|
601
925
|
}
|
|
602
926
|
|
|
603
927
|
/**
|
|
@@ -606,89 +930,18 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
606
930
|
* @returns Whether the message is synced and ready to be included in a block.
|
|
607
931
|
*/
|
|
608
932
|
public async isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise<boolean> {
|
|
609
|
-
|
|
933
|
+
const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
|
|
934
|
+
return messageIndex !== undefined;
|
|
610
935
|
}
|
|
611
936
|
|
|
612
937
|
/**
|
|
613
|
-
* Returns the
|
|
614
|
-
* @remarks This tree is considered ephemeral because it is created on-demand by: taking all the l2ToL1 messages
|
|
615
|
-
* in a single block, and then using them to make a variable depth append-only tree with these messages as leaves.
|
|
616
|
-
* The tree is discarded immediately after calculating what we need from it.
|
|
617
|
-
* TODO: Handle the case where two messages in the same tx have the same hash.
|
|
938
|
+
* Returns all the L2 to L1 messages in a block.
|
|
618
939
|
* @param blockNumber - The block number at which to get the data.
|
|
619
|
-
* @
|
|
620
|
-
* @returns A tuple of the index and the sibling path of the L2ToL1Message.
|
|
940
|
+
* @returns The L2 to L1 messages (undefined if the block number is not found).
|
|
621
941
|
*/
|
|
622
|
-
public async
|
|
623
|
-
blockNumber: L2BlockNumber,
|
|
624
|
-
l2ToL1Message: Fr,
|
|
625
|
-
): Promise<[bigint, SiblingPath<number>]> {
|
|
942
|
+
public async getL2ToL1Messages(blockNumber: L2BlockNumber): Promise<Fr[][] | undefined> {
|
|
626
943
|
const block = await this.blockSource.getBlock(blockNumber === 'latest' ? await this.getBlockNumber() : blockNumber);
|
|
627
|
-
|
|
628
|
-
if (block === undefined) {
|
|
629
|
-
throw new Error('Block is not defined');
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
const l2ToL1Messages = block.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
|
|
633
|
-
|
|
634
|
-
// Find index of message
|
|
635
|
-
let indexOfMsgInSubtree = -1;
|
|
636
|
-
const indexOfMsgTx = l2ToL1Messages.findIndex(msgs => {
|
|
637
|
-
const idx = msgs.findIndex(msg => msg.equals(l2ToL1Message));
|
|
638
|
-
indexOfMsgInSubtree = Math.max(indexOfMsgInSubtree, idx);
|
|
639
|
-
return idx !== -1;
|
|
640
|
-
});
|
|
641
|
-
|
|
642
|
-
if (indexOfMsgTx === -1) {
|
|
643
|
-
throw new Error('The L2ToL1Message you are trying to prove inclusion of does not exist');
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
const tempStores: AztecKVStore[] = [];
|
|
647
|
-
|
|
648
|
-
// Construct message subtrees
|
|
649
|
-
const l2toL1Subtrees = await Promise.all(
|
|
650
|
-
l2ToL1Messages.map(async (msgs, i) => {
|
|
651
|
-
const store = openTmpStore(true);
|
|
652
|
-
tempStores.push(store);
|
|
653
|
-
const treeHeight = msgs.length <= 1 ? 1 : Math.ceil(Math.log2(msgs.length));
|
|
654
|
-
const tree = new StandardTree(store, new SHA256Trunc(), `temp_msgs_subtrees_${i}`, treeHeight, 0n, Fr);
|
|
655
|
-
await tree.appendLeaves(msgs);
|
|
656
|
-
return tree;
|
|
657
|
-
}),
|
|
658
|
-
);
|
|
659
|
-
|
|
660
|
-
// path of the input msg from leaf -> first out hash calculated in base rolllup
|
|
661
|
-
const subtreePathOfL2ToL1Message = await l2toL1Subtrees[indexOfMsgTx].getSiblingPath(
|
|
662
|
-
BigInt(indexOfMsgInSubtree),
|
|
663
|
-
true,
|
|
664
|
-
);
|
|
665
|
-
|
|
666
|
-
const numTxs = block.body.txEffects.length;
|
|
667
|
-
if (numTxs === 1) {
|
|
668
|
-
return [BigInt(indexOfMsgInSubtree), subtreePathOfL2ToL1Message];
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
const l2toL1SubtreeRoots = l2toL1Subtrees.map(t => Fr.fromBuffer(t.getRoot(true)));
|
|
672
|
-
const maxTreeHeight = Math.ceil(Math.log2(l2toL1SubtreeRoots.length));
|
|
673
|
-
// The root of this tree is the out_hash calculated in Noir => we truncate to match Noir's SHA
|
|
674
|
-
const outHashTree = new UnbalancedTree(new SHA256Trunc(), 'temp_outhash_sibling_path', maxTreeHeight, Fr);
|
|
675
|
-
await outHashTree.appendLeaves(l2toL1SubtreeRoots);
|
|
676
|
-
|
|
677
|
-
const pathOfTxInOutHashTree = await outHashTree.getSiblingPath(l2toL1SubtreeRoots[indexOfMsgTx].toBigInt());
|
|
678
|
-
// Append subtree path to out hash tree path
|
|
679
|
-
const mergedPath = subtreePathOfL2ToL1Message.toBufferArray().concat(pathOfTxInOutHashTree.toBufferArray());
|
|
680
|
-
// Append binary index of subtree path to binary index of out hash tree path
|
|
681
|
-
const mergedIndex = parseInt(
|
|
682
|
-
indexOfMsgTx
|
|
683
|
-
.toString(2)
|
|
684
|
-
.concat(indexOfMsgInSubtree.toString(2).padStart(l2toL1Subtrees[indexOfMsgTx].getDepth(), '0')),
|
|
685
|
-
2,
|
|
686
|
-
);
|
|
687
|
-
|
|
688
|
-
// clear the tmp stores
|
|
689
|
-
await Promise.all(tempStores.map(store => store.delete()));
|
|
690
|
-
|
|
691
|
-
return [BigInt(mergedIndex), new SiblingPath(mergedPath.length, mergedPath)];
|
|
944
|
+
return block?.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
|
|
692
945
|
}
|
|
693
946
|
|
|
694
947
|
/**
|
|
@@ -730,24 +983,18 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
730
983
|
nullifier: Fr,
|
|
731
984
|
): Promise<NullifierMembershipWitness | undefined> {
|
|
732
985
|
const db = await this.#getWorldState(blockNumber);
|
|
733
|
-
const
|
|
734
|
-
if (!
|
|
986
|
+
const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
|
|
987
|
+
if (!witness) {
|
|
735
988
|
return undefined;
|
|
736
989
|
}
|
|
737
990
|
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
MerkleTreeId.NULLIFIER_TREE,
|
|
741
|
-
BigInt(index),
|
|
742
|
-
);
|
|
743
|
-
|
|
744
|
-
const [leafPreimage, siblingPath] = await Promise.all([leafPreimagePromise, siblingPathPromise]);
|
|
745
|
-
|
|
991
|
+
const { index, path } = witness;
|
|
992
|
+
const leafPreimage = await db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
|
|
746
993
|
if (!leafPreimage) {
|
|
747
994
|
return undefined;
|
|
748
995
|
}
|
|
749
996
|
|
|
750
|
-
return new NullifierMembershipWitness(
|
|
997
|
+
return new NullifierMembershipWitness(index, leafPreimage as NullifierLeafPreimage, path);
|
|
751
998
|
}
|
|
752
999
|
|
|
753
1000
|
/**
|
|
@@ -779,14 +1026,11 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
779
1026
|
}
|
|
780
1027
|
const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
|
|
781
1028
|
|
|
782
|
-
const siblingPath = await committedDb.getSiblingPath
|
|
783
|
-
MerkleTreeId.NULLIFIER_TREE,
|
|
784
|
-
BigInt(index),
|
|
785
|
-
);
|
|
1029
|
+
const siblingPath = await committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, BigInt(index));
|
|
786
1030
|
return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
|
|
787
1031
|
}
|
|
788
1032
|
|
|
789
|
-
async
|
|
1033
|
+
async getPublicDataWitness(blockNumber: L2BlockNumber, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
|
|
790
1034
|
const committedDb = await this.#getWorldState(blockNumber);
|
|
791
1035
|
const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
|
|
792
1036
|
if (!lowLeafResult) {
|
|
@@ -796,10 +1040,7 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
796
1040
|
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
797
1041
|
lowLeafResult.index,
|
|
798
1042
|
)) as PublicDataTreeLeafPreimage;
|
|
799
|
-
const path = await committedDb.getSiblingPath
|
|
800
|
-
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
801
|
-
lowLeafResult.index,
|
|
802
|
-
);
|
|
1043
|
+
const path = await committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
|
|
803
1044
|
return new PublicDataWitness(lowLeafResult.index, preimage, path);
|
|
804
1045
|
}
|
|
805
1046
|
}
|
|
@@ -827,7 +1068,7 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
827
1068
|
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
828
1069
|
lowLeafResult.index,
|
|
829
1070
|
)) as PublicDataTreeLeafPreimage;
|
|
830
|
-
return preimage.value;
|
|
1071
|
+
return preimage.leaf.value;
|
|
831
1072
|
}
|
|
832
1073
|
|
|
833
1074
|
/**
|
|
@@ -840,23 +1081,55 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
840
1081
|
: this.blockSource.getBlockHeader(blockNumber);
|
|
841
1082
|
}
|
|
842
1083
|
|
|
1084
|
+
/**
|
|
1085
|
+
* Get a block header specified by its hash.
|
|
1086
|
+
* @param blockHash - The block hash being requested.
|
|
1087
|
+
* @returns The requested block header.
|
|
1088
|
+
*/
|
|
1089
|
+
public async getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
|
|
1090
|
+
return await this.blockSource.getBlockHeaderByHash(blockHash);
|
|
1091
|
+
}
|
|
1092
|
+
|
|
1093
|
+
/**
|
|
1094
|
+
* Get a block header specified by its archive root.
|
|
1095
|
+
* @param archive - The archive root being requested.
|
|
1096
|
+
* @returns The requested block header.
|
|
1097
|
+
*/
|
|
1098
|
+
public async getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
|
|
1099
|
+
return await this.blockSource.getBlockHeaderByArchive(archive);
|
|
1100
|
+
}
|
|
1101
|
+
|
|
843
1102
|
/**
|
|
844
1103
|
* Simulates the public part of a transaction with the current state.
|
|
845
1104
|
* @param tx - The transaction to simulate.
|
|
846
1105
|
**/
|
|
847
|
-
@trackSpan('AztecNodeService.simulatePublicCalls',
|
|
848
|
-
[Attributes.TX_HASH]:
|
|
1106
|
+
@trackSpan('AztecNodeService.simulatePublicCalls', (tx: Tx) => ({
|
|
1107
|
+
[Attributes.TX_HASH]: tx.getTxHash().toString(),
|
|
849
1108
|
}))
|
|
850
1109
|
public async simulatePublicCalls(tx: Tx, skipFeeEnforcement = false): Promise<PublicSimulationOutput> {
|
|
851
|
-
|
|
1110
|
+
// Check total gas limit for simulation
|
|
1111
|
+
const gasSettings = tx.data.constants.txContext.gasSettings;
|
|
1112
|
+
const txGasLimit = gasSettings.gasLimits.l2Gas;
|
|
1113
|
+
const teardownGasLimit = gasSettings.teardownGasLimits.l2Gas;
|
|
1114
|
+
if (txGasLimit + teardownGasLimit > this.config.rpcSimulatePublicMaxGasLimit) {
|
|
1115
|
+
throw new BadRequestError(
|
|
1116
|
+
`Transaction total gas limit ${
|
|
1117
|
+
txGasLimit + teardownGasLimit
|
|
1118
|
+
} (${txGasLimit} + ${teardownGasLimit}) exceeds maximum gas limit ${
|
|
1119
|
+
this.config.rpcSimulatePublicMaxGasLimit
|
|
1120
|
+
} for simulation`,
|
|
1121
|
+
);
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
const txHash = tx.getTxHash();
|
|
852
1125
|
const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
|
|
853
1126
|
|
|
854
1127
|
// If sequencer is not initialized, we just set these values to zero for simulation.
|
|
855
|
-
const coinbase =
|
|
856
|
-
const feeRecipient =
|
|
1128
|
+
const coinbase = EthAddress.ZERO;
|
|
1129
|
+
const feeRecipient = AztecAddress.ZERO;
|
|
857
1130
|
|
|
858
1131
|
const newGlobalVariables = await this.globalVariableBuilder.buildGlobalVariables(
|
|
859
|
-
|
|
1132
|
+
blockNumber,
|
|
860
1133
|
coinbase,
|
|
861
1134
|
feeRecipient,
|
|
862
1135
|
);
|
|
@@ -865,7 +1138,6 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
865
1138
|
new DateProvider(),
|
|
866
1139
|
this.telemetry,
|
|
867
1140
|
);
|
|
868
|
-
const fork = await this.worldStateSynchronizer.fork();
|
|
869
1141
|
|
|
870
1142
|
this.log.verbose(`Simulating public calls for tx ${txHash}`, {
|
|
871
1143
|
globalVariables: newGlobalVariables.toInspect(),
|
|
@@ -873,11 +1145,20 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
873
1145
|
blockNumber,
|
|
874
1146
|
});
|
|
875
1147
|
|
|
1148
|
+
const merkleTreeFork = await this.worldStateSynchronizer.fork();
|
|
876
1149
|
try {
|
|
877
|
-
const
|
|
1150
|
+
const config = PublicSimulatorConfig.from({
|
|
1151
|
+
skipFeeEnforcement,
|
|
1152
|
+
collectDebugLogs: true,
|
|
1153
|
+
collectHints: false,
|
|
1154
|
+
collectCallMetadata: true,
|
|
1155
|
+
maxDebugLogMemoryReads: this.config.rpcSimulatePublicMaxDebugLogMemoryReads,
|
|
1156
|
+
collectStatistics: false,
|
|
1157
|
+
});
|
|
1158
|
+
const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
|
|
878
1159
|
|
|
879
1160
|
// REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
|
|
880
|
-
const [processedTxs, failedTxs, returns] = await processor.process([tx]);
|
|
1161
|
+
const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([tx]);
|
|
881
1162
|
// REFACTOR: Consider returning the error rather than throwing
|
|
882
1163
|
if (failedTxs.length) {
|
|
883
1164
|
this.log.warn(`Simulated tx ${txHash} fails: ${failedTxs[0].error}`, { txHash });
|
|
@@ -887,13 +1168,13 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
887
1168
|
const [processedTx] = processedTxs;
|
|
888
1169
|
return new PublicSimulationOutput(
|
|
889
1170
|
processedTx.revertReason,
|
|
890
|
-
processedTx.
|
|
1171
|
+
processedTx.globalVariables,
|
|
891
1172
|
processedTx.txEffect,
|
|
892
1173
|
returns,
|
|
893
1174
|
processedTx.gasUsed,
|
|
894
1175
|
);
|
|
895
1176
|
} finally {
|
|
896
|
-
await
|
|
1177
|
+
await merkleTreeFork.close();
|
|
897
1178
|
}
|
|
898
1179
|
}
|
|
899
1180
|
|
|
@@ -901,24 +1182,42 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
901
1182
|
tx: Tx,
|
|
902
1183
|
{ isSimulation, skipFeeEnforcement }: { isSimulation?: boolean; skipFeeEnforcement?: boolean } = {},
|
|
903
1184
|
): Promise<TxValidationResult> {
|
|
904
|
-
const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
|
|
905
1185
|
const db = this.worldStateSynchronizer.getCommitted();
|
|
906
1186
|
const verifier = isSimulation ? undefined : this.proofVerifier;
|
|
1187
|
+
|
|
1188
|
+
// We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
|
|
1189
|
+
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1190
|
+
const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
|
|
907
1191
|
const validator = createValidatorForAcceptingTxs(db, this.contractDataSource, verifier, {
|
|
1192
|
+
timestamp: nextSlotTimestamp,
|
|
908
1193
|
blockNumber,
|
|
909
1194
|
l1ChainId: this.l1ChainId,
|
|
910
|
-
|
|
1195
|
+
rollupVersion: this.version,
|
|
1196
|
+
setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()),
|
|
911
1197
|
gasFees: await this.getCurrentBaseFees(),
|
|
912
1198
|
skipFeeEnforcement,
|
|
1199
|
+
txsPermitted: !this.config.disableTransactions,
|
|
913
1200
|
});
|
|
914
1201
|
|
|
915
1202
|
return await validator.validateTx(tx);
|
|
916
1203
|
}
|
|
917
1204
|
|
|
918
|
-
public
|
|
919
|
-
const
|
|
920
|
-
|
|
1205
|
+
public getConfig(): Promise<AztecNodeAdminConfig> {
|
|
1206
|
+
const schema = AztecNodeAdminConfigSchema;
|
|
1207
|
+
const keys = schema.keyof().options;
|
|
1208
|
+
return Promise.resolve(pick(this.config, ...keys));
|
|
1209
|
+
}
|
|
921
1210
|
|
|
1211
|
+
public async setConfig(config: Partial<AztecNodeAdminConfig>): Promise<void> {
|
|
1212
|
+
const newConfig = { ...this.config, ...config };
|
|
1213
|
+
this.sequencer?.updateConfig(config);
|
|
1214
|
+
this.slasherClient?.updateConfig(config);
|
|
1215
|
+
this.validatorsSentinel?.updateConfig(config);
|
|
1216
|
+
await this.p2pClient.updateP2PConfig(config);
|
|
1217
|
+
const archiver = this.blockSource as Archiver;
|
|
1218
|
+
if ('updateConfig' in archiver) {
|
|
1219
|
+
archiver.updateConfig(config);
|
|
1220
|
+
}
|
|
922
1221
|
if (newConfig.realProofs !== this.config.realProofs) {
|
|
923
1222
|
this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
|
|
924
1223
|
}
|
|
@@ -928,31 +1227,140 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
928
1227
|
|
|
929
1228
|
public getProtocolContractAddresses(): Promise<ProtocolContractAddresses> {
|
|
930
1229
|
return Promise.resolve({
|
|
931
|
-
|
|
1230
|
+
classRegistry: ProtocolContractAddress.ContractClassRegistry,
|
|
932
1231
|
feeJuice: ProtocolContractAddress.FeeJuice,
|
|
933
|
-
|
|
1232
|
+
instanceRegistry: ProtocolContractAddress.ContractInstanceRegistry,
|
|
934
1233
|
multiCallEntrypoint: ProtocolContractAddress.MultiCallEntrypoint,
|
|
935
1234
|
});
|
|
936
1235
|
}
|
|
937
1236
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1237
|
+
public registerContractFunctionSignatures(signatures: string[]): Promise<void> {
|
|
1238
|
+
return this.contractDataSource.registerContractFunctionSignatures(signatures);
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
public getValidatorsStats(): Promise<ValidatorsStats> {
|
|
1242
|
+
return this.validatorsSentinel?.computeStats() ?? Promise.resolve({ stats: {}, slotWindow: 0 });
|
|
942
1243
|
}
|
|
943
1244
|
|
|
944
|
-
public
|
|
945
|
-
|
|
1245
|
+
public getValidatorStats(
|
|
1246
|
+
validatorAddress: EthAddress,
|
|
1247
|
+
fromSlot?: SlotNumber,
|
|
1248
|
+
toSlot?: SlotNumber,
|
|
1249
|
+
): Promise<SingleValidatorStats | undefined> {
|
|
1250
|
+
return this.validatorsSentinel?.getValidatorStats(validatorAddress, fromSlot, toSlot) ?? Promise.resolve(undefined);
|
|
1251
|
+
}
|
|
1252
|
+
|
|
1253
|
+
public async startSnapshotUpload(location: string): Promise<void> {
|
|
1254
|
+
// Note that we are forcefully casting the blocksource as an archiver
|
|
1255
|
+
// We break support for archiver running remotely to the node
|
|
1256
|
+
const archiver = this.blockSource as Archiver;
|
|
1257
|
+
if (!('backupTo' in archiver)) {
|
|
1258
|
+
this.metrics.recordSnapshotError();
|
|
1259
|
+
throw new Error('Archiver implementation does not support backups. Cannot generate snapshot.');
|
|
1260
|
+
}
|
|
1261
|
+
|
|
1262
|
+
// Test that the archiver has done an initial sync.
|
|
1263
|
+
if (!archiver.isInitialSyncComplete()) {
|
|
1264
|
+
this.metrics.recordSnapshotError();
|
|
1265
|
+
throw new Error(`Archiver initial sync not complete. Cannot start snapshot.`);
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1268
|
+
// And it has an L2 block hash
|
|
1269
|
+
const l2BlockHash = await archiver.getL2Tips().then(tips => tips.latest.hash);
|
|
1270
|
+
if (!l2BlockHash) {
|
|
1271
|
+
this.metrics.recordSnapshotError();
|
|
1272
|
+
throw new Error(`Archiver has no latest L2 block hash downloaded. Cannot start snapshot.`);
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
if (this.isUploadingSnapshot) {
|
|
1276
|
+
this.metrics.recordSnapshotError();
|
|
1277
|
+
throw new Error(`Snapshot upload already in progress. Cannot start another one until complete.`);
|
|
1278
|
+
}
|
|
1279
|
+
|
|
1280
|
+
// Do not wait for the upload to be complete to return to the caller, but flag that an operation is in progress
|
|
1281
|
+
this.isUploadingSnapshot = true;
|
|
1282
|
+
const timer = new Timer();
|
|
1283
|
+
void uploadSnapshot(location, this.blockSource as Archiver, this.worldStateSynchronizer, this.config, this.log)
|
|
1284
|
+
.then(() => {
|
|
1285
|
+
this.isUploadingSnapshot = false;
|
|
1286
|
+
this.metrics.recordSnapshot(timer.ms());
|
|
1287
|
+
})
|
|
1288
|
+
.catch(err => {
|
|
1289
|
+
this.isUploadingSnapshot = false;
|
|
1290
|
+
this.metrics.recordSnapshotError();
|
|
1291
|
+
this.log.error(`Error uploading snapshot: ${err}`);
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
return Promise.resolve();
|
|
946
1295
|
}
|
|
947
1296
|
|
|
948
|
-
public
|
|
949
|
-
|
|
950
|
-
|
|
1297
|
+
public async rollbackTo(targetBlock: number, force?: boolean): Promise<void> {
|
|
1298
|
+
const archiver = this.blockSource as Archiver;
|
|
1299
|
+
if (!('rollbackTo' in archiver)) {
|
|
1300
|
+
throw new Error('Archiver implementation does not support rollbacks.');
|
|
951
1301
|
}
|
|
952
|
-
|
|
1302
|
+
|
|
1303
|
+
const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.number);
|
|
1304
|
+
if (targetBlock < finalizedBlock) {
|
|
1305
|
+
if (force) {
|
|
1306
|
+
this.log.warn(`Clearing world state database to allow rolling back behind finalized block ${finalizedBlock}`);
|
|
1307
|
+
await this.worldStateSynchronizer.clear();
|
|
1308
|
+
await this.p2pClient.clear();
|
|
1309
|
+
} else {
|
|
1310
|
+
throw new Error(`Cannot rollback to block ${targetBlock} as it is before finalized ${finalizedBlock}`);
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
try {
|
|
1315
|
+
this.log.info(`Pausing archiver and world state sync to start rollback`);
|
|
1316
|
+
await archiver.stop();
|
|
1317
|
+
await this.worldStateSynchronizer.stopSync();
|
|
1318
|
+
const currentBlock = await archiver.getBlockNumber();
|
|
1319
|
+
const blocksToUnwind = currentBlock - targetBlock;
|
|
1320
|
+
this.log.info(`Unwinding ${count(blocksToUnwind, 'block')} from L2 block ${currentBlock} to ${targetBlock}`);
|
|
1321
|
+
await archiver.rollbackTo(targetBlock);
|
|
1322
|
+
this.log.info(`Unwinding complete.`);
|
|
1323
|
+
} catch (err) {
|
|
1324
|
+
this.log.error(`Error during rollback`, err);
|
|
1325
|
+
throw err;
|
|
1326
|
+
} finally {
|
|
1327
|
+
this.log.info(`Resuming world state and archiver sync.`);
|
|
1328
|
+
this.worldStateSynchronizer.resumeSync();
|
|
1329
|
+
archiver.resume();
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
public async pauseSync(): Promise<void> {
|
|
1334
|
+
this.log.info(`Pausing archiver and world state sync`);
|
|
1335
|
+
await (this.blockSource as Archiver).stop();
|
|
1336
|
+
await this.worldStateSynchronizer.stopSync();
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
public resumeSync(): Promise<void> {
|
|
1340
|
+
this.log.info(`Resuming world state and archiver sync.`);
|
|
1341
|
+
this.worldStateSynchronizer.resumeSync();
|
|
1342
|
+
(this.blockSource as Archiver).resume();
|
|
953
1343
|
return Promise.resolve();
|
|
954
1344
|
}
|
|
955
1345
|
|
|
1346
|
+
public getSlashPayloads(): Promise<SlashPayloadRound[]> {
|
|
1347
|
+
if (!this.slasherClient) {
|
|
1348
|
+
throw new Error(`Slasher client not enabled`);
|
|
1349
|
+
}
|
|
1350
|
+
return this.slasherClient.getSlashPayloads();
|
|
1351
|
+
}
|
|
1352
|
+
|
|
1353
|
+
public getSlashOffenses(round: bigint | 'all' | 'current'): Promise<Offense[]> {
|
|
1354
|
+
if (!this.slasherClient) {
|
|
1355
|
+
throw new Error(`Slasher client not enabled`);
|
|
1356
|
+
}
|
|
1357
|
+
if (round === 'all') {
|
|
1358
|
+
return this.slasherClient.getPendingOffenses();
|
|
1359
|
+
} else {
|
|
1360
|
+
return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
|
|
956
1364
|
/**
|
|
957
1365
|
* Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
|
|
958
1366
|
* @param blockNumber - The block number at which to get the data.
|