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