@aztec/sequencer-client 0.33.0 → 0.35.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/client/sequencer-client.d.ts.map +1 -1
- package/dest/client/sequencer-client.js +3 -3
- package/dest/config.d.ts.map +1 -1
- package/dest/config.js +84 -7
- package/dest/global_variable_builder/global_builder.d.ts.map +1 -1
- package/dest/global_variable_builder/global_builder.js +5 -4
- package/dest/index.d.ts +0 -1
- package/dest/index.d.ts.map +1 -1
- package/dest/index.js +1 -2
- package/dest/publisher/l1-publisher.js +10 -10
- package/dest/publisher/viem-tx-sender.js +2 -2
- package/dest/sequencer/sequencer.d.ts +7 -8
- package/dest/sequencer/sequencer.d.ts.map +1 -1
- package/dest/sequencer/sequencer.js +22 -24
- package/dest/tx_validator/aggregate_tx_validator.d.ts +7 -0
- package/dest/tx_validator/aggregate_tx_validator.d.ts.map +1 -0
- package/dest/tx_validator/aggregate_tx_validator.js +23 -0
- package/dest/tx_validator/double_spend_validator.d.ts +11 -0
- package/dest/tx_validator/double_spend_validator.d.ts.map +1 -0
- package/dest/tx_validator/double_spend_validator.js +50 -0
- package/dest/tx_validator/gas_validator.d.ts +12 -0
- package/dest/tx_validator/gas_validator.d.ts.map +1 -0
- package/dest/tx_validator/gas_validator.js +62 -0
- package/dest/tx_validator/metadata_validator.d.ts +8 -0
- package/dest/tx_validator/metadata_validator.d.ts.map +1 -0
- package/dest/tx_validator/metadata_validator.js +50 -0
- package/dest/tx_validator/phases_validator.d.ts +13 -0
- package/dest/tx_validator/phases_validator.d.ts.map +1 -0
- package/dest/tx_validator/phases_validator.js +73 -0
- package/dest/tx_validator/tx_validator_factory.d.ts +13 -0
- package/dest/tx_validator/tx_validator_factory.d.ts.map +1 -0
- package/dest/tx_validator/tx_validator_factory.js +21 -0
- package/package.json +14 -13
- package/src/client/sequencer-client.ts +2 -3
- package/src/config.ts +93 -7
- package/src/global_variable_builder/global_builder.ts +4 -3
- package/src/index.ts +0 -1
- package/src/publisher/l1-publisher.ts +9 -9
- package/src/publisher/viem-tx-sender.ts +1 -1
- package/src/sequencer/sequencer.ts +39 -33
- package/src/tx_validator/aggregate_tx_validator.ts +24 -0
- package/src/tx_validator/double_spend_validator.ts +65 -0
- package/src/tx_validator/gas_validator.ts +81 -0
- package/src/tx_validator/metadata_validator.ts +60 -0
- package/src/tx_validator/phases_validator.ts +101 -0
- package/src/tx_validator/tx_validator_factory.ts +37 -0
- package/dest/sequencer/abstract_phase_manager.d.ts +0 -77
- package/dest/sequencer/abstract_phase_manager.d.ts.map +0 -1
- package/dest/sequencer/abstract_phase_manager.js +0 -307
- package/dest/sequencer/app_logic_phase_manager.d.ts +0 -28
- package/dest/sequencer/app_logic_phase_manager.d.ts.map +0 -1
- package/dest/sequencer/app_logic_phase_manager.js +0 -41
- package/dest/sequencer/hints_builder.d.ts +0 -23
- package/dest/sequencer/hints_builder.d.ts.map +0 -1
- package/dest/sequencer/hints_builder.js +0 -62
- package/dest/sequencer/phase_manager_factory.d.ts +0 -18
- package/dest/sequencer/phase_manager_factory.d.ts.map +0 -1
- package/dest/sequencer/phase_manager_factory.js +0 -56
- package/dest/sequencer/public_processor.d.ts +0 -54
- package/dest/sequencer/public_processor.d.ts.map +0 -1
- package/dest/sequencer/public_processor.js +0 -142
- package/dest/sequencer/setup_phase_manager.d.ts +0 -28
- package/dest/sequencer/setup_phase_manager.d.ts.map +0 -1
- package/dest/sequencer/setup_phase_manager.js +0 -30
- package/dest/sequencer/tail_phase_manager.d.ts +0 -29
- package/dest/sequencer/tail_phase_manager.d.ts.map +0 -1
- package/dest/sequencer/tail_phase_manager.js +0 -52
- package/dest/sequencer/teardown_phase_manager.d.ts +0 -28
- package/dest/sequencer/teardown_phase_manager.d.ts.map +0 -1
- package/dest/sequencer/teardown_phase_manager.js +0 -30
- package/dest/sequencer/tx_validator.d.ts +0 -28
- package/dest/sequencer/tx_validator.d.ts.map +0 -1
- package/dest/sequencer/tx_validator.js +0 -174
- package/dest/sequencer/tx_validator_factory.d.ts +0 -12
- package/dest/sequencer/tx_validator_factory.d.ts.map +0 -1
- package/dest/sequencer/tx_validator_factory.js +0 -17
- package/dest/sequencer/utils.d.ts +0 -8
- package/dest/sequencer/utils.d.ts.map +0 -1
- package/dest/sequencer/utils.js +0 -29
- package/dest/simulator/index.d.ts +0 -31
- package/dest/simulator/index.d.ts.map +0 -1
- package/dest/simulator/index.js +0 -2
- package/dest/simulator/public_executor.d.ts +0 -79
- package/dest/simulator/public_executor.d.ts.map +0 -1
- package/dest/simulator/public_executor.js +0 -198
- package/dest/simulator/public_kernel.d.ts +0 -37
- package/dest/simulator/public_kernel.d.ts.map +0 -1
- package/dest/simulator/public_kernel.js +0 -97
- package/src/sequencer/abstract_phase_manager.ts +0 -549
- package/src/sequencer/app_logic_phase_manager.ts +0 -62
- package/src/sequencer/hints_builder.ts +0 -119
- package/src/sequencer/phase_manager_factory.ts +0 -126
- package/src/sequencer/public_processor.ts +0 -209
- package/src/sequencer/setup_phase_manager.ts +0 -50
- package/src/sequencer/tail_phase_manager.ts +0 -111
- package/src/sequencer/teardown_phase_manager.ts +0 -50
- package/src/sequencer/tx_validator.ts +0 -265
- package/src/sequencer/tx_validator_factory.ts +0 -32
- package/src/sequencer/utils.ts +0 -31
- package/src/simulator/index.ts +0 -36
- package/src/simulator/public_executor.ts +0 -267
- package/src/simulator/public_kernel.ts +0 -139
|
@@ -122,7 +122,7 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
122
122
|
// TODO(#4148) Remove this block number check, it's here because we don't currently have proper genesis state on the contract
|
|
123
123
|
const lastArchive = block.header.lastArchive.root.toBuffer();
|
|
124
124
|
if (block.number != 1 && !(await this.checkLastArchiveHash(lastArchive))) {
|
|
125
|
-
this.log(`Detected different last archive prior to publishing a block, aborting publish...`);
|
|
125
|
+
this.log.info(`Detected different last archive prior to publishing a block, aborting publish...`);
|
|
126
126
|
return false;
|
|
127
127
|
}
|
|
128
128
|
|
|
@@ -131,7 +131,7 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
131
131
|
// Publish block transaction effects
|
|
132
132
|
while (!this.interrupted) {
|
|
133
133
|
if (await this.txSender.checkIfTxsAreAvailable(block)) {
|
|
134
|
-
this.log(`Transaction effects of a block ${block.number} already published.`);
|
|
134
|
+
this.log.verbose(`Transaction effects of a block ${block.number} already published.`);
|
|
135
135
|
break;
|
|
136
136
|
}
|
|
137
137
|
|
|
@@ -151,14 +151,14 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
151
151
|
// txsEffectsHash from IAvailabilityOracle.TxsPublished event
|
|
152
152
|
txsEffectsHash = receipt.logs[0].data;
|
|
153
153
|
} else {
|
|
154
|
-
this.log(`Expected 1 log, got ${receipt.logs.length}`);
|
|
154
|
+
this.log.warn(`Expected 1 log, got ${receipt.logs.length}`);
|
|
155
155
|
}
|
|
156
156
|
|
|
157
157
|
this.log.info(`Block txs effects published, txsEffectsHash: ${txsEffectsHash}`);
|
|
158
158
|
break;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
this.log(`AvailabilityOracle.publish tx status failed: ${receipt.transactionHash}`);
|
|
161
|
+
this.log.error(`AvailabilityOracle.publish tx status failed: ${receipt.transactionHash}`);
|
|
162
162
|
await this.sleepOrInterrupted();
|
|
163
163
|
}
|
|
164
164
|
|
|
@@ -196,15 +196,15 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
196
196
|
|
|
197
197
|
// Check if someone else incremented the block number
|
|
198
198
|
if (!(await this.checkLastArchiveHash(lastArchive))) {
|
|
199
|
-
this.log('Publish failed. Detected different last archive hash.');
|
|
199
|
+
this.log.warn('Publish failed. Detected different last archive hash.');
|
|
200
200
|
break;
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
this.log(`Rollup.process tx status failed: ${receipt.transactionHash}`);
|
|
203
|
+
this.log.error(`Rollup.process tx status failed: ${receipt.transactionHash}`);
|
|
204
204
|
await this.sleepOrInterrupted();
|
|
205
205
|
}
|
|
206
206
|
|
|
207
|
-
this.log('L2 block data syncing interrupted while processing blocks.');
|
|
207
|
+
this.log.verbose('L2 block data syncing interrupted while processing blocks.');
|
|
208
208
|
return false;
|
|
209
209
|
}
|
|
210
210
|
|
|
@@ -233,8 +233,8 @@ export class L1Publisher implements L2BlockReceiver {
|
|
|
233
233
|
const fromChain = await this.txSender.getCurrentArchive();
|
|
234
234
|
const areSame = lastArchive.equals(fromChain);
|
|
235
235
|
if (!areSame) {
|
|
236
|
-
this.log(`
|
|
237
|
-
this.log(`
|
|
236
|
+
this.log.debug(`Contract archive: ${fromChain.toString('hex')}`);
|
|
237
|
+
this.log.debug(`New block last archive: ${lastArchive.toString('hex')}`);
|
|
238
238
|
}
|
|
239
239
|
return areSame;
|
|
240
240
|
}
|
|
@@ -1,5 +1,12 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
type L1ToL2MessageSource,
|
|
3
|
+
type L2Block,
|
|
4
|
+
type L2BlockSource,
|
|
5
|
+
type ProcessedTx,
|
|
6
|
+
Tx,
|
|
7
|
+
type TxValidator,
|
|
8
|
+
} from '@aztec/circuit-types';
|
|
9
|
+
import { type AllowedFunction, type BlockProver, PROVING_STATUS } from '@aztec/circuit-types/interfaces';
|
|
3
10
|
import { type L2BlockBuiltStats } from '@aztec/circuit-types/stats';
|
|
4
11
|
import { AztecAddress, EthAddress } from '@aztec/circuits.js';
|
|
5
12
|
import { Fr } from '@aztec/foundation/fields';
|
|
@@ -7,14 +14,13 @@ import { createDebugLogger } from '@aztec/foundation/log';
|
|
|
7
14
|
import { RunningPromise } from '@aztec/foundation/running-promise';
|
|
8
15
|
import { Timer, elapsed } from '@aztec/foundation/timer';
|
|
9
16
|
import { type P2P } from '@aztec/p2p';
|
|
17
|
+
import { type PublicProcessorFactory } from '@aztec/simulator';
|
|
10
18
|
import { type WorldStateStatus, type WorldStateSynchronizer } from '@aztec/world-state';
|
|
11
19
|
|
|
12
20
|
import { type GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
|
|
13
21
|
import { type L1Publisher } from '../publisher/l1-publisher.js';
|
|
22
|
+
import { type TxValidatorFactory } from '../tx_validator/tx_validator_factory.js';
|
|
14
23
|
import { type SequencerConfig } from './config.js';
|
|
15
|
-
import { type PublicProcessorFactory } from './public_processor.js';
|
|
16
|
-
import { type TxValidator } from './tx_validator.js';
|
|
17
|
-
import { type TxValidatorFactory } from './tx_validator_factory.js';
|
|
18
24
|
|
|
19
25
|
/**
|
|
20
26
|
* Sequencer client
|
|
@@ -35,8 +41,8 @@ export class Sequencer {
|
|
|
35
41
|
private _feeRecipient = AztecAddress.ZERO;
|
|
36
42
|
private lastPublishedBlock = 0;
|
|
37
43
|
private state = SequencerState.STOPPED;
|
|
38
|
-
private
|
|
39
|
-
private
|
|
44
|
+
private allowedFunctionsInSetup: AllowedFunction[] = [];
|
|
45
|
+
private allowedFunctionsInTeardown: AllowedFunction[] = [];
|
|
40
46
|
|
|
41
47
|
constructor(
|
|
42
48
|
private publisher: L1Publisher,
|
|
@@ -52,7 +58,7 @@ export class Sequencer {
|
|
|
52
58
|
private log = createDebugLogger('aztec:sequencer'),
|
|
53
59
|
) {
|
|
54
60
|
this.updateConfig(config);
|
|
55
|
-
this.log(`Initialized sequencer with ${this.minTxsPerBLock}-${this.maxTxsPerBlock} txs per block.`);
|
|
61
|
+
this.log.verbose(`Initialized sequencer with ${this.minTxsPerBLock}-${this.maxTxsPerBlock} txs per block.`);
|
|
56
62
|
}
|
|
57
63
|
|
|
58
64
|
/**
|
|
@@ -75,11 +81,11 @@ export class Sequencer {
|
|
|
75
81
|
if (config.feeRecipient) {
|
|
76
82
|
this._feeRecipient = config.feeRecipient;
|
|
77
83
|
}
|
|
78
|
-
if (config.
|
|
79
|
-
this.
|
|
84
|
+
if (config.allowedFunctionsInSetup) {
|
|
85
|
+
this.allowedFunctionsInSetup = config.allowedFunctionsInSetup;
|
|
80
86
|
}
|
|
81
|
-
if (config.
|
|
82
|
-
this.
|
|
87
|
+
if (config.allowedFunctionsInTeardown) {
|
|
88
|
+
this.allowedFunctionsInTeardown = config.allowedFunctionsInTeardown;
|
|
83
89
|
}
|
|
84
90
|
}
|
|
85
91
|
|
|
@@ -92,25 +98,25 @@ export class Sequencer {
|
|
|
92
98
|
this.runningPromise = new RunningPromise(this.work.bind(this), this.pollingIntervalMs);
|
|
93
99
|
this.runningPromise.start();
|
|
94
100
|
this.state = SequencerState.IDLE;
|
|
95
|
-
this.log('Sequencer started');
|
|
101
|
+
this.log.info('Sequencer started');
|
|
96
102
|
}
|
|
97
103
|
|
|
98
104
|
/**
|
|
99
105
|
* Stops the sequencer from processing txs and moves to STOPPED state.
|
|
100
106
|
*/
|
|
101
107
|
public async stop(): Promise<void> {
|
|
102
|
-
this.log(`Stopping sequencer`);
|
|
108
|
+
this.log.debug(`Stopping sequencer`);
|
|
103
109
|
await this.runningPromise?.stop();
|
|
104
110
|
this.publisher.interrupt();
|
|
105
111
|
this.state = SequencerState.STOPPED;
|
|
106
|
-
this.log('Stopped sequencer');
|
|
112
|
+
this.log.info('Stopped sequencer');
|
|
107
113
|
}
|
|
108
114
|
|
|
109
115
|
/**
|
|
110
116
|
* Starts a previously stopped sequencer.
|
|
111
117
|
*/
|
|
112
118
|
public restart() {
|
|
113
|
-
this.log('Restarting sequencer');
|
|
119
|
+
this.log.info('Restarting sequencer');
|
|
114
120
|
this.publisher.restart();
|
|
115
121
|
this.runningPromise!.start();
|
|
116
122
|
this.state = SequencerState.IDLE;
|
|
@@ -137,7 +143,7 @@ export class Sequencer {
|
|
|
137
143
|
// Update state when the previous block has been synced
|
|
138
144
|
const prevBlockSynced = await this.isBlockSynced();
|
|
139
145
|
if (prevBlockSynced && this.state === SequencerState.PUBLISHING_BLOCK) {
|
|
140
|
-
this.log(`Block has been synced`);
|
|
146
|
+
this.log.debug(`Block has been synced`);
|
|
141
147
|
this.state = SequencerState.IDLE;
|
|
142
148
|
}
|
|
143
149
|
|
|
@@ -178,14 +184,15 @@ export class Sequencer {
|
|
|
178
184
|
this._feeRecipient,
|
|
179
185
|
);
|
|
180
186
|
|
|
181
|
-
const txValidator = this.txValidatorFactory.buildTxValidator(
|
|
182
|
-
newGlobalVariables,
|
|
183
|
-
this.allowedFeePaymentContractClasses,
|
|
184
|
-
this.allowedFeePaymentContractInstances,
|
|
185
|
-
);
|
|
186
|
-
|
|
187
187
|
// TODO: It should be responsibility of the P2P layer to validate txs before passing them on here
|
|
188
|
-
const validTxs = await this.takeValidTxs(
|
|
188
|
+
const validTxs = await this.takeValidTxs(
|
|
189
|
+
pendingTxs,
|
|
190
|
+
this.txValidatorFactory.validatorForNewTxs(
|
|
191
|
+
newGlobalVariables,
|
|
192
|
+
this.allowedFunctionsInSetup,
|
|
193
|
+
this.allowedFunctionsInTeardown,
|
|
194
|
+
),
|
|
195
|
+
);
|
|
189
196
|
if (validTxs.length < this.minTxsPerBLock) {
|
|
190
197
|
return;
|
|
191
198
|
}
|
|
@@ -194,9 +201,9 @@ export class Sequencer {
|
|
|
194
201
|
this.state = SequencerState.CREATING_BLOCK;
|
|
195
202
|
|
|
196
203
|
// Get l1 to l2 messages from the contract
|
|
197
|
-
this.log('Requesting L1 to L2 messages from contract');
|
|
204
|
+
this.log.debug('Requesting L1 to L2 messages from contract');
|
|
198
205
|
const l1ToL2Messages = await this.l1ToL2MessageSource.getL1ToL2Messages(BigInt(newBlockNumber));
|
|
199
|
-
this.log(`Retrieved ${l1ToL2Messages.length} L1 to L2 messages for block ${newBlockNumber}`);
|
|
206
|
+
this.log.verbose(`Retrieved ${l1ToL2Messages.length} L1 to L2 messages for block ${newBlockNumber}`);
|
|
200
207
|
|
|
201
208
|
// We create a fresh processor each time to reset any cached state (eg storage writes)
|
|
202
209
|
const processor = await this.publicProcessorFactory.create(historicalHeader, newGlobalVariables);
|
|
@@ -213,16 +220,16 @@ export class Sequencer {
|
|
|
213
220
|
const blockTicket = await this.prover.startNewBlock(blockSize, newGlobalVariables, l1ToL2Messages, emptyTx);
|
|
214
221
|
|
|
215
222
|
const [publicProcessorDuration, [processedTxs, failedTxs]] = await elapsed(() =>
|
|
216
|
-
processor.process(validTxs, blockSize, this.prover,
|
|
223
|
+
processor.process(validTxs, blockSize, this.prover, this.txValidatorFactory.validatorForProcessedTxs()),
|
|
217
224
|
);
|
|
218
225
|
if (failedTxs.length > 0) {
|
|
219
226
|
const failedTxData = failedTxs.map(fail => fail.tx);
|
|
220
|
-
this.log(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
|
|
227
|
+
this.log.debug(`Dropping failed txs ${Tx.getHashes(failedTxData).join(', ')}`);
|
|
221
228
|
await this.p2pClient.deleteTxs(Tx.getHashes(failedTxData));
|
|
222
229
|
}
|
|
223
230
|
|
|
224
231
|
if (processedTxs.length === 0) {
|
|
225
|
-
this.log('No txs processed correctly to build block. Exiting');
|
|
232
|
+
this.log.verbose('No txs processed correctly to build block. Exiting');
|
|
226
233
|
this.prover.cancelBlock();
|
|
227
234
|
return;
|
|
228
235
|
}
|
|
@@ -248,7 +255,7 @@ export class Sequencer {
|
|
|
248
255
|
|
|
249
256
|
await assertBlockHeight();
|
|
250
257
|
|
|
251
|
-
this.log(`Assembled block ${block.number}`, {
|
|
258
|
+
this.log.verbose(`Assembled block ${block.number}`, {
|
|
252
259
|
eventName: 'l2-block-built',
|
|
253
260
|
duration: workTimer.ms(),
|
|
254
261
|
publicProcessDuration: publicProcessorDuration,
|
|
@@ -275,17 +282,16 @@ export class Sequencer {
|
|
|
275
282
|
this.state = SequencerState.PUBLISHING_BLOCK;
|
|
276
283
|
const publishedL2Block = await this.publisher.processL2Block(block);
|
|
277
284
|
if (publishedL2Block) {
|
|
278
|
-
this.log(`Successfully published block ${block.number}`);
|
|
279
285
|
this.lastPublishedBlock = block.number;
|
|
280
286
|
} else {
|
|
281
287
|
throw new Error(`Failed to publish block`);
|
|
282
288
|
}
|
|
283
289
|
}
|
|
284
290
|
|
|
285
|
-
protected async takeValidTxs<T extends Tx | ProcessedTx>(txs: T[], validator: TxValidator): Promise<T[]> {
|
|
291
|
+
protected async takeValidTxs<T extends Tx | ProcessedTx>(txs: T[], validator: TxValidator<T>): Promise<T[]> {
|
|
286
292
|
const [valid, invalid] = await validator.validateTxs(txs);
|
|
287
293
|
if (invalid.length > 0) {
|
|
288
|
-
this.log(`Dropping invalid txs from the p2p pool ${Tx.getHashes(invalid).join(', ')}`);
|
|
294
|
+
this.log.debug(`Dropping invalid txs from the p2p pool ${Tx.getHashes(invalid).join(', ')}`);
|
|
289
295
|
await this.p2pClient.deleteTxs(Tx.getHashes(invalid));
|
|
290
296
|
}
|
|
291
297
|
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type ProcessedTx, type Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
|
|
3
|
+
export class AggregateTxValidator<T extends Tx | ProcessedTx> implements TxValidator<T> {
|
|
4
|
+
#validators: TxValidator<T>[];
|
|
5
|
+
constructor(...validators: TxValidator<T>[]) {
|
|
6
|
+
if (validators.length === 0) {
|
|
7
|
+
throw new Error('At least one validator must be provided');
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
this.#validators = validators;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
|
|
14
|
+
const invalidTxs: T[] = [];
|
|
15
|
+
let txPool = txs;
|
|
16
|
+
for (const validator of this.#validators) {
|
|
17
|
+
const [valid, invalid] = await validator.validateTxs(txPool);
|
|
18
|
+
invalidTxs.push(...invalid);
|
|
19
|
+
txPool = valid;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return [txPool, invalidTxs];
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type AnyTx, Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
import { Fr } from '@aztec/circuits.js';
|
|
3
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
+
|
|
5
|
+
export interface NullifierSource {
|
|
6
|
+
getNullifierIndex: (nullifier: Fr) => Promise<bigint | undefined>;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export class DoubleSpendTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
10
|
+
#log = createDebugLogger('aztec:sequencer:tx_validator:tx_double_spend');
|
|
11
|
+
#nullifierSource: NullifierSource;
|
|
12
|
+
|
|
13
|
+
constructor(nullifierSource: NullifierSource) {
|
|
14
|
+
this.#nullifierSource = nullifierSource;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
|
|
18
|
+
const validTxs: T[] = [];
|
|
19
|
+
const invalidTxs: T[] = [];
|
|
20
|
+
const thisBlockNullifiers = new Set<bigint>();
|
|
21
|
+
|
|
22
|
+
for (const tx of txs) {
|
|
23
|
+
if (!(await this.#uniqueNullifiers(tx, thisBlockNullifiers))) {
|
|
24
|
+
invalidTxs.push(tx);
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
validTxs.push(tx);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return [validTxs, invalidTxs];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async #uniqueNullifiers(tx: AnyTx, thisBlockNullifiers: Set<bigint>): Promise<boolean> {
|
|
35
|
+
const newNullifiers = tx.data.getNonEmptyNullifiers().map(x => x.toBigInt());
|
|
36
|
+
|
|
37
|
+
// Ditch this tx if it has repeated nullifiers
|
|
38
|
+
const uniqueNullifiers = new Set(newNullifiers);
|
|
39
|
+
if (uniqueNullifiers.size !== newNullifiers.length) {
|
|
40
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for emitting duplicate nullifiers`);
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
for (const nullifier of newNullifiers) {
|
|
45
|
+
if (thisBlockNullifiers.has(nullifier)) {
|
|
46
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating a nullifier in the same block`);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
thisBlockNullifiers.add(nullifier);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const nullifierIndexes = await Promise.all(
|
|
54
|
+
newNullifiers.map(n => this.#nullifierSource.getNullifierIndex(new Fr(n))),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
const hasDuplicates = nullifierIndexes.some(index => index !== undefined);
|
|
58
|
+
if (hasDuplicates) {
|
|
59
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for repeating nullifiers present in state trees`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return true;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
import { type AztecAddress, Fr } from '@aztec/circuits.js';
|
|
3
|
+
import { pedersenHash } from '@aztec/foundation/crypto';
|
|
4
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
5
|
+
import { GasTokenContract } from '@aztec/noir-contracts.js';
|
|
6
|
+
import { AbstractPhaseManager, PublicKernelPhase } from '@aztec/simulator';
|
|
7
|
+
|
|
8
|
+
/** Provides a view into public contract state */
|
|
9
|
+
export interface PublicStateSource {
|
|
10
|
+
storageRead: (contractAddress: AztecAddress, slot: Fr) => Promise<Fr>;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class GasTxValidator implements TxValidator<Tx> {
|
|
14
|
+
#log = createDebugLogger('aztec:sequencer:tx_validator:tx_gas');
|
|
15
|
+
#publicDataSource: PublicStateSource;
|
|
16
|
+
#gasTokenAddress: AztecAddress;
|
|
17
|
+
#requireFees: boolean;
|
|
18
|
+
|
|
19
|
+
constructor(publicDataSource: PublicStateSource, gasTokenAddress: AztecAddress, requireFees = false) {
|
|
20
|
+
this.#publicDataSource = publicDataSource;
|
|
21
|
+
this.#gasTokenAddress = gasTokenAddress;
|
|
22
|
+
this.#requireFees = requireFees;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
|
|
26
|
+
const validTxs: Tx[] = [];
|
|
27
|
+
const invalidTxs: Tx[] = [];
|
|
28
|
+
|
|
29
|
+
for (const tx of txs) {
|
|
30
|
+
if (await this.#validateTxFee(tx)) {
|
|
31
|
+
validTxs.push(tx);
|
|
32
|
+
} else {
|
|
33
|
+
invalidTxs.push(tx);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return [validTxs, invalidTxs];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async #validateTxFee(tx: Tx): Promise<boolean> {
|
|
41
|
+
const { [PublicKernelPhase.TEARDOWN]: teardownFns } = AbstractPhaseManager.extractEnqueuedPublicCallsByPhase(
|
|
42
|
+
tx.data,
|
|
43
|
+
tx.enqueuedPublicFunctionCalls,
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
if (teardownFns.length === 0) {
|
|
47
|
+
if (this.#requireFees) {
|
|
48
|
+
this.#log.warn(
|
|
49
|
+
`Rejecting tx ${Tx.getHash(tx)} because it should pay for gas but has no enqueued teardown functions`,
|
|
50
|
+
);
|
|
51
|
+
return false;
|
|
52
|
+
} else {
|
|
53
|
+
this.#log.debug(`Tx ${Tx.getHash(tx)} does not pay fees. Skipping balance check.`);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (teardownFns.length > 1) {
|
|
59
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} because it has multiple teardown functions`);
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// check that the caller of the teardown function has enough balance to pay for tx costs
|
|
64
|
+
const teardownFn = teardownFns[0];
|
|
65
|
+
const slot = pedersenHash([GasTokenContract.storage.balances.slot, teardownFn.callContext.msgSender]);
|
|
66
|
+
const gasBalance = await this.#publicDataSource.storageRead(this.#gasTokenAddress, slot);
|
|
67
|
+
|
|
68
|
+
// TODO(#5004) calculate fee needed based on tx limits and gas prices
|
|
69
|
+
const gasAmountNeeded = new Fr(1);
|
|
70
|
+
if (gasBalance.lt(gasAmountNeeded)) {
|
|
71
|
+
this.#log.warn(
|
|
72
|
+
`Rejecting tx ${Tx.getHash(
|
|
73
|
+
tx,
|
|
74
|
+
)} because it should pay for gas but has insufficient balance ${gasBalance.toShortString()} < ${gasAmountNeeded.toShortString()}`,
|
|
75
|
+
);
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return true;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { type AnyTx, Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
import { type GlobalVariables } from '@aztec/circuits.js';
|
|
3
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
+
|
|
5
|
+
export class MetadataTxValidator<T extends AnyTx> implements TxValidator<T> {
|
|
6
|
+
#log = createDebugLogger('aztec:sequencer:tx_validator:tx_metadata');
|
|
7
|
+
#globalVariables: GlobalVariables;
|
|
8
|
+
|
|
9
|
+
constructor(globalVariables: GlobalVariables) {
|
|
10
|
+
this.#globalVariables = globalVariables;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
validateTxs(txs: T[]): Promise<[validTxs: T[], invalidTxs: T[]]> {
|
|
14
|
+
const validTxs: T[] = [];
|
|
15
|
+
const invalidTxs: T[] = [];
|
|
16
|
+
for (const tx of txs) {
|
|
17
|
+
if (!this.#hasCorrectChainId(tx)) {
|
|
18
|
+
invalidTxs.push(tx);
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!this.#isValidForBlockNumber(tx)) {
|
|
23
|
+
invalidTxs.push(tx);
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
validTxs.push(tx);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return Promise.resolve([validTxs, invalidTxs]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
#hasCorrectChainId(tx: T): boolean {
|
|
34
|
+
if (!tx.data.constants.txContext.chainId.equals(this.#globalVariables.chainId)) {
|
|
35
|
+
this.#log.warn(
|
|
36
|
+
`Rejecting tx ${Tx.getHash(
|
|
37
|
+
tx,
|
|
38
|
+
)} because of incorrect chain ${tx.data.constants.txContext.chainId.toNumber()} != ${this.#globalVariables.chainId.toNumber()}`,
|
|
39
|
+
);
|
|
40
|
+
return false;
|
|
41
|
+
} else {
|
|
42
|
+
return true;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
#isValidForBlockNumber(tx: T): boolean {
|
|
47
|
+
const target =
|
|
48
|
+
tx instanceof Tx
|
|
49
|
+
? tx.data.forRollup?.rollupValidationRequests || tx.data.forPublic!.validationRequests.forRollup
|
|
50
|
+
: tx.data.rollupValidationRequests;
|
|
51
|
+
const maxBlockNumber = target.maxBlockNumber;
|
|
52
|
+
|
|
53
|
+
if (maxBlockNumber.isSome && maxBlockNumber.value < this.#globalVariables.blockNumber) {
|
|
54
|
+
this.#log.warn(`Rejecting tx ${Tx.getHash(tx)} for low max block number`);
|
|
55
|
+
return false;
|
|
56
|
+
} else {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { type AllowedFunction, Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
import { type PublicCallRequest } from '@aztec/circuits.js';
|
|
3
|
+
import { createDebugLogger } from '@aztec/foundation/log';
|
|
4
|
+
import { AbstractPhaseManager, PublicKernelPhase } from '@aztec/simulator';
|
|
5
|
+
import { type ContractDataSource } from '@aztec/types/contracts';
|
|
6
|
+
|
|
7
|
+
export class PhasesTxValidator implements TxValidator<Tx> {
|
|
8
|
+
#log = createDebugLogger('aztec:sequencer:tx_validator:tx_phases');
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
private contractDataSource: ContractDataSource,
|
|
12
|
+
private setupAllowList: AllowedFunction[],
|
|
13
|
+
private teardownAllowList: AllowedFunction[],
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
async validateTxs(txs: Tx[]): Promise<[validTxs: Tx[], invalidTxs: Tx[]]> {
|
|
17
|
+
const validTxs: Tx[] = [];
|
|
18
|
+
const invalidTxs: Tx[] = [];
|
|
19
|
+
|
|
20
|
+
for (const tx of txs) {
|
|
21
|
+
if (await this.#validateTx(tx)) {
|
|
22
|
+
validTxs.push(tx);
|
|
23
|
+
} else {
|
|
24
|
+
invalidTxs.push(tx);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return Promise.resolve([validTxs, invalidTxs]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async #validateTx(tx: Tx): Promise<boolean> {
|
|
32
|
+
if (!tx.data.forPublic) {
|
|
33
|
+
this.#log.debug(`Tx ${Tx.getHash(tx)} does not contain enqueued public functions. Skipping phases validation.`);
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const { [PublicKernelPhase.SETUP]: setupFns, [PublicKernelPhase.TEARDOWN]: teardownFns } =
|
|
38
|
+
AbstractPhaseManager.extractEnqueuedPublicCallsByPhase(tx.data, tx.enqueuedPublicFunctionCalls);
|
|
39
|
+
|
|
40
|
+
for (const setupFn of setupFns) {
|
|
41
|
+
if (!(await this.isOnAllowList(setupFn, this.setupAllowList))) {
|
|
42
|
+
this.#log.warn(
|
|
43
|
+
`Rejecting tx ${Tx.getHash(tx)} because it calls setup function not on allow list: ${
|
|
44
|
+
setupFn.contractAddress
|
|
45
|
+
}:${setupFn.functionData.selector}`,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
for (const teardownFn of teardownFns) {
|
|
53
|
+
if (!(await this.isOnAllowList(teardownFn, this.teardownAllowList))) {
|
|
54
|
+
this.#log.warn(
|
|
55
|
+
`Rejecting tx ${Tx.getHash(tx)} because it calls teardown function not on allowlist: ${
|
|
56
|
+
teardownFn.contractAddress
|
|
57
|
+
}:${teardownFn.functionData.selector}`,
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return true;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async isOnAllowList(publicCall: PublicCallRequest, allowList: AllowedFunction[]): Promise<boolean> {
|
|
68
|
+
const {
|
|
69
|
+
contractAddress,
|
|
70
|
+
functionData: { selector },
|
|
71
|
+
} = publicCall;
|
|
72
|
+
|
|
73
|
+
// do these checks first since they don't require the contract class
|
|
74
|
+
for (const entry of allowList) {
|
|
75
|
+
if (!('address' in entry)) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (contractAddress.equals(entry.address) && entry.selector.equals(selector)) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const contractClass = await this.contractDataSource.getContract(contractAddress);
|
|
85
|
+
if (!contractClass) {
|
|
86
|
+
throw new Error(`Contract not found: ${publicCall.contractAddress.toString()}`);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
for (const entry of allowList) {
|
|
90
|
+
if (!('classId' in entry)) {
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (contractClass.contractClassId.equals(entry.classId) && entry.selector.equals(selector)) {
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return false;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type AllowedFunction, type ProcessedTx, type Tx, type TxValidator } from '@aztec/circuit-types';
|
|
2
|
+
import { type EthAddress, type GlobalVariables } from '@aztec/circuits.js';
|
|
3
|
+
import { getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token';
|
|
4
|
+
import { WorldStateDB, WorldStatePublicDB } from '@aztec/simulator';
|
|
5
|
+
import { type ContractDataSource } from '@aztec/types/contracts';
|
|
6
|
+
import { type MerkleTreeOperations } from '@aztec/world-state';
|
|
7
|
+
|
|
8
|
+
import { AggregateTxValidator } from './aggregate_tx_validator.js';
|
|
9
|
+
import { DoubleSpendTxValidator } from './double_spend_validator.js';
|
|
10
|
+
import { GasTxValidator } from './gas_validator.js';
|
|
11
|
+
import { MetadataTxValidator } from './metadata_validator.js';
|
|
12
|
+
import { PhasesTxValidator } from './phases_validator.js';
|
|
13
|
+
|
|
14
|
+
export class TxValidatorFactory {
|
|
15
|
+
constructor(
|
|
16
|
+
private merkleTreeDb: MerkleTreeOperations,
|
|
17
|
+
private contractDataSource: ContractDataSource,
|
|
18
|
+
private gasPortalAddress: EthAddress,
|
|
19
|
+
) {}
|
|
20
|
+
|
|
21
|
+
validatorForNewTxs(
|
|
22
|
+
globalVariables: GlobalVariables,
|
|
23
|
+
setupAllowList: AllowedFunction[],
|
|
24
|
+
teardownAllowList: AllowedFunction[],
|
|
25
|
+
): TxValidator<Tx> {
|
|
26
|
+
return new AggregateTxValidator(
|
|
27
|
+
new MetadataTxValidator(globalVariables),
|
|
28
|
+
new DoubleSpendTxValidator(new WorldStateDB(this.merkleTreeDb)),
|
|
29
|
+
new PhasesTxValidator(this.contractDataSource, setupAllowList, teardownAllowList),
|
|
30
|
+
new GasTxValidator(new WorldStatePublicDB(this.merkleTreeDb), getCanonicalGasTokenAddress(this.gasPortalAddress)),
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
validatorForProcessedTxs(): TxValidator<ProcessedTx> {
|
|
35
|
+
return new DoubleSpendTxValidator(new WorldStateDB(this.merkleTreeDb));
|
|
36
|
+
}
|
|
37
|
+
}
|