@aztec/aztec-node 0.0.0-test.1 → 0.0.1-commit.5476d83
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 +93 -52
- package/dest/aztec-node/server.d.ts.map +1 -1
- package/dest/aztec-node/server.js +535 -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 +695 -279
- 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,
|
|
@@ -41,34 +63,44 @@ import type {
|
|
|
41
63
|
NodeInfo,
|
|
42
64
|
ProtocolContractAddresses,
|
|
43
65
|
} from '@aztec/stdlib/contract';
|
|
44
|
-
import
|
|
45
|
-
import { computePublicDataTreeLeafSlot
|
|
46
|
-
import
|
|
66
|
+
import { GasFees } from '@aztec/stdlib/gas';
|
|
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,
|
|
305
|
+
telemetry,
|
|
306
|
+
deps.p2pClientDeps,
|
|
307
|
+
);
|
|
308
|
+
|
|
309
|
+
// We should really not be modifying the config object
|
|
310
|
+
config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions());
|
|
311
|
+
|
|
312
|
+
const blockBuilder = new BlockBuilder(
|
|
313
|
+
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration) },
|
|
314
|
+
worldStateSynchronizer,
|
|
315
|
+
archiver,
|
|
316
|
+
dateProvider,
|
|
183
317
|
telemetry,
|
|
184
318
|
);
|
|
185
319
|
|
|
186
|
-
|
|
320
|
+
// We'll accumulate sentinel watchers here
|
|
321
|
+
const watchers: Watcher[] = [];
|
|
322
|
+
|
|
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
|
+
}
|
|
187
344
|
|
|
188
|
-
//
|
|
189
|
-
|
|
190
|
-
|
|
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
|
+
}
|
|
191
359
|
|
|
192
|
-
|
|
360
|
+
// Start world state and wait for it to sync to the archiver.
|
|
361
|
+
await worldStateSynchronizer.start();
|
|
193
362
|
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
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.
|
|
@@ -317,8 +596,16 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
317
596
|
return await this.globalVariableBuilder.getCurrentBaseFees();
|
|
318
597
|
}
|
|
319
598
|
|
|
599
|
+
public async getMaxPriorityFees(): Promise<GasFees> {
|
|
600
|
+
for await (const tx of this.p2pClient.iteratePendingTxs()) {
|
|
601
|
+
return tx.getGasSettings().maxPriorityFeesPerGas;
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
return GasFees.from({ feePerDaGas: 0n, feePerL2Gas: 0n });
|
|
605
|
+
}
|
|
606
|
+
|
|
320
607
|
/**
|
|
321
|
-
* Method to fetch the
|
|
608
|
+
* Method to fetch the latest block number synchronized by the node.
|
|
322
609
|
* @returns The block number.
|
|
323
610
|
*/
|
|
324
611
|
public async getBlockNumber(): Promise<number> {
|
|
@@ -353,25 +640,8 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
353
640
|
return Promise.resolve(this.l1ChainId);
|
|
354
641
|
}
|
|
355
642
|
|
|
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;
|
|
643
|
+
public getContractClass(id: Fr): Promise<ContractClassPublic | undefined> {
|
|
644
|
+
return this.contractDataSource.getContractClass(id);
|
|
375
645
|
}
|
|
376
646
|
|
|
377
647
|
public getContract(address: AztecAddress): Promise<ContractInstanceWithAddress | undefined> {
|
|
@@ -391,11 +661,12 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
391
661
|
/**
|
|
392
662
|
* Gets all logs that match any of the received tags (i.e. logs with their first field equal to a tag).
|
|
393
663
|
* @param tags - The tags to filter the logs by.
|
|
664
|
+
* @param logsPerTag - The maximum number of logs to return for each tag. By default no limit is set
|
|
394
665
|
* @returns For each received tag, an array of matching logs is returned. An empty array implies no logs match
|
|
395
666
|
* that tag.
|
|
396
667
|
*/
|
|
397
|
-
public getLogsByTags(tags: Fr[]): Promise<TxScopedL2Log[][]> {
|
|
398
|
-
return this.logsSource.getLogsByTags(tags);
|
|
668
|
+
public getLogsByTags(tags: Fr[], logsPerTag?: number): Promise<TxScopedL2Log[][]> {
|
|
669
|
+
return this.logsSource.getLogsByTags(tags, logsPerTag);
|
|
399
670
|
}
|
|
400
671
|
|
|
401
672
|
/**
|
|
@@ -421,17 +692,19 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
421
692
|
* @param tx - The transaction to be submitted.
|
|
422
693
|
*/
|
|
423
694
|
public async sendTx(tx: Tx) {
|
|
695
|
+
await this.#sendTx(tx);
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
async #sendTx(tx: Tx) {
|
|
424
699
|
const timer = new Timer();
|
|
425
|
-
const txHash =
|
|
700
|
+
const txHash = tx.getTxHash().toString();
|
|
426
701
|
|
|
427
702
|
const valid = await this.isValidTx(tx);
|
|
428
703
|
if (valid.result !== 'valid') {
|
|
429
704
|
const reason = valid.reason.join(', ');
|
|
430
705
|
this.metrics.receivedTx(timer.ms(), false);
|
|
431
|
-
this.log.warn(`
|
|
432
|
-
|
|
433
|
-
// throw new Error(`Invalid tx: ${reason}`);
|
|
434
|
-
return;
|
|
706
|
+
this.log.warn(`Received invalid tx ${txHash}: ${reason}`, { txHash });
|
|
707
|
+
throw new Error(`Invalid tx: ${reason}`);
|
|
435
708
|
}
|
|
436
709
|
|
|
437
710
|
await this.p2pClient!.sendTx(tx);
|
|
@@ -457,7 +730,7 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
457
730
|
return txReceipt;
|
|
458
731
|
}
|
|
459
732
|
|
|
460
|
-
public getTxEffect(txHash: TxHash): Promise<
|
|
733
|
+
public getTxEffect(txHash: TxHash): Promise<IndexedTxEffect | undefined> {
|
|
461
734
|
return this.blockSource.getTxEffect(txHash);
|
|
462
735
|
}
|
|
463
736
|
|
|
@@ -465,89 +738,120 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
465
738
|
* Method to stop the aztec node.
|
|
466
739
|
*/
|
|
467
740
|
public async stop() {
|
|
468
|
-
this.log.info(`Stopping`);
|
|
469
|
-
await this.
|
|
470
|
-
await this.
|
|
471
|
-
await this.
|
|
741
|
+
this.log.info(`Stopping Aztec Node`);
|
|
742
|
+
await tryStop(this.validatorsSentinel);
|
|
743
|
+
await tryStop(this.epochPruneWatcher);
|
|
744
|
+
await tryStop(this.slasherClient);
|
|
745
|
+
await tryStop(this.proofVerifier);
|
|
746
|
+
await tryStop(this.sequencer);
|
|
747
|
+
await tryStop(this.p2pClient);
|
|
748
|
+
await tryStop(this.worldStateSynchronizer);
|
|
472
749
|
await tryStop(this.blockSource);
|
|
473
|
-
await this.telemetry
|
|
474
|
-
this.log.info(`Stopped`);
|
|
750
|
+
await tryStop(this.telemetry);
|
|
751
|
+
this.log.info(`Stopped Aztec Node`);
|
|
475
752
|
}
|
|
476
753
|
|
|
477
754
|
/**
|
|
478
755
|
* Method to retrieve pending txs.
|
|
756
|
+
* @param limit - The number of items to returns
|
|
757
|
+
* @param after - The last known pending tx. Used for pagination
|
|
479
758
|
* @returns - The pending txs.
|
|
480
759
|
*/
|
|
481
|
-
public getPendingTxs() {
|
|
482
|
-
return this.p2pClient!.getPendingTxs();
|
|
760
|
+
public getPendingTxs(limit?: number, after?: TxHash): Promise<Tx[]> {
|
|
761
|
+
return this.p2pClient!.getPendingTxs(limit, after);
|
|
483
762
|
}
|
|
484
763
|
|
|
485
|
-
public
|
|
486
|
-
|
|
487
|
-
return pendingTxs.length;
|
|
764
|
+
public getPendingTxCount(): Promise<number> {
|
|
765
|
+
return this.p2pClient!.getPendingTxCount();
|
|
488
766
|
}
|
|
489
767
|
|
|
490
768
|
/**
|
|
491
|
-
* Method to retrieve a single tx from the mempool or
|
|
769
|
+
* Method to retrieve a single tx from the mempool or unfinalized chain.
|
|
492
770
|
* @param txHash - The transaction hash to return.
|
|
493
771
|
* @returns - The tx if it exists.
|
|
494
772
|
*/
|
|
495
|
-
public getTxByHash(txHash: TxHash) {
|
|
773
|
+
public getTxByHash(txHash: TxHash): Promise<Tx | undefined> {
|
|
496
774
|
return Promise.resolve(this.p2pClient!.getTxByHashFromPool(txHash));
|
|
497
775
|
}
|
|
498
776
|
|
|
499
777
|
/**
|
|
500
|
-
* Method to retrieve txs from the mempool or
|
|
778
|
+
* Method to retrieve txs from the mempool or unfinalized chain.
|
|
501
779
|
* @param txHash - The transaction hash to return.
|
|
502
780
|
* @returns - The txs if it exists.
|
|
503
781
|
*/
|
|
504
|
-
public async getTxsByHash(txHashes: TxHash[]) {
|
|
782
|
+
public async getTxsByHash(txHashes: TxHash[]): Promise<Tx[]> {
|
|
505
783
|
return compactArray(await Promise.all(txHashes.map(txHash => this.getTxByHash(txHash))));
|
|
506
784
|
}
|
|
507
785
|
|
|
508
786
|
/**
|
|
509
|
-
* Find the indexes of the given leaves in the given tree
|
|
510
|
-
*
|
|
787
|
+
* Find the indexes of the given leaves in the given tree along with a block metadata pointing to the block in which
|
|
788
|
+
* the leaves were inserted.
|
|
789
|
+
* @param blockNumber - The block number at which to get the data or 'latest' for latest data.
|
|
511
790
|
* @param treeId - The tree to search in.
|
|
512
|
-
* @param
|
|
513
|
-
* @returns The
|
|
791
|
+
* @param leafValues - The values to search for.
|
|
792
|
+
* @returns The indices of leaves and the block metadata of a block in which the leaves were inserted.
|
|
514
793
|
*/
|
|
515
794
|
public async findLeavesIndexes(
|
|
516
795
|
blockNumber: L2BlockNumber,
|
|
517
796
|
treeId: MerkleTreeId,
|
|
518
797
|
leafValues: Fr[],
|
|
519
|
-
): Promise<(bigint | undefined)[]> {
|
|
798
|
+
): Promise<(InBlock<bigint> | undefined)[]> {
|
|
520
799
|
const committedDb = await this.#getWorldState(blockNumber);
|
|
521
|
-
|
|
800
|
+
const maybeIndices = await committedDb.findLeafIndices(
|
|
522
801
|
treeId,
|
|
523
802
|
leafValues.map(x => x.toBuffer()),
|
|
524
803
|
);
|
|
525
|
-
|
|
804
|
+
// We filter out undefined values
|
|
805
|
+
const indices = maybeIndices.filter(x => x !== undefined) as bigint[];
|
|
526
806
|
|
|
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
|
-
}
|
|
807
|
+
// Now we find the block numbers for the indices
|
|
808
|
+
const blockNumbers = await committedDb.getBlockNumbersForLeafIndices(treeId, indices);
|
|
542
809
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
blockNumber = await this.getBlockNumber();
|
|
810
|
+
// If any of the block numbers are undefined, we throw an error.
|
|
811
|
+
for (let i = 0; i < indices.length; i++) {
|
|
812
|
+
if (blockNumbers[i] === undefined) {
|
|
813
|
+
throw new Error(`Block number is undefined for leaf index ${indices[i]} in tree ${MerkleTreeId[treeId]}`);
|
|
814
|
+
}
|
|
549
815
|
}
|
|
550
|
-
|
|
816
|
+
|
|
817
|
+
// Get unique block numbers in order to optimize num calls to getLeafValue function.
|
|
818
|
+
const uniqueBlockNumbers = [...new Set(blockNumbers.filter(x => x !== undefined))];
|
|
819
|
+
|
|
820
|
+
// Now we obtain the block hashes from the archive tree by calling await `committedDb.getLeafValue(treeId, index)`
|
|
821
|
+
// (note that block number corresponds to the leaf index in the archive tree).
|
|
822
|
+
const blockHashes = await Promise.all(
|
|
823
|
+
uniqueBlockNumbers.map(blockNumber => {
|
|
824
|
+
return committedDb.getLeafValue(MerkleTreeId.ARCHIVE, blockNumber!);
|
|
825
|
+
}),
|
|
826
|
+
);
|
|
827
|
+
|
|
828
|
+
// If any of the block hashes are undefined, we throw an error.
|
|
829
|
+
for (let i = 0; i < uniqueBlockNumbers.length; i++) {
|
|
830
|
+
if (blockHashes[i] === undefined) {
|
|
831
|
+
throw new Error(`Block hash is undefined for block number ${uniqueBlockNumbers[i]}`);
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
// Create InBlock objects by combining indices, blockNumbers and blockHashes and return them.
|
|
836
|
+
return maybeIndices.map((index, i) => {
|
|
837
|
+
if (index === undefined) {
|
|
838
|
+
return undefined;
|
|
839
|
+
}
|
|
840
|
+
const blockNumber = blockNumbers[i];
|
|
841
|
+
if (blockNumber === undefined) {
|
|
842
|
+
return undefined;
|
|
843
|
+
}
|
|
844
|
+
const blockHashIndex = uniqueBlockNumbers.indexOf(blockNumber);
|
|
845
|
+
const blockHash = blockHashes[blockHashIndex];
|
|
846
|
+
if (!blockHash) {
|
|
847
|
+
return undefined;
|
|
848
|
+
}
|
|
849
|
+
return {
|
|
850
|
+
l2BlockNumber: Number(blockNumber),
|
|
851
|
+
l2BlockHash: L2BlockHash.fromField(blockHash),
|
|
852
|
+
data: index,
|
|
853
|
+
};
|
|
854
|
+
});
|
|
551
855
|
}
|
|
552
856
|
|
|
553
857
|
/**
|
|
@@ -578,6 +882,31 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
578
882
|
return committedDb.getSiblingPath(MerkleTreeId.NOTE_HASH_TREE, leafIndex);
|
|
579
883
|
}
|
|
580
884
|
|
|
885
|
+
public async getArchiveMembershipWitness(
|
|
886
|
+
blockNumber: L2BlockNumber,
|
|
887
|
+
archive: Fr,
|
|
888
|
+
): Promise<MembershipWitness<typeof ARCHIVE_HEIGHT> | undefined> {
|
|
889
|
+
const committedDb = await this.#getWorldState(blockNumber);
|
|
890
|
+
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.ARCHIVE>(MerkleTreeId.ARCHIVE, [archive]);
|
|
891
|
+
return pathAndIndex === undefined
|
|
892
|
+
? undefined
|
|
893
|
+
: MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
public async getNoteHashMembershipWitness(
|
|
897
|
+
blockNumber: L2BlockNumber,
|
|
898
|
+
noteHash: Fr,
|
|
899
|
+
): Promise<MembershipWitness<typeof NOTE_HASH_TREE_HEIGHT> | undefined> {
|
|
900
|
+
const committedDb = await this.#getWorldState(blockNumber);
|
|
901
|
+
const [pathAndIndex] = await committedDb.findSiblingPaths<MerkleTreeId.NOTE_HASH_TREE>(
|
|
902
|
+
MerkleTreeId.NOTE_HASH_TREE,
|
|
903
|
+
[noteHash],
|
|
904
|
+
);
|
|
905
|
+
return pathAndIndex === undefined
|
|
906
|
+
? undefined
|
|
907
|
+
: MembershipWitness.fromSiblingPath(pathAndIndex.index, pathAndIndex.path);
|
|
908
|
+
}
|
|
909
|
+
|
|
581
910
|
/**
|
|
582
911
|
* Returns the index and a sibling path for a leaf in the committed l1 to l2 data tree.
|
|
583
912
|
* @param blockNumber - The block number at which to get the data.
|
|
@@ -588,16 +917,19 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
588
917
|
blockNumber: L2BlockNumber,
|
|
589
918
|
l1ToL2Message: Fr,
|
|
590
919
|
): Promise<[bigint, SiblingPath<typeof L1_TO_L2_MSG_TREE_HEIGHT>] | undefined> {
|
|
591
|
-
const
|
|
592
|
-
|
|
920
|
+
const db = await this.#getWorldState(blockNumber);
|
|
921
|
+
const [witness] = await db.findSiblingPaths(MerkleTreeId.L1_TO_L2_MESSAGE_TREE, [l1ToL2Message]);
|
|
922
|
+
if (!witness) {
|
|
593
923
|
return undefined;
|
|
594
924
|
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
925
|
+
|
|
926
|
+
// REFACTOR: Return a MembershipWitness object
|
|
927
|
+
return [witness.index, witness.path];
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
public async getL1ToL2MessageBlock(l1ToL2Message: Fr): Promise<number | undefined> {
|
|
931
|
+
const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
|
|
932
|
+
return messageIndex ? InboxLeaf.l2BlockFromIndex(messageIndex) : undefined;
|
|
601
933
|
}
|
|
602
934
|
|
|
603
935
|
/**
|
|
@@ -606,89 +938,18 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
606
938
|
* @returns Whether the message is synced and ready to be included in a block.
|
|
607
939
|
*/
|
|
608
940
|
public async isL1ToL2MessageSynced(l1ToL2Message: Fr): Promise<boolean> {
|
|
609
|
-
|
|
941
|
+
const messageIndex = await this.l1ToL2MessageSource.getL1ToL2MessageIndex(l1ToL2Message);
|
|
942
|
+
return messageIndex !== undefined;
|
|
610
943
|
}
|
|
611
944
|
|
|
612
945
|
/**
|
|
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.
|
|
946
|
+
* Returns all the L2 to L1 messages in a block.
|
|
618
947
|
* @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.
|
|
948
|
+
* @returns The L2 to L1 messages (undefined if the block number is not found).
|
|
621
949
|
*/
|
|
622
|
-
public async
|
|
623
|
-
blockNumber: L2BlockNumber,
|
|
624
|
-
l2ToL1Message: Fr,
|
|
625
|
-
): Promise<[bigint, SiblingPath<number>]> {
|
|
950
|
+
public async getL2ToL1Messages(blockNumber: L2BlockNumber): Promise<Fr[][] | undefined> {
|
|
626
951
|
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)];
|
|
952
|
+
return block?.body.txEffects.map(txEffect => txEffect.l2ToL1Msgs);
|
|
692
953
|
}
|
|
693
954
|
|
|
694
955
|
/**
|
|
@@ -730,24 +991,18 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
730
991
|
nullifier: Fr,
|
|
731
992
|
): Promise<NullifierMembershipWitness | undefined> {
|
|
732
993
|
const db = await this.#getWorldState(blockNumber);
|
|
733
|
-
const
|
|
734
|
-
if (!
|
|
994
|
+
const [witness] = await db.findSiblingPaths(MerkleTreeId.NULLIFIER_TREE, [nullifier.toBuffer()]);
|
|
995
|
+
if (!witness) {
|
|
735
996
|
return undefined;
|
|
736
997
|
}
|
|
737
998
|
|
|
738
|
-
const
|
|
739
|
-
const
|
|
740
|
-
MerkleTreeId.NULLIFIER_TREE,
|
|
741
|
-
BigInt(index),
|
|
742
|
-
);
|
|
743
|
-
|
|
744
|
-
const [leafPreimage, siblingPath] = await Promise.all([leafPreimagePromise, siblingPathPromise]);
|
|
745
|
-
|
|
999
|
+
const { index, path } = witness;
|
|
1000
|
+
const leafPreimage = await db.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index);
|
|
746
1001
|
if (!leafPreimage) {
|
|
747
1002
|
return undefined;
|
|
748
1003
|
}
|
|
749
1004
|
|
|
750
|
-
return new NullifierMembershipWitness(
|
|
1005
|
+
return new NullifierMembershipWitness(index, leafPreimage as NullifierLeafPreimage, path);
|
|
751
1006
|
}
|
|
752
1007
|
|
|
753
1008
|
/**
|
|
@@ -779,14 +1034,11 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
779
1034
|
}
|
|
780
1035
|
const preimageData = (await committedDb.getLeafPreimage(MerkleTreeId.NULLIFIER_TREE, index))!;
|
|
781
1036
|
|
|
782
|
-
const siblingPath = await committedDb.getSiblingPath
|
|
783
|
-
MerkleTreeId.NULLIFIER_TREE,
|
|
784
|
-
BigInt(index),
|
|
785
|
-
);
|
|
1037
|
+
const siblingPath = await committedDb.getSiblingPath(MerkleTreeId.NULLIFIER_TREE, BigInt(index));
|
|
786
1038
|
return new NullifierMembershipWitness(BigInt(index), preimageData as NullifierLeafPreimage, siblingPath);
|
|
787
1039
|
}
|
|
788
1040
|
|
|
789
|
-
async
|
|
1041
|
+
async getPublicDataWitness(blockNumber: L2BlockNumber, leafSlot: Fr): Promise<PublicDataWitness | undefined> {
|
|
790
1042
|
const committedDb = await this.#getWorldState(blockNumber);
|
|
791
1043
|
const lowLeafResult = await committedDb.getPreviousValueIndex(MerkleTreeId.PUBLIC_DATA_TREE, leafSlot.toBigInt());
|
|
792
1044
|
if (!lowLeafResult) {
|
|
@@ -796,10 +1048,7 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
796
1048
|
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
797
1049
|
lowLeafResult.index,
|
|
798
1050
|
)) as PublicDataTreeLeafPreimage;
|
|
799
|
-
const path = await committedDb.getSiblingPath
|
|
800
|
-
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
801
|
-
lowLeafResult.index,
|
|
802
|
-
);
|
|
1051
|
+
const path = await committedDb.getSiblingPath(MerkleTreeId.PUBLIC_DATA_TREE, lowLeafResult.index);
|
|
803
1052
|
return new PublicDataWitness(lowLeafResult.index, preimage, path);
|
|
804
1053
|
}
|
|
805
1054
|
}
|
|
@@ -827,7 +1076,7 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
827
1076
|
MerkleTreeId.PUBLIC_DATA_TREE,
|
|
828
1077
|
lowLeafResult.index,
|
|
829
1078
|
)) as PublicDataTreeLeafPreimage;
|
|
830
|
-
return preimage.value;
|
|
1079
|
+
return preimage.leaf.value;
|
|
831
1080
|
}
|
|
832
1081
|
|
|
833
1082
|
/**
|
|
@@ -840,23 +1089,55 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
840
1089
|
: this.blockSource.getBlockHeader(blockNumber);
|
|
841
1090
|
}
|
|
842
1091
|
|
|
1092
|
+
/**
|
|
1093
|
+
* Get a block header specified by its hash.
|
|
1094
|
+
* @param blockHash - The block hash being requested.
|
|
1095
|
+
* @returns The requested block header.
|
|
1096
|
+
*/
|
|
1097
|
+
public async getBlockHeaderByHash(blockHash: Fr): Promise<BlockHeader | undefined> {
|
|
1098
|
+
return await this.blockSource.getBlockHeaderByHash(blockHash);
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
/**
|
|
1102
|
+
* Get a block header specified by its archive root.
|
|
1103
|
+
* @param archive - The archive root being requested.
|
|
1104
|
+
* @returns The requested block header.
|
|
1105
|
+
*/
|
|
1106
|
+
public async getBlockHeaderByArchive(archive: Fr): Promise<BlockHeader | undefined> {
|
|
1107
|
+
return await this.blockSource.getBlockHeaderByArchive(archive);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
843
1110
|
/**
|
|
844
1111
|
* Simulates the public part of a transaction with the current state.
|
|
845
1112
|
* @param tx - The transaction to simulate.
|
|
846
1113
|
**/
|
|
847
|
-
@trackSpan('AztecNodeService.simulatePublicCalls',
|
|
848
|
-
[Attributes.TX_HASH]:
|
|
1114
|
+
@trackSpan('AztecNodeService.simulatePublicCalls', (tx: Tx) => ({
|
|
1115
|
+
[Attributes.TX_HASH]: tx.getTxHash().toString(),
|
|
849
1116
|
}))
|
|
850
1117
|
public async simulatePublicCalls(tx: Tx, skipFeeEnforcement = false): Promise<PublicSimulationOutput> {
|
|
851
|
-
|
|
1118
|
+
// Check total gas limit for simulation
|
|
1119
|
+
const gasSettings = tx.data.constants.txContext.gasSettings;
|
|
1120
|
+
const txGasLimit = gasSettings.gasLimits.l2Gas;
|
|
1121
|
+
const teardownGasLimit = gasSettings.teardownGasLimits.l2Gas;
|
|
1122
|
+
if (txGasLimit + teardownGasLimit > this.config.rpcSimulatePublicMaxGasLimit) {
|
|
1123
|
+
throw new BadRequestError(
|
|
1124
|
+
`Transaction total gas limit ${
|
|
1125
|
+
txGasLimit + teardownGasLimit
|
|
1126
|
+
} (${txGasLimit} + ${teardownGasLimit}) exceeds maximum gas limit ${
|
|
1127
|
+
this.config.rpcSimulatePublicMaxGasLimit
|
|
1128
|
+
} for simulation`,
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
const txHash = tx.getTxHash();
|
|
852
1133
|
const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
|
|
853
1134
|
|
|
854
1135
|
// If sequencer is not initialized, we just set these values to zero for simulation.
|
|
855
|
-
const coinbase =
|
|
856
|
-
const feeRecipient =
|
|
1136
|
+
const coinbase = EthAddress.ZERO;
|
|
1137
|
+
const feeRecipient = AztecAddress.ZERO;
|
|
857
1138
|
|
|
858
1139
|
const newGlobalVariables = await this.globalVariableBuilder.buildGlobalVariables(
|
|
859
|
-
|
|
1140
|
+
blockNumber,
|
|
860
1141
|
coinbase,
|
|
861
1142
|
feeRecipient,
|
|
862
1143
|
);
|
|
@@ -865,7 +1146,6 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
865
1146
|
new DateProvider(),
|
|
866
1147
|
this.telemetry,
|
|
867
1148
|
);
|
|
868
|
-
const fork = await this.worldStateSynchronizer.fork();
|
|
869
1149
|
|
|
870
1150
|
this.log.verbose(`Simulating public calls for tx ${txHash}`, {
|
|
871
1151
|
globalVariables: newGlobalVariables.toInspect(),
|
|
@@ -873,11 +1153,20 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
873
1153
|
blockNumber,
|
|
874
1154
|
});
|
|
875
1155
|
|
|
1156
|
+
const merkleTreeFork = await this.worldStateSynchronizer.fork();
|
|
876
1157
|
try {
|
|
877
|
-
const
|
|
1158
|
+
const config = PublicSimulatorConfig.from({
|
|
1159
|
+
skipFeeEnforcement,
|
|
1160
|
+
collectDebugLogs: true,
|
|
1161
|
+
collectHints: false,
|
|
1162
|
+
collectCallMetadata: true,
|
|
1163
|
+
maxDebugLogMemoryReads: this.config.rpcSimulatePublicMaxDebugLogMemoryReads,
|
|
1164
|
+
collectStatistics: false,
|
|
1165
|
+
});
|
|
1166
|
+
const processor = publicProcessorFactory.create(merkleTreeFork, newGlobalVariables, config);
|
|
878
1167
|
|
|
879
1168
|
// REFACTOR: Consider merging ProcessReturnValues into ProcessedTx
|
|
880
|
-
const [processedTxs, failedTxs, returns] = await processor.process([tx]);
|
|
1169
|
+
const [processedTxs, failedTxs, _usedTxs, returns] = await processor.process([tx]);
|
|
881
1170
|
// REFACTOR: Consider returning the error rather than throwing
|
|
882
1171
|
if (failedTxs.length) {
|
|
883
1172
|
this.log.warn(`Simulated tx ${txHash} fails: ${failedTxs[0].error}`, { txHash });
|
|
@@ -887,13 +1176,13 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
887
1176
|
const [processedTx] = processedTxs;
|
|
888
1177
|
return new PublicSimulationOutput(
|
|
889
1178
|
processedTx.revertReason,
|
|
890
|
-
processedTx.
|
|
1179
|
+
processedTx.globalVariables,
|
|
891
1180
|
processedTx.txEffect,
|
|
892
1181
|
returns,
|
|
893
1182
|
processedTx.gasUsed,
|
|
894
1183
|
);
|
|
895
1184
|
} finally {
|
|
896
|
-
await
|
|
1185
|
+
await merkleTreeFork.close();
|
|
897
1186
|
}
|
|
898
1187
|
}
|
|
899
1188
|
|
|
@@ -901,24 +1190,42 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
901
1190
|
tx: Tx,
|
|
902
1191
|
{ isSimulation, skipFeeEnforcement }: { isSimulation?: boolean; skipFeeEnforcement?: boolean } = {},
|
|
903
1192
|
): Promise<TxValidationResult> {
|
|
904
|
-
const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
|
|
905
1193
|
const db = this.worldStateSynchronizer.getCommitted();
|
|
906
1194
|
const verifier = isSimulation ? undefined : this.proofVerifier;
|
|
1195
|
+
|
|
1196
|
+
// We accept transactions if they are not expired by the next slot (checked based on the IncludeByTimestamp field)
|
|
1197
|
+
const { ts: nextSlotTimestamp } = this.epochCache.getEpochAndSlotInNextL1Slot();
|
|
1198
|
+
const blockNumber = (await this.blockSource.getBlockNumber()) + 1;
|
|
907
1199
|
const validator = createValidatorForAcceptingTxs(db, this.contractDataSource, verifier, {
|
|
1200
|
+
timestamp: nextSlotTimestamp,
|
|
908
1201
|
blockNumber,
|
|
909
1202
|
l1ChainId: this.l1ChainId,
|
|
910
|
-
|
|
1203
|
+
rollupVersion: this.version,
|
|
1204
|
+
setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()),
|
|
911
1205
|
gasFees: await this.getCurrentBaseFees(),
|
|
912
1206
|
skipFeeEnforcement,
|
|
1207
|
+
txsPermitted: !this.config.disableTransactions,
|
|
913
1208
|
});
|
|
914
1209
|
|
|
915
1210
|
return await validator.validateTx(tx);
|
|
916
1211
|
}
|
|
917
1212
|
|
|
918
|
-
public
|
|
919
|
-
const
|
|
920
|
-
|
|
1213
|
+
public getConfig(): Promise<AztecNodeAdminConfig> {
|
|
1214
|
+
const schema = AztecNodeAdminConfigSchema;
|
|
1215
|
+
const keys = schema.keyof().options;
|
|
1216
|
+
return Promise.resolve(pick(this.config, ...keys));
|
|
1217
|
+
}
|
|
921
1218
|
|
|
1219
|
+
public async setConfig(config: Partial<AztecNodeAdminConfig>): Promise<void> {
|
|
1220
|
+
const newConfig = { ...this.config, ...config };
|
|
1221
|
+
this.sequencer?.updateConfig(config);
|
|
1222
|
+
this.slasherClient?.updateConfig(config);
|
|
1223
|
+
this.validatorsSentinel?.updateConfig(config);
|
|
1224
|
+
await this.p2pClient.updateP2PConfig(config);
|
|
1225
|
+
const archiver = this.blockSource as Archiver;
|
|
1226
|
+
if ('updateConfig' in archiver) {
|
|
1227
|
+
archiver.updateConfig(config);
|
|
1228
|
+
}
|
|
922
1229
|
if (newConfig.realProofs !== this.config.realProofs) {
|
|
923
1230
|
this.proofVerifier = config.realProofs ? await BBCircuitVerifier.new(newConfig) : new TestCircuitVerifier();
|
|
924
1231
|
}
|
|
@@ -928,31 +1235,140 @@ export class AztecNodeService implements AztecNode, Traceable {
|
|
|
928
1235
|
|
|
929
1236
|
public getProtocolContractAddresses(): Promise<ProtocolContractAddresses> {
|
|
930
1237
|
return Promise.resolve({
|
|
931
|
-
|
|
1238
|
+
classRegistry: ProtocolContractAddress.ContractClassRegistry,
|
|
932
1239
|
feeJuice: ProtocolContractAddress.FeeJuice,
|
|
933
|
-
|
|
1240
|
+
instanceRegistry: ProtocolContractAddress.ContractInstanceRegistry,
|
|
934
1241
|
multiCallEntrypoint: ProtocolContractAddress.MultiCallEntrypoint,
|
|
935
1242
|
});
|
|
936
1243
|
}
|
|
937
1244
|
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
1245
|
+
public registerContractFunctionSignatures(signatures: string[]): Promise<void> {
|
|
1246
|
+
return this.contractDataSource.registerContractFunctionSignatures(signatures);
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
public getValidatorsStats(): Promise<ValidatorsStats> {
|
|
1250
|
+
return this.validatorsSentinel?.computeStats() ?? Promise.resolve({ stats: {}, slotWindow: 0 });
|
|
942
1251
|
}
|
|
943
1252
|
|
|
944
|
-
public
|
|
945
|
-
|
|
1253
|
+
public getValidatorStats(
|
|
1254
|
+
validatorAddress: EthAddress,
|
|
1255
|
+
fromSlot?: SlotNumber,
|
|
1256
|
+
toSlot?: SlotNumber,
|
|
1257
|
+
): Promise<SingleValidatorStats | undefined> {
|
|
1258
|
+
return this.validatorsSentinel?.getValidatorStats(validatorAddress, fromSlot, toSlot) ?? Promise.resolve(undefined);
|
|
946
1259
|
}
|
|
947
1260
|
|
|
948
|
-
public
|
|
949
|
-
|
|
950
|
-
|
|
1261
|
+
public async startSnapshotUpload(location: string): Promise<void> {
|
|
1262
|
+
// Note that we are forcefully casting the blocksource as an archiver
|
|
1263
|
+
// We break support for archiver running remotely to the node
|
|
1264
|
+
const archiver = this.blockSource as Archiver;
|
|
1265
|
+
if (!('backupTo' in archiver)) {
|
|
1266
|
+
this.metrics.recordSnapshotError();
|
|
1267
|
+
throw new Error('Archiver implementation does not support backups. Cannot generate snapshot.');
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
// Test that the archiver has done an initial sync.
|
|
1271
|
+
if (!archiver.isInitialSyncComplete()) {
|
|
1272
|
+
this.metrics.recordSnapshotError();
|
|
1273
|
+
throw new Error(`Archiver initial sync not complete. Cannot start snapshot.`);
|
|
1274
|
+
}
|
|
1275
|
+
|
|
1276
|
+
// And it has an L2 block hash
|
|
1277
|
+
const l2BlockHash = await archiver.getL2Tips().then(tips => tips.latest.hash);
|
|
1278
|
+
if (!l2BlockHash) {
|
|
1279
|
+
this.metrics.recordSnapshotError();
|
|
1280
|
+
throw new Error(`Archiver has no latest L2 block hash downloaded. Cannot start snapshot.`);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (this.isUploadingSnapshot) {
|
|
1284
|
+
this.metrics.recordSnapshotError();
|
|
1285
|
+
throw new Error(`Snapshot upload already in progress. Cannot start another one until complete.`);
|
|
951
1286
|
}
|
|
952
|
-
|
|
1287
|
+
|
|
1288
|
+
// Do not wait for the upload to be complete to return to the caller, but flag that an operation is in progress
|
|
1289
|
+
this.isUploadingSnapshot = true;
|
|
1290
|
+
const timer = new Timer();
|
|
1291
|
+
void uploadSnapshot(location, this.blockSource as Archiver, this.worldStateSynchronizer, this.config, this.log)
|
|
1292
|
+
.then(() => {
|
|
1293
|
+
this.isUploadingSnapshot = false;
|
|
1294
|
+
this.metrics.recordSnapshot(timer.ms());
|
|
1295
|
+
})
|
|
1296
|
+
.catch(err => {
|
|
1297
|
+
this.isUploadingSnapshot = false;
|
|
1298
|
+
this.metrics.recordSnapshotError();
|
|
1299
|
+
this.log.error(`Error uploading snapshot: ${err}`);
|
|
1300
|
+
});
|
|
1301
|
+
|
|
953
1302
|
return Promise.resolve();
|
|
954
1303
|
}
|
|
955
1304
|
|
|
1305
|
+
public async rollbackTo(targetBlock: number, force?: boolean): Promise<void> {
|
|
1306
|
+
const archiver = this.blockSource as Archiver;
|
|
1307
|
+
if (!('rollbackTo' in archiver)) {
|
|
1308
|
+
throw new Error('Archiver implementation does not support rollbacks.');
|
|
1309
|
+
}
|
|
1310
|
+
|
|
1311
|
+
const finalizedBlock = await archiver.getL2Tips().then(tips => tips.finalized.number);
|
|
1312
|
+
if (targetBlock < finalizedBlock) {
|
|
1313
|
+
if (force) {
|
|
1314
|
+
this.log.warn(`Clearing world state database to allow rolling back behind finalized block ${finalizedBlock}`);
|
|
1315
|
+
await this.worldStateSynchronizer.clear();
|
|
1316
|
+
await this.p2pClient.clear();
|
|
1317
|
+
} else {
|
|
1318
|
+
throw new Error(`Cannot rollback to block ${targetBlock} as it is before finalized ${finalizedBlock}`);
|
|
1319
|
+
}
|
|
1320
|
+
}
|
|
1321
|
+
|
|
1322
|
+
try {
|
|
1323
|
+
this.log.info(`Pausing archiver and world state sync to start rollback`);
|
|
1324
|
+
await archiver.stop();
|
|
1325
|
+
await this.worldStateSynchronizer.stopSync();
|
|
1326
|
+
const currentBlock = await archiver.getBlockNumber();
|
|
1327
|
+
const blocksToUnwind = currentBlock - targetBlock;
|
|
1328
|
+
this.log.info(`Unwinding ${count(blocksToUnwind, 'block')} from L2 block ${currentBlock} to ${targetBlock}`);
|
|
1329
|
+
await archiver.rollbackTo(targetBlock);
|
|
1330
|
+
this.log.info(`Unwinding complete.`);
|
|
1331
|
+
} catch (err) {
|
|
1332
|
+
this.log.error(`Error during rollback`, err);
|
|
1333
|
+
throw err;
|
|
1334
|
+
} finally {
|
|
1335
|
+
this.log.info(`Resuming world state and archiver sync.`);
|
|
1336
|
+
this.worldStateSynchronizer.resumeSync();
|
|
1337
|
+
archiver.resume();
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
|
|
1341
|
+
public async pauseSync(): Promise<void> {
|
|
1342
|
+
this.log.info(`Pausing archiver and world state sync`);
|
|
1343
|
+
await (this.blockSource as Archiver).stop();
|
|
1344
|
+
await this.worldStateSynchronizer.stopSync();
|
|
1345
|
+
}
|
|
1346
|
+
|
|
1347
|
+
public resumeSync(): Promise<void> {
|
|
1348
|
+
this.log.info(`Resuming world state and archiver sync.`);
|
|
1349
|
+
this.worldStateSynchronizer.resumeSync();
|
|
1350
|
+
(this.blockSource as Archiver).resume();
|
|
1351
|
+
return Promise.resolve();
|
|
1352
|
+
}
|
|
1353
|
+
|
|
1354
|
+
public getSlashPayloads(): Promise<SlashPayloadRound[]> {
|
|
1355
|
+
if (!this.slasherClient) {
|
|
1356
|
+
throw new Error(`Slasher client not enabled`);
|
|
1357
|
+
}
|
|
1358
|
+
return this.slasherClient.getSlashPayloads();
|
|
1359
|
+
}
|
|
1360
|
+
|
|
1361
|
+
public getSlashOffenses(round: bigint | 'all' | 'current'): Promise<Offense[]> {
|
|
1362
|
+
if (!this.slasherClient) {
|
|
1363
|
+
throw new Error(`Slasher client not enabled`);
|
|
1364
|
+
}
|
|
1365
|
+
if (round === 'all') {
|
|
1366
|
+
return this.slasherClient.getPendingOffenses();
|
|
1367
|
+
} else {
|
|
1368
|
+
return this.slasherClient.gatherOffensesForRound(round === 'current' ? undefined : BigInt(round));
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
|
|
956
1372
|
/**
|
|
957
1373
|
* Returns an instance of MerkleTreeOperations having first ensured the world state is fully synched
|
|
958
1374
|
* @param blockNumber - The block number at which to get the data.
|