@aztec/sequencer-client 0.28.1 → 0.30.0
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/block_builder/index.d.ts +2 -4
- package/dest/block_builder/index.d.ts.map +1 -1
- package/dest/block_builder/solo_block_builder.d.ts +4 -7
- package/dest/block_builder/solo_block_builder.d.ts.map +1 -1
- package/dest/block_builder/solo_block_builder.js +18 -20
- package/dest/client/sequencer-client.d.ts +4 -3
- package/dest/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +2 -2
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +6 -2
- package/dest/sequencer/abstract_phase_manager.d.ts.map +1 -1
- package/dest/sequencer/abstract_phase_manager.js +10 -11
- package/dest/sequencer/hints_builder.d.ts +1 -1
- package/dest/sequencer/hints_builder.d.ts.map +1 -1
- package/dest/sequencer/hints_builder.js +4 -4
- package/dest/sequencer/processed_tx.d.ts.map +1 -1
- package/dest/sequencer/processed_tx.js +3 -3
- package/dest/sequencer/public_processor.d.ts +2 -1
- package/dest/sequencer/public_processor.d.ts.map +1 -1
- package/dest/sequencer/public_processor.js +1 -1
- package/dest/sequencer/sequencer.d.ts +6 -26
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +26 -101
- package/dest/sequencer/tx_validator.d.ts +22 -0
- package/dest/sequencer/tx_validator.d.ts.map +1 -0
- package/dest/sequencer/tx_validator.js +125 -0
- package/dest/simulator/public_executor.d.ts +3 -5
- package/dest/simulator/public_executor.d.ts.map +1 -1
- package/dest/simulator/public_executor.js +18 -34
- package/package.json +13 -13
- package/src/block_builder/index.ts +2 -8
- package/src/block_builder/solo_block_builder.ts +18 -29
- package/src/client/sequencer-client.ts +3 -1
- package/src/config.ts +6 -0
- package/src/sequencer/abstract_phase_manager.ts +9 -12
- package/src/sequencer/hints_builder.ts +2 -8
- package/src/sequencer/processed_tx.ts +2 -0
- package/src/sequencer/public_processor.ts +2 -1
- package/src/sequencer/sequencer.ts +31 -117
- package/src/sequencer/tx_validator.ts +189 -0
- package/src/simulator/public_executor.ts +15 -42
|
@@ -11,6 +11,7 @@ import {
|
|
|
11
11
|
PublicKernelCircuitPublicInputs,
|
|
12
12
|
SideEffect,
|
|
13
13
|
SideEffectLinkedToNoteHash,
|
|
14
|
+
ValidationRequests,
|
|
14
15
|
makeEmptyProof,
|
|
15
16
|
} from '@aztec/circuits.js';
|
|
16
17
|
import { Tuple, fromFieldsTuple } from '@aztec/foundation/serialize';
|
|
@@ -109,6 +110,7 @@ export function getPreviousOutputAndProof(
|
|
|
109
110
|
} else {
|
|
110
111
|
const publicKernelPublicInput = new PublicKernelCircuitPublicInputs(
|
|
111
112
|
tx.data.aggregationObject,
|
|
113
|
+
ValidationRequests.empty(),
|
|
112
114
|
PublicAccumulatedNonRevertibleData.fromPrivateAccumulatedNonRevertibleData(tx.data.endNonRevertibleData),
|
|
113
115
|
PublicAccumulatedRevertibleData.fromPrivateAccumulatedRevertibleData(tx.data.end),
|
|
114
116
|
tx.data.constants,
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { L1ToL2MessageSource, SimulationError, Tx } from '@aztec/circuit-types';
|
|
2
2
|
import { TxSequencerProcessingStats } from '@aztec/circuit-types/stats';
|
|
3
3
|
import { GlobalVariables, Header } from '@aztec/circuits.js';
|
|
4
4
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
5
5
|
import { Timer } from '@aztec/foundation/timer';
|
|
6
6
|
import { PublicExecutor, PublicStateDB } from '@aztec/simulator';
|
|
7
|
+
import { ContractDataSource } from '@aztec/types/contracts';
|
|
7
8
|
import { MerkleTreeOperations } from '@aztec/world-state';
|
|
8
9
|
|
|
9
10
|
import { EmptyPublicProver } from '../prover/empty.js';
|
|
@@ -12,10 +12,12 @@ import { WorldStateStatus, WorldStateSynchronizer } from '@aztec/world-state';
|
|
|
12
12
|
import { BlockBuilder } from '../block_builder/index.js';
|
|
13
13
|
import { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
14
14
|
import { L1Publisher } from '../publisher/l1-publisher.js';
|
|
15
|
+
import { WorldStatePublicDB } from '../simulator/public_executor.js';
|
|
15
16
|
import { ceilPowerOfTwo } from '../utils.js';
|
|
16
17
|
import { SequencerConfig } from './config.js';
|
|
17
18
|
import { ProcessedTx } from './processed_tx.js';
|
|
18
19
|
import { PublicProcessorFactory } from './public_processor.js';
|
|
20
|
+
import { TxValidator } from './tx_validator.js';
|
|
19
21
|
|
|
20
22
|
/**
|
|
21
23
|
* Sequencer client
|
|
@@ -47,6 +49,7 @@ export class Sequencer {
|
|
|
47
49
|
private l1ToL2MessageSource: L1ToL2MessageSource,
|
|
48
50
|
private publicProcessorFactory: PublicProcessorFactory,
|
|
49
51
|
config: SequencerConfig = {},
|
|
52
|
+
private gasPortalAddress = EthAddress.ZERO,
|
|
50
53
|
private log = createDebugLogger('aztec:sequencer'),
|
|
51
54
|
) {
|
|
52
55
|
this.updateConfig(config);
|
|
@@ -171,8 +174,20 @@ export class Sequencer {
|
|
|
171
174
|
);
|
|
172
175
|
|
|
173
176
|
// Filter out invalid txs
|
|
177
|
+
const trees = this.worldState.getLatest();
|
|
178
|
+
const txValidator = new TxValidator(
|
|
179
|
+
{
|
|
180
|
+
getNullifierIndex(nullifier: Fr): Promise<bigint | undefined> {
|
|
181
|
+
return trees.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
new WorldStatePublicDB(trees),
|
|
185
|
+
this.gasPortalAddress,
|
|
186
|
+
newGlobalVariables,
|
|
187
|
+
);
|
|
188
|
+
|
|
174
189
|
// TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
|
|
175
|
-
const validTxs = await this.takeValidTxs(pendingTxs,
|
|
190
|
+
const validTxs = await this.takeValidTxs(pendingTxs, txValidator);
|
|
176
191
|
if (validTxs.length < this.minTxsPerBLock) {
|
|
177
192
|
return;
|
|
178
193
|
}
|
|
@@ -193,7 +208,7 @@ export class Sequencer {
|
|
|
193
208
|
// public functions emitting nullifiers would pass earlier check but fail here.
|
|
194
209
|
// Note that we're checking all nullifiers generated in the private execution twice,
|
|
195
210
|
// we could store the ones already checked and skip them here as an optimization.
|
|
196
|
-
const processedValidTxs = await this.takeValidTxs(processedTxs,
|
|
211
|
+
const processedValidTxs = await this.takeValidTxs(processedTxs, txValidator);
|
|
197
212
|
|
|
198
213
|
if (processedValidTxs.length === 0) {
|
|
199
214
|
this.log('No txs processed correctly to build block. Exiting');
|
|
@@ -202,12 +217,10 @@ export class Sequencer {
|
|
|
202
217
|
|
|
203
218
|
await assertBlockHeight();
|
|
204
219
|
|
|
205
|
-
const newModelL1ToL2Messages = await this.l1ToL2MessageSource.getNewL1ToL2Messages(BigInt(newBlockNumber));
|
|
206
|
-
|
|
207
220
|
// Get l1 to l2 messages from the contract
|
|
208
221
|
this.log('Requesting L1 to L2 messages from contract');
|
|
209
|
-
const l1ToL2Messages = await this.
|
|
210
|
-
this.log(
|
|
222
|
+
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(newBlockNumber));
|
|
223
|
+
this.log(`Retrieved ${l1ToL2Messages.length} L1 to L2 messages for block ${newBlockNumber}`);
|
|
211
224
|
|
|
212
225
|
// Build the new block by running the rollup circuits
|
|
213
226
|
this.log(`Assembling block with txs ${processedValidTxs.map(tx => tx.hash).join(', ')}`);
|
|
@@ -216,7 +229,7 @@ export class Sequencer {
|
|
|
216
229
|
|
|
217
230
|
const emptyTx = processor.makeEmptyProcessedTx();
|
|
218
231
|
const [rollupCircuitsDuration, block] = await elapsed(() =>
|
|
219
|
-
this.buildBlock(processedValidTxs,
|
|
232
|
+
this.buildBlock(processedValidTxs, l1ToL2Messages, emptyTx, newGlobalVariables),
|
|
220
233
|
);
|
|
221
234
|
|
|
222
235
|
this.log(`Assembled block ${block.number}`, {
|
|
@@ -253,47 +266,14 @@ export class Sequencer {
|
|
|
253
266
|
}
|
|
254
267
|
}
|
|
255
268
|
|
|
256
|
-
protected async takeValidTxs<T extends Tx | ProcessedTx>(txs: T[],
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
// Process txs until we get to maxTxsPerBlock, rejecting double spends in the process
|
|
262
|
-
for (const tx of txs) {
|
|
263
|
-
if (tx.data.constants.txContext.chainId.value !== globalVariables.chainId.value) {
|
|
264
|
-
this.log(
|
|
265
|
-
`Deleting tx for incorrect chain ${tx.data.constants.txContext.chainId.toString()}, tx hash ${Tx.getHash(
|
|
266
|
-
tx,
|
|
267
|
-
)}`,
|
|
268
|
-
);
|
|
269
|
-
txsToDelete.push(tx);
|
|
270
|
-
continue;
|
|
271
|
-
}
|
|
272
|
-
if (await this.isTxDoubleSpend(tx)) {
|
|
273
|
-
this.log(`Deleting double spend tx ${Tx.getHash(tx)}`);
|
|
274
|
-
txsToDelete.push(tx);
|
|
275
|
-
continue;
|
|
276
|
-
} else if (this.isTxDoubleSpendSameBlock(tx, thisBlockNullifiers)) {
|
|
277
|
-
// We don't drop these txs from the p2p pool immediately since they become valid
|
|
278
|
-
// again if the current block fails to be published for some reason.
|
|
279
|
-
this.log(`Skipping tx with double-spend for this same block ${Tx.getHash(tx)}`);
|
|
280
|
-
continue;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
tx.data.end.newNullifiers.forEach(n => thisBlockNullifiers.add(n.value.toBigInt()));
|
|
284
|
-
tx.data.endNonRevertibleData.newNullifiers.forEach(n => thisBlockNullifiers.add(n.value.toBigInt()));
|
|
285
|
-
validTxs.push(tx);
|
|
286
|
-
if (validTxs.length >= this.maxTxsPerBlock) {
|
|
287
|
-
break;
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Make sure we remove these from the tx pool so we do not consider it again
|
|
292
|
-
if (txsToDelete.length > 0) {
|
|
293
|
-
await this.p2pClient.deleteTxs(Tx.getHashes([...txsToDelete]));
|
|
269
|
+
protected async takeValidTxs<T extends Tx | ProcessedTx>(txs: T[], validator: TxValidator): Promise<T[]> {
|
|
270
|
+
const [valid, invalid] = await validator.validateTxs(txs);
|
|
271
|
+
if (invalid.length > 0) {
|
|
272
|
+
this.log(`Dropping invalid txs from the p2p pool ${Tx.getHashes(invalid).join(', ')}`);
|
|
273
|
+
await this.p2pClient.deleteTxs(Tx.getHashes(invalid));
|
|
294
274
|
}
|
|
295
275
|
|
|
296
|
-
return
|
|
276
|
+
return valid.slice(0, this.maxTxsPerBlock);
|
|
297
277
|
}
|
|
298
278
|
|
|
299
279
|
/**
|
|
@@ -314,16 +294,14 @@ export class Sequencer {
|
|
|
314
294
|
/**
|
|
315
295
|
* Pads the set of txs to a power of two and assembles a block by calling the block builder.
|
|
316
296
|
* @param txs - Processed txs to include in the next block.
|
|
317
|
-
* @param
|
|
318
|
-
* @param newL1ToL2Messages - L1 to L2 messages to be part of the block.
|
|
297
|
+
* @param l1ToL2Messages - L1 to L2 messages to be part of the block.
|
|
319
298
|
* @param emptyTx - Empty tx to repeat at the end of the block to pad to a power of two.
|
|
320
299
|
* @param globalVariables - Global variables to use in the block.
|
|
321
300
|
* @returns The new block.
|
|
322
301
|
*/
|
|
323
302
|
protected async buildBlock(
|
|
324
303
|
txs: ProcessedTx[],
|
|
325
|
-
|
|
326
|
-
newL1ToL2Messages: Fr[], // TODO(#4492): Nuke this when purging the old inbox
|
|
304
|
+
l1ToL2Messages: Fr[],
|
|
327
305
|
emptyTx: ProcessedTx,
|
|
328
306
|
globalVariables: GlobalVariables,
|
|
329
307
|
) {
|
|
@@ -332,74 +310,10 @@ export class Sequencer {
|
|
|
332
310
|
const emptyTxCount = txsTargetSize - txs.length;
|
|
333
311
|
|
|
334
312
|
const allTxs = [...txs, ...times(emptyTxCount, () => emptyTx)];
|
|
335
|
-
this.log(`Building block ${globalVariables.blockNumber}`);
|
|
336
|
-
|
|
337
|
-
const [block] = await this.blockBuilder.buildL2Block(
|
|
338
|
-
globalVariables,
|
|
339
|
-
allTxs,
|
|
340
|
-
newModelL1ToL2Messages,
|
|
341
|
-
newL1ToL2Messages,
|
|
342
|
-
);
|
|
343
|
-
return block;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
/**
|
|
347
|
-
* Calls the archiver to pull upto `NUMBER_OF_L1_L2_MESSAGES_PER_ROLLUP` entry keys
|
|
348
|
-
* (archiver returns the top messages sorted by fees)
|
|
349
|
-
* @returns An array of L1 to L2 messages' entryKeys
|
|
350
|
-
*/
|
|
351
|
-
protected async getPendingL1ToL2EntryKeys(): Promise<Fr[]> {
|
|
352
|
-
return await this.l1ToL2MessageSource.getPendingL1ToL2EntryKeys();
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
/**
|
|
356
|
-
* Returns true if one of the tx nullifiers exist on the block being built.
|
|
357
|
-
* @param tx - The tx to test.
|
|
358
|
-
* @param thisBlockNullifiers - The nullifiers added so far.
|
|
359
|
-
*/
|
|
360
|
-
protected isTxDoubleSpendSameBlock(tx: Tx | ProcessedTx, thisBlockNullifiers: Set<bigint>): boolean {
|
|
361
|
-
// We only consider non-empty nullifiers
|
|
362
|
-
const newNullifiers = [
|
|
363
|
-
...tx.data.endNonRevertibleData.newNullifiers.filter(n => !n.isEmpty()),
|
|
364
|
-
...tx.data.end.newNullifiers.filter(n => !n.isEmpty()),
|
|
365
|
-
];
|
|
366
|
-
|
|
367
|
-
for (const nullifier of newNullifiers) {
|
|
368
|
-
if (thisBlockNullifiers.has(nullifier.value.toBigInt())) {
|
|
369
|
-
return true;
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
313
|
+
this.log(`Building block ${globalVariables.blockNumber.toBigInt()}`);
|
|
374
314
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
* Nullifiers prevent double spends in a private context.
|
|
378
|
-
* @param tx - The transaction.
|
|
379
|
-
* @returns Whether this is a problematic double spend that the L1 contract would reject.
|
|
380
|
-
*/
|
|
381
|
-
protected async isTxDoubleSpend(tx: Tx | ProcessedTx): Promise<boolean> {
|
|
382
|
-
// We only consider non-empty nullifiers
|
|
383
|
-
const newNullifiers = [
|
|
384
|
-
...tx.data.endNonRevertibleData.newNullifiers.filter(n => !n.isEmpty()),
|
|
385
|
-
...tx.data.end.newNullifiers.filter(n => !n.isEmpty()),
|
|
386
|
-
];
|
|
387
|
-
|
|
388
|
-
// Ditch this tx if it has a repeated nullifiers
|
|
389
|
-
const uniqNullifiers = new Set(newNullifiers.map(n => n.value.toBigInt()));
|
|
390
|
-
if (uniqNullifiers.size !== newNullifiers.length) {
|
|
391
|
-
return true;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
for (const nullifier of newNullifiers) {
|
|
395
|
-
// TODO(AD): this is an exhaustive search currently
|
|
396
|
-
const db = this.worldState.getLatest();
|
|
397
|
-
const indexInDb = await db.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer());
|
|
398
|
-
if (indexInDb !== undefined) {
|
|
399
|
-
return true;
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
return false;
|
|
315
|
+
const [block] = await this.blockBuilder.buildL2Block(globalVariables, allTxs, l1ToL2Messages);
|
|
316
|
+
return block;
|
|
403
317
|
}
|
|
404
318
|
|
|
405
319
|
get coinbase(): EthAddress {
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { Tx } from '@aztec/circuit-types';
|
|
2
|
+
import { AztecAddress, EthAddress, Fr, GlobalVariables } from '@aztec/circuits.js';
|
|
3
|
+
import { pedersenHash } from '@aztec/foundation/crypto';
|
|
4
|
+
import { Logger, createDebugLogger } from '@aztec/foundation/log';
|
|
5
|
+
import { getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token';
|
|
6
|
+
|
|
7
|
+
import { AbstractPhaseManager, PublicKernelPhase } from './abstract_phase_manager.js';
|
|
8
|
+
import { ProcessedTx } from './processed_tx.js';
|
|
9
|
+
|
|
10
|
+
/** A source of what nullifiers have been committed to the state trees */
|
|
11
|
+
export interface NullifierSource {
|
|
12
|
+
getNullifierIndex: (nullifier: Fr) => Promise<bigint | undefined>;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** Provides a view into public contract state */
|
|
16
|
+
export interface PublicStateSource {
|
|
17
|
+
storageRead: (contractAddress: AztecAddress, slot: Fr) => Promise<Fr>;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// prefer symbols over booleans so it's clear what the intention is
|
|
21
|
+
// vs returning true/false is tied to the function name
|
|
22
|
+
// eg. isDoubleSpend vs isValidChain assign different meanings to booleans
|
|
23
|
+
const VALID_TX = Symbol('valid_tx');
|
|
24
|
+
const INVALID_TX = Symbol('invalid_tx');
|
|
25
|
+
|
|
26
|
+
type TxValidationStatus = typeof VALID_TX | typeof INVALID_TX;
|
|
27
|
+
|
|
28
|
+
// the storage slot associated with "storage.balances"
|
|
29
|
+
const GAS_TOKEN_BALANCES_SLOT = new Fr(1);
|
|
30
|
+
|
|
31
|
+
export class TxValidator {
|
|
32
|
+
#log: Logger;
|
|
33
|
+
#globalVariables: GlobalVariables;
|
|
34
|
+
#nullifierSource: NullifierSource;
|
|
35
|
+
#publicStateSource: PublicStateSource;
|
|
36
|
+
#gasPortalAddress: EthAddress;
|
|
37
|
+
|
|
38
|
+
constructor(
|
|
39
|
+
nullifierSource: NullifierSource,
|
|
40
|
+
publicStateSource: PublicStateSource,
|
|
41
|
+
gasPortalAddress: EthAddress,
|
|
42
|
+
globalVariables: GlobalVariables,
|
|
43
|
+
log = createDebugLogger('aztec:sequencer:tx_validator'),
|
|
44
|
+
) {
|
|
45
|
+
this.#nullifierSource = nullifierSource;
|
|
46
|
+
this.#globalVariables = globalVariables;
|
|
47
|
+
this.#publicStateSource = publicStateSource;
|
|
48
|
+
this.#gasPortalAddress = gasPortalAddress;
|
|
49
|
+
|
|
50
|
+
this.#log = log;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Validates a list of transactions.
|
|
55
|
+
* @param txs - The transactions to validate.
|
|
56
|
+
* @returns A tuple of valid and invalid transactions.
|
|
57
|
+
*/
|
|
58
|
+
public async validateTxs<T extends Tx | ProcessedTx>(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
|
|
59
|
+
const validTxs: T[] = [];
|
|
60
|
+
const invalidTxs: T[] = [];
|
|
61
|
+
const thisBlockNullifiers = new Set<bigint>();
|
|
62
|
+
|
|
63
|
+
for (const tx of txs) {
|
|
64
|
+
if (this.#validateMetadata(tx) === INVALID_TX) {
|
|
65
|
+
invalidTxs.push(tx);
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
if ((await this.#validateNullifiers(tx, thisBlockNullifiers)) === INVALID_TX) {
|
|
70
|
+
invalidTxs.push(tx);
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// skip already processed transactions
|
|
75
|
+
if (tx instanceof Tx && (await this.#validateFee(tx)) === INVALID_TX) {
|
|
76
|
+
invalidTxs.push(tx);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
validTxs.push(tx);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return [validTxs, invalidTxs];
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* It rejects transactions with the wrong chain id.
|
|
88
|
+
* @param tx - The transaction.
|
|
89
|
+
* @returns Whether the transaction is valid.
|
|
90
|
+
*/
|
|
91
|
+
#validateMetadata(tx: Tx | ProcessedTx): TxValidationStatus {
|
|
92
|
+
if (!tx.data.constants.txContext.chainId.equals(this.#globalVariables.chainId)) {
|
|
93
|
+
this.#log.warn(
|
|
94
|
+
`Rejecting tx ${Tx.getHash(
|
|
95
|
+
tx,
|
|
96
|
+
)} because of incorrect chain ${tx.data.constants.txContext.chainId.toString()} != ${this.#globalVariables.chainId.toString()}`,
|
|
97
|
+
);
|
|
98
|
+
return INVALID_TX;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
return VALID_TX;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* It looks for duplicate nullifiers:
|
|
106
|
+
* - in the same transaction
|
|
107
|
+
* - in the same block
|
|
108
|
+
* - in the nullifier tree
|
|
109
|
+
*
|
|
110
|
+
* Nullifiers prevent double spends in a private context.
|
|
111
|
+
*
|
|
112
|
+
* @param tx - The transaction.
|
|
113
|
+
* @returns Whether this is a problematic double spend that the L1 contract would reject.
|
|
114
|
+
*/
|
|
115
|
+
async #validateNullifiers(tx: Tx | ProcessedTx, thisBlockNullifiers: Set<bigint>): Promise<TxValidationStatus> {
|
|
116
|
+
const newNullifiers = [...tx.data.endNonRevertibleData.newNullifiers, ...tx.data.end.newNullifiers]
|
|
117
|
+
.filter(x => !x.isEmpty())
|
|
118
|
+
.map(x => x.value.toBigInt());
|
|
119
|
+
|
|
120
|
+
// Ditch this tx if it has repeated nullifiers
|
|
121
|
+
const uniqueNullifiers = new Set(newNullifiers);
|
|
122
|
+
if (uniqueNullifiers.size !== newNullifiers.length) {
|
|
123
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for emitting duplicate nullifiers`);
|
|
124
|
+
return INVALID_TX;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
for (const nullifier of newNullifiers) {
|
|
128
|
+
if (thisBlockNullifiers.has(nullifier)) {
|
|
129
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating a nullifier in the same block`);
|
|
130
|
+
return INVALID_TX;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
thisBlockNullifiers.add(nullifier);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const nullifierIndexes = await Promise.all(
|
|
137
|
+
newNullifiers.map(n => this.#nullifierSource.getNullifierIndex(new Fr(n))),
|
|
138
|
+
);
|
|
139
|
+
|
|
140
|
+
const hasDuplicates = nullifierIndexes.some(index => index !== undefined);
|
|
141
|
+
if (hasDuplicates) {
|
|
142
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating nullifiers present in state trees`);
|
|
143
|
+
return INVALID_TX;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return VALID_TX;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async #validateFee(tx: Tx): Promise<TxValidationStatus> {
|
|
150
|
+
if (!tx.data.needsTeardown) {
|
|
151
|
+
// TODO check if fees are mandatory and reject this tx
|
|
152
|
+
this.#log.debug(`Tx ${Tx.getHash(tx)} doesn't pay for gas`);
|
|
153
|
+
return VALID_TX;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const {
|
|
157
|
+
// TODO what if there's more than one function call?
|
|
158
|
+
// if we're to enshrine that teardown = 1 function call, then we should turn this into a single function call
|
|
159
|
+
[PublicKernelPhase.TEARDOWN]: [teardownFn],
|
|
160
|
+
} = AbstractPhaseManager.extractEnqueuedPublicCallsByPhase(tx.data, tx.enqueuedPublicFunctionCalls);
|
|
161
|
+
|
|
162
|
+
if (!teardownFn) {
|
|
163
|
+
this.#log.warn(
|
|
164
|
+
`Rejecting tx ${Tx.getHash(tx)} because it should pay for gas but has no enqueued teardown function call`,
|
|
165
|
+
);
|
|
166
|
+
return INVALID_TX;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// TODO(#1204) if a generator index is used for the derived storage slot of a map, update it here as well
|
|
170
|
+
const slot = pedersenHash([GAS_TOKEN_BALANCES_SLOT.toBuffer(), teardownFn.callContext.msgSender.toBuffer()]);
|
|
171
|
+
const gasBalance = await this.#publicStateSource.storageRead(
|
|
172
|
+
getCanonicalGasTokenAddress(this.#gasPortalAddress),
|
|
173
|
+
slot,
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
// TODO(#5004) calculate fee needed based on tx limits and gas prices
|
|
177
|
+
const gasAmountNeeded = new Fr(1);
|
|
178
|
+
if (gasBalance.lt(gasAmountNeeded)) {
|
|
179
|
+
this.#log.warn(
|
|
180
|
+
`Rejecting tx ${Tx.getHash(
|
|
181
|
+
tx,
|
|
182
|
+
)} because it should pay for gas but has insufficient balance ${gasBalance.toShortString()} < ${gasAmountNeeded.toShortString()}`,
|
|
183
|
+
);
|
|
184
|
+
return INVALID_TX;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return VALID_TX;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
@@ -1,6 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
ContractDataSource,
|
|
3
|
-
ExtendedContractData,
|
|
4
2
|
L1ToL2MessageSource,
|
|
5
3
|
MerkleTreeId,
|
|
6
4
|
NullifierMembershipWitness,
|
|
@@ -22,9 +20,8 @@ import {
|
|
|
22
20
|
import { computePublicDataTreeLeafSlot } from '@aztec/circuits.js/hash';
|
|
23
21
|
import { createDebugLogger } from '@aztec/foundation/log';
|
|
24
22
|
import { ClassRegistererAddress } from '@aztec/protocol-contracts/class-registerer';
|
|
25
|
-
import { InstanceDeployerAddress } from '@aztec/protocol-contracts/instance-deployer';
|
|
26
23
|
import { CommitmentsDB, MessageLoadOracleInputs, PublicContractsDB, PublicStateDB } from '@aztec/simulator';
|
|
27
|
-
import { ContractClassPublic, ContractInstanceWithAddress } from '@aztec/types/contracts';
|
|
24
|
+
import { ContractClassPublic, ContractDataSource, ContractInstanceWithAddress } from '@aztec/types/contracts';
|
|
28
25
|
import { MerkleTreeOperations } from '@aztec/world-state';
|
|
29
26
|
|
|
30
27
|
/**
|
|
@@ -32,7 +29,6 @@ import { MerkleTreeOperations } from '@aztec/world-state';
|
|
|
32
29
|
* Progressively records contracts in transaction as they are processed in a block.
|
|
33
30
|
*/
|
|
34
31
|
export class ContractsDataSourcePublicDB implements PublicContractsDB {
|
|
35
|
-
private cache = new Map<string, ExtendedContractData>();
|
|
36
32
|
private instanceCache = new Map<string, ContractInstanceWithAddress>();
|
|
37
33
|
private classCache = new Map<string, ContractClassPublic>();
|
|
38
34
|
|
|
@@ -51,7 +47,7 @@ export class ContractsDataSourcePublicDB implements PublicContractsDB {
|
|
|
51
47
|
this.log(`Adding class ${e.contractClassId.toString()} to public execution contract cache`);
|
|
52
48
|
this.classCache.set(e.contractClassId.toString(), e.toContractClassPublic());
|
|
53
49
|
});
|
|
54
|
-
ContractInstanceDeployedEvent.fromLogs(logs
|
|
50
|
+
ContractInstanceDeployedEvent.fromLogs(logs).forEach(e => {
|
|
55
51
|
this.log(
|
|
56
52
|
`Adding instance ${e.address.toString()} with class ${e.contractClassId.toString()} to public execution contract cache`,
|
|
57
53
|
);
|
|
@@ -73,9 +69,7 @@ export class ContractsDataSourcePublicDB implements PublicContractsDB {
|
|
|
73
69
|
ContractClassRegisteredEvent.fromLogs(logs, ClassRegistererAddress).forEach(e =>
|
|
74
70
|
this.classCache.delete(e.contractClassId.toString()),
|
|
75
71
|
);
|
|
76
|
-
ContractInstanceDeployedEvent.fromLogs(logs
|
|
77
|
-
this.instanceCache.delete(e.address.toString()),
|
|
78
|
-
);
|
|
72
|
+
ContractInstanceDeployedEvent.fromLogs(logs).forEach(e => this.instanceCache.delete(e.address.toString()));
|
|
79
73
|
return Promise.resolve();
|
|
80
74
|
}
|
|
81
75
|
|
|
@@ -83,46 +77,25 @@ export class ContractsDataSourcePublicDB implements PublicContractsDB {
|
|
|
83
77
|
return this.instanceCache.get(address.toString()) ?? (await this.db.getContract(address));
|
|
84
78
|
}
|
|
85
79
|
|
|
86
|
-
async
|
|
87
|
-
|
|
88
|
-
return contract?.getPublicFunction(selector)?.bytecode;
|
|
80
|
+
public async getContractClass(contractClassId: Fr): Promise<ContractClassPublic | undefined> {
|
|
81
|
+
return this.classCache.get(contractClassId.toString()) ?? (await this.db.getContractClass(contractClassId));
|
|
89
82
|
}
|
|
90
83
|
|
|
91
|
-
async
|
|
92
|
-
const
|
|
93
|
-
return contract?.getPublicFunction(selector)?.isInternal;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
async getPortalContractAddress(address: AztecAddress): Promise<EthAddress | undefined> {
|
|
97
|
-
const contract = await this.#getContract(address);
|
|
98
|
-
return contract?.contractData.portalContractAddress;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
async #getContract(address: AztecAddress): Promise<ExtendedContractData | undefined> {
|
|
102
|
-
return (
|
|
103
|
-
this.cache.get(address.toString()) ??
|
|
104
|
-
(await this.#makeExtendedContractDataFor(address)) ??
|
|
105
|
-
(await this.db.getExtendedContractData(address))
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
async #makeExtendedContractDataFor(address: AztecAddress): Promise<ExtendedContractData | undefined> {
|
|
110
|
-
const instance = this.instanceCache.get(address.toString());
|
|
84
|
+
async getBytecode(address: AztecAddress, selector: FunctionSelector): Promise<Buffer | undefined> {
|
|
85
|
+
const instance = await this.getContractInstance(address);
|
|
111
86
|
if (!instance) {
|
|
112
|
-
|
|
87
|
+
throw new Error(`Contract ${address.toString()} not found`);
|
|
113
88
|
}
|
|
114
|
-
|
|
115
|
-
const contractClass =
|
|
116
|
-
this.classCache.get(instance.contractClassId.toString()) ??
|
|
117
|
-
(await this.db.getContractClass(instance.contractClassId));
|
|
89
|
+
const contractClass = await this.getContractClass(instance.contractClassId);
|
|
118
90
|
if (!contractClass) {
|
|
119
|
-
|
|
120
|
-
`Contract class ${instance.contractClassId.toString()} for address ${address.toString()} not found`,
|
|
121
|
-
);
|
|
122
|
-
return undefined;
|
|
91
|
+
throw new Error(`Contract class ${instance.contractClassId.toString()} for ${address.toString()} not found`);
|
|
123
92
|
}
|
|
93
|
+
return contractClass.publicFunctions.find(f => f.selector.equals(selector))?.bytecode;
|
|
94
|
+
}
|
|
124
95
|
|
|
125
|
-
|
|
96
|
+
async getPortalContractAddress(address: AztecAddress): Promise<EthAddress | undefined> {
|
|
97
|
+
const contract = await this.getContractInstance(address);
|
|
98
|
+
return contract?.portalContractAddress;
|
|
126
99
|
}
|
|
127
100
|
}
|
|
128
101
|
|