From b62d1ca73ee8fee52358a069af5fd8732d9e6800 Mon Sep 17 00:00:00 2001 From: Alex Gherghisan Date: Tue, 19 Mar 2024 14:10:20 +0000 Subject: [PATCH] feat: sequencer checks list of allowed FPCs --- .../aztec-node/src/aztec-node/server.ts | 1 - .../circuit-types/src/interfaces/configs.ts | 8 +- .../src/e2e_dapp_subscription.test.ts | 5 + yarn-project/end-to-end/src/e2e_fees.test.ts | 7 +- .../src/client/sequencer-client.ts | 10 +- yarn-project/sequencer-client/src/config.ts | 8 +- .../src/sequencer/public_processor.ts | 5 +- .../src/sequencer/sequencer.test.ts | 12 ++ .../src/sequencer/sequencer.ts | 27 ++--- .../src/sequencer/tx_validator.test.ts | 67 +++++++++++- .../src/sequencer/tx_validator.ts | 103 +++++++++++++----- .../src/sequencer/tx_validator_factory.ts | 32 ++++++ .../src/simulator/public_executor.ts | 10 +- 13 files changed, 233 insertions(+), 62 deletions(-) create mode 100644 yarn-project/sequencer-client/src/sequencer/tx_validator_factory.ts diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index e2a666d6f54d..80cb70122a88 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -615,7 +615,6 @@ export class AztecNodeService implements AztecNode { const publicProcessorFactory = new PublicProcessorFactory( merkleTrees.asLatest(), this.contractDataSource, - this.l1ToL2MessageSource, new WASMSimulator(), ); const processor = await publicProcessorFactory.create(prevHeader, newGlobalVariables); diff --git a/yarn-project/circuit-types/src/interfaces/configs.ts b/yarn-project/circuit-types/src/interfaces/configs.ts index 19dd07ac4937..80198bfca29c 100644 --- a/yarn-project/circuit-types/src/interfaces/configs.ts +++ b/yarn-project/circuit-types/src/interfaces/configs.ts @@ -1,4 +1,4 @@ -import { AztecAddress, EthAddress } from '@aztec/circuits.js'; +import { AztecAddress, EthAddress, Fr } from '@aztec/circuits.js'; /** * The sequencer configuration. @@ -18,4 +18,10 @@ export interface SequencerConfig { acvmWorkingDirectory?: string; /** The path to the ACVM binary */ acvmBinaryPath?: string; + + /** The list of permitted fee payment contract classes */ + allowedFeePaymentContractClasses?: Fr[]; + + /** The list of permitted fee payment contract instances. Takes precedence over contract classes */ + allowedFeePaymentContractInstances?: AztecAddress[]; } diff --git a/yarn-project/end-to-end/src/e2e_dapp_subscription.test.ts b/yarn-project/end-to-end/src/e2e_dapp_subscription.test.ts index 37ed31610a75..04878449bdb2 100644 --- a/yarn-project/end-to-end/src/e2e_dapp_subscription.test.ts +++ b/yarn-project/end-to-end/src/e2e_dapp_subscription.test.ts @@ -6,6 +6,7 @@ import { PrivateFeePaymentMethod, PublicFeePaymentMethod, SentTx, + getContractClassFromArtifact, } from '@aztec/aztec.js'; import { DefaultDappEntrypoint } from '@aztec/entrypoints/dapp'; import { @@ -67,6 +68,10 @@ describe('e2e_dapp_subscription', () => { const { wallets, accounts, aztecNode, deployL1ContractsValues } = e2eContext; + await aztecNode.setConfig({ + allowedFeePaymentContractClasses: [getContractClassFromArtifact(FPCContract.artifact).id], + }); + // this should be a SignerlessWallet but that can't call public functions directly gasTokenContract = await GasTokenContract.at( getCanonicalGasTokenAddress(deployL1ContractsValues.l1ContractAddresses.gasPortalAddress), diff --git a/yarn-project/end-to-end/src/e2e_fees.test.ts b/yarn-project/end-to-end/src/e2e_fees.test.ts index 794a359d775f..e7d5bee7cbc6 100644 --- a/yarn-project/end-to-end/src/e2e_fees.test.ts +++ b/yarn-project/end-to-end/src/e2e_fees.test.ts @@ -14,7 +14,7 @@ import { computeAuthWitMessageHash, computeMessageSecretHash, } from '@aztec/aztec.js'; -import { FunctionData } from '@aztec/circuits.js'; +import { FunctionData, getContractClassFromArtifact } from '@aztec/circuits.js'; import { ContractArtifact, decodeFunctionSignature } from '@aztec/foundation/abi'; import { TokenContract as BananaCoin, @@ -40,7 +40,7 @@ const TOKEN_SYMBOL = 'BC'; const TOKEN_DECIMALS = 18n; const BRIDGED_FPC_GAS = 500n; -jest.setTimeout(100_000); +jest.setTimeout(1_000_000_000); describe('e2e_fees', () => { let aliceWallet: Wallet; @@ -62,6 +62,9 @@ describe('e2e_fees', () => { e2eContext = await setup(3); const { accounts, logger, aztecNode, pxe, deployL1ContractsValues, wallets } = e2eContext; + await aztecNode.setConfig({ + allowedFeePaymentContractClasses: [getContractClassFromArtifact(FPCContract.artifact).id], + }); logFunctionSignatures(BananaCoin.artifact, logger); logFunctionSignatures(FPCContract.artifact, logger); diff --git a/yarn-project/sequencer-client/src/client/sequencer-client.ts b/yarn-project/sequencer-client/src/client/sequencer-client.ts index b85130d57167..5a413a03e4f2 100644 --- a/yarn-project/sequencer-client/src/client/sequencer-client.ts +++ b/yarn-project/sequencer-client/src/client/sequencer-client.ts @@ -14,6 +14,7 @@ import { EmptyRollupProver } from '../prover/empty.js'; import { getL1Publisher } from '../publisher/index.js'; import { Sequencer, SequencerConfig } from '../sequencer/index.js'; import { PublicProcessorFactory } from '../sequencer/public_processor.js'; +import { TxValidatorFactory } from '../sequencer/tx_validator_factory.js'; import { NativeACVMSimulator } from '../simulator/acvm_native.js'; import { WASMSimulator } from '../simulator/acvm_wasm.js'; import { RealRollupCircuitSimulator } from '../simulator/rollup.js'; @@ -78,12 +79,7 @@ export class SequencerClient { new EmptyRollupProver(), ); - const publicProcessorFactory = new PublicProcessorFactory( - merkleTreeDb, - contractDataSource, - l1ToL2MessageSource, - simulationProvider, - ); + const publicProcessorFactory = new PublicProcessorFactory(merkleTreeDb, contractDataSource, simulationProvider); const sequencer = new Sequencer( publisher, @@ -94,8 +90,8 @@ export class SequencerClient { l2BlockSource, l1ToL2MessageSource, publicProcessorFactory, + new TxValidatorFactory(merkleTreeDb, contractDataSource, config.l1Contracts.gasPortalAddress), config, - config.l1Contracts.gasPortalAddress, ); await sequencer.start(); diff --git a/yarn-project/sequencer-client/src/config.ts b/yarn-project/sequencer-client/src/config.ts index a1d601030990..f54dabbe3a9e 100644 --- a/yarn-project/sequencer-client/src/config.ts +++ b/yarn-project/sequencer-client/src/config.ts @@ -1,4 +1,4 @@ -import { AztecAddress } from '@aztec/circuits.js'; +import { AztecAddress, Fr } from '@aztec/circuits.js'; import { L1ContractAddresses, NULL_KEY } from '@aztec/ethereum'; import { EthAddress } from '@aztec/foundation/eth-address'; @@ -40,6 +40,8 @@ export function getConfigEnvVars(): SequencerClientConfig { SEQ_TX_POLLING_INTERVAL_MS, SEQ_MAX_TX_PER_BLOCK, SEQ_MIN_TX_PER_BLOCK, + SEQ_FPC_CLASSES, + SEQ_FPC_INSTANCES, AVAILABILITY_ORACLE_CONTRACT_ADDRESS, ROLLUP_CONTRACT_ADDRESS, REGISTRY_CONTRACT_ADDRESS, @@ -88,5 +90,9 @@ export function getConfigEnvVars(): SequencerClientConfig { feeRecipient: FEE_RECIPIENT ? AztecAddress.fromString(FEE_RECIPIENT) : undefined, acvmWorkingDirectory: ACVM_WORKING_DIRECTORY ? ACVM_WORKING_DIRECTORY : undefined, acvmBinaryPath: ACVM_BINARY_PATH ? ACVM_BINARY_PATH : undefined, + allowedFeePaymentContractClasses: SEQ_FPC_CLASSES ? SEQ_FPC_CLASSES.split(',').map(Fr.fromString) : [], + allowedFeePaymentContractInstances: SEQ_FPC_INSTANCES + ? SEQ_FPC_INSTANCES.split(',').map(AztecAddress.fromString) + : [], }; } diff --git a/yarn-project/sequencer-client/src/sequencer/public_processor.ts b/yarn-project/sequencer-client/src/sequencer/public_processor.ts index 1c620bd27873..e0d773f916e3 100644 --- a/yarn-project/sequencer-client/src/sequencer/public_processor.ts +++ b/yarn-project/sequencer-client/src/sequencer/public_processor.ts @@ -1,4 +1,4 @@ -import { L1ToL2MessageSource, SimulationError, Tx } from '@aztec/circuit-types'; +import { SimulationError, Tx } from '@aztec/circuit-types'; import { TxSequencerProcessingStats } from '@aztec/circuit-types/stats'; import { GlobalVariables, Header } from '@aztec/circuits.js'; import { createDebugLogger } from '@aztec/foundation/log'; @@ -31,7 +31,6 @@ export class PublicProcessorFactory { constructor( private merkleTree: MerkleTreeOperations, private contractDataSource: ContractDataSource, - private l1Tol2MessagesDataSource: L1ToL2MessageSource, private simulator: SimulationProvider, ) {} @@ -50,7 +49,7 @@ export class PublicProcessorFactory { const publicContractsDB = new ContractsDataSourcePublicDB(this.contractDataSource); const worldStatePublicDB = new WorldStatePublicDB(this.merkleTree); - const worldStateDB = new WorldStateDB(this.merkleTree, this.l1Tol2MessagesDataSource); + const worldStateDB = new WorldStateDB(this.merkleTree); const publicExecutor = new PublicExecutor(worldStatePublicDB, publicContractsDB, worldStateDB, historicalHeader); return new PublicProcessor( this.merkleTree, diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts index 61ccb97a3464..4287072a79f4 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.test.ts @@ -20,6 +20,7 @@ import { L1Publisher } from '../index.js'; import { makeEmptyProcessedTx, makeProcessedTx } from './processed_tx.js'; import { PublicProcessor, PublicProcessorFactory } from './public_processor.js'; import { Sequencer } from './sequencer.js'; +import { TxValidatorFactory } from './tx_validator_factory.js'; describe('sequencer', () => { let publisher: MockProxy; @@ -77,6 +78,16 @@ describe('sequencer', () => { getBlockNumber: () => Promise.resolve(lastBlockNumber), }); + const txValidationFactory = mock({ + // accept everything! + buildTxValidator: () => + ({ + validateTxs(txs: any[]) { + return Promise.resolve([txs, []]); + }, + } as any), + }); + sequencer = new TestSubject( publisher, globalVariableBuilder, @@ -86,6 +97,7 @@ describe('sequencer', () => { l2BlockSource, l1ToL2MessageSource, publicProcessorFactory, + txValidationFactory, ); }); diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 84ce6bf3dfed..44a399820cd4 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -1,4 +1,4 @@ -import { L1ToL2MessageSource, L2Block, L2BlockSource, MerkleTreeId, Tx } from '@aztec/circuit-types'; +import { L1ToL2MessageSource, L2Block, L2BlockSource, Tx } from '@aztec/circuit-types'; import { L2BlockBuiltStats } from '@aztec/circuit-types/stats'; import { AztecAddress, EthAddress, GlobalVariables } from '@aztec/circuits.js'; import { times } from '@aztec/foundation/collection'; @@ -12,12 +12,12 @@ import { WorldStateStatus, WorldStateSynchronizer } from '@aztec/world-state'; import { BlockBuilder } from '../block_builder/index.js'; import { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js'; import { L1Publisher } from '../publisher/l1-publisher.js'; -import { WorldStatePublicDB } from '../simulator/public_executor.js'; import { ceilPowerOfTwo } from '../utils.js'; import { SequencerConfig } from './config.js'; import { ProcessedTx } from './processed_tx.js'; import { PublicProcessorFactory } from './public_processor.js'; import { TxValidator } from './tx_validator.js'; +import { TxValidatorFactory } from './tx_validator_factory.js'; /** * Sequencer client @@ -38,6 +38,8 @@ export class Sequencer { private _feeRecipient = AztecAddress.ZERO; private lastPublishedBlock = 0; private state = SequencerState.STOPPED; + private allowedFeePaymentContractClasses: Fr[] = []; + private allowedFeePaymentContractInstances: AztecAddress[] = []; constructor( private publisher: L1Publisher, @@ -48,8 +50,8 @@ export class Sequencer { private l2BlockSource: L2BlockSource, private l1ToL2MessageSource: L1ToL2MessageSource, private publicProcessorFactory: PublicProcessorFactory, + private txValidatorFactory: TxValidatorFactory, config: SequencerConfig = {}, - private gasPortalAddress = EthAddress.ZERO, private log = createDebugLogger('aztec:sequencer'), ) { this.updateConfig(config); @@ -76,6 +78,12 @@ export class Sequencer { if (config.feeRecipient) { this._feeRecipient = config.feeRecipient; } + if (config.allowedFeePaymentContractClasses) { + this.allowedFeePaymentContractClasses = config.allowedFeePaymentContractClasses; + } + if (config.allowedFeePaymentContractInstances) { + this.allowedFeePaymentContractInstances = config.allowedFeePaymentContractInstances; + } } /** @@ -173,17 +181,10 @@ export class Sequencer { this._feeRecipient, ); - // Filter out invalid txs - const trees = this.worldState.getLatest(); - const txValidator = new TxValidator( - { - getNullifierIndex(nullifier: Fr): Promise { - return trees.findLeafIndex(MerkleTreeId.NULLIFIER_TREE, nullifier.toBuffer()); - }, - }, - new WorldStatePublicDB(trees), - this.gasPortalAddress, + const txValidator = this.txValidatorFactory.buildTxValidator( newGlobalVariables, + this.allowedFeePaymentContractClasses, + this.allowedFeePaymentContractInstances, ); // TODO: It should be responsibility of the P2P layer to validate txs before passing them on here diff --git a/yarn-project/sequencer-client/src/sequencer/tx_validator.test.ts b/yarn-project/sequencer-client/src/sequencer/tx_validator.test.ts index 7076b6e1cc03..cdb9228f4d8c 100644 --- a/yarn-project/sequencer-client/src/sequencer/tx_validator.test.ts +++ b/yarn-project/sequencer-client/src/sequencer/tx_validator.test.ts @@ -16,6 +16,7 @@ import { makeAztecAddress, makeGlobalVariables } from '@aztec/circuits.js/testin import { makeTuple } from '@aztec/foundation/array'; import { pedersenHash } from '@aztec/foundation/crypto'; import { getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token'; +import { ContractDataSource } from '@aztec/types/contracts'; import { MockProxy, mock, mockFn } from 'jest-mock-extended'; @@ -26,12 +27,18 @@ describe('TxValidator', () => { let globalVariables: GlobalVariables; let nullifierSource: MockProxy; let publicStateSource: MockProxy; + let contractDataSource: MockProxy; + let allowedFPCClass: Fr; + let allowedFPC: AztecAddress; let gasPortalAddress: EthAddress; let gasTokenAddress: AztecAddress; beforeEach(() => { gasPortalAddress = EthAddress.random(); gasTokenAddress = getCanonicalGasTokenAddress(gasPortalAddress); + allowedFPCClass = Fr.random(); + allowedFPC = makeAztecAddress(100); + nullifierSource = mock({ getNullifierIndex: mockFn().mockImplementation(() => { return Promise.resolve(undefined); @@ -46,8 +53,20 @@ describe('TxValidator', () => { } }), }); + contractDataSource = mock({ + getContract: mockFn().mockImplementation(() => { + return Promise.resolve({ + contractClassId: allowedFPCClass, + }); + }), + }); + globalVariables = makeGlobalVariables(); - validator = new TxValidator(nullifierSource, publicStateSource, gasPortalAddress, globalVariables); + validator = new TxValidator(nullifierSource, publicStateSource, contractDataSource, globalVariables, { + allowedFeePaymentContractClasses: [allowedFPCClass], + allowedFeePaymentContractInstances: [allowedFPC], + gasPortalAddress, + }); }); describe('inspects tx metadata', () => { @@ -93,6 +112,52 @@ describe('TxValidator', () => { }); }); + describe('inspects how fee is paid', () => { + it('allows native gas', async () => { + const tx = nativeFeePayingTx(makeAztecAddress()); + // check that the whitelist on contract address won't shadow this check + contractDataSource.getContract.mockImplementationOnce(() => { + return Promise.resolve({ contractClassId: Fr.random() } as any); + }); + await expect(validator.validateTxs([tx])).resolves.toEqual([[tx], []]); + }); + + it('allows correct contract class', async () => { + const fpc = makeAztecAddress(); + const tx = fxFeePayingTx(fpc); + + contractDataSource.getContract.mockImplementationOnce(address => { + if (fpc.equals(address)) { + return Promise.resolve({ contractClassId: allowedFPCClass } as any); + } else { + return Promise.resolve({ contractClassId: Fr.random() }); + } + }); + + await expect(validator.validateTxs([tx])).resolves.toEqual([[tx], []]); + }); + + it('allows correct contract', async () => { + const tx = fxFeePayingTx(allowedFPC); + // check that the whitelist on contract address works and won't get shadowed by the class whitelist + contractDataSource.getContract.mockImplementationOnce(() => { + return Promise.resolve({ contractClassId: Fr.random() } as any); + }); + await expect(validator.validateTxs([tx])).resolves.toEqual([[tx], []]); + }); + + it('rejects incorrect contract and class', async () => { + const fpc = makeAztecAddress(); + const tx = fxFeePayingTx(fpc); + + contractDataSource.getContract.mockImplementationOnce(() => { + return Promise.resolve({ contractClassId: Fr.random() } as any); + }); + + await expect(validator.validateTxs([tx])).resolves.toEqual([[], [tx]]); + }); + }); + describe('inspects tx gas', () => { it('allows native fee paying txs', async () => { const sender = makeAztecAddress(); diff --git a/yarn-project/sequencer-client/src/sequencer/tx_validator.ts b/yarn-project/sequencer-client/src/sequencer/tx_validator.ts index 271b9a2928c9..8053b0f23e4d 100644 --- a/yarn-project/sequencer-client/src/sequencer/tx_validator.ts +++ b/yarn-project/sequencer-client/src/sequencer/tx_validator.ts @@ -1,8 +1,9 @@ import { Tx } from '@aztec/circuit-types'; -import { AztecAddress, EthAddress, Fr, GlobalVariables } from '@aztec/circuits.js'; +import { AztecAddress, EthAddress, Fr, GlobalVariables, PublicCallRequest } from '@aztec/circuits.js'; import { pedersenHash } from '@aztec/foundation/crypto'; import { Logger, createDebugLogger } from '@aztec/foundation/log'; import { getCanonicalGasTokenAddress } from '@aztec/protocol-contracts/gas-token'; +import { ContractDataSource } from '@aztec/types/contracts'; import { AbstractPhaseManager, PublicKernelPhase } from './abstract_phase_manager.js'; import { ProcessedTx } from './processed_tx.js'; @@ -28,25 +29,33 @@ type TxValidationStatus = typeof VALID_TX | typeof INVALID_TX; // the storage slot associated with "storage.balances" const GAS_TOKEN_BALANCES_SLOT = new Fr(1); +type FeeValidationConfig = { + gasPortalAddress: EthAddress; + allowedFeePaymentContractClasses: Fr[]; + allowedFeePaymentContractInstances: AztecAddress[]; +}; + export class TxValidator { #log: Logger; #globalVariables: GlobalVariables; #nullifierSource: NullifierSource; #publicStateSource: PublicStateSource; - #gasPortalAddress: EthAddress; + #contractDataSource: ContractDataSource; + #feeValidationConfig: FeeValidationConfig; constructor( nullifierSource: NullifierSource, publicStateSource: PublicStateSource, - gasPortalAddress: EthAddress, + contractDataSource: ContractDataSource, globalVariables: GlobalVariables, + feeValidationConfig: FeeValidationConfig, log = createDebugLogger('aztec:sequencer:tx_validator'), ) { this.#nullifierSource = nullifierSource; - this.#globalVariables = globalVariables; this.#publicStateSource = publicStateSource; - this.#gasPortalAddress = gasPortalAddress; - + this.#contractDataSource = contractDataSource; + this.#globalVariables = globalVariables; + this.#feeValidationConfig = feeValidationConfig; this.#log = log; } @@ -72,9 +81,15 @@ export class TxValidator { } // skip already processed transactions - if (tx instanceof Tx && (await this.#validateFee(tx)) === INVALID_TX) { - invalidTxs.push(tx); - continue; + if (tx instanceof Tx) { + if ((await this.#validateFee(tx)) === INVALID_TX) { + invalidTxs.push(tx); + continue; + } + if ((await this.#validateGasBalance(tx)) === INVALID_TX) { + invalidTxs.push(tx); + continue; + } } validTxs.push(tx); @@ -146,30 +161,17 @@ export class TxValidator { return VALID_TX; } - async #validateFee(tx: Tx): Promise { + async #validateGasBalance(tx: Tx): Promise { if (!tx.data.needsTeardown) { - // TODO check if fees are mandatory and reject this tx - this.#log.debug(`Tx ${Tx.getHash(tx)} doesn't pay for gas`); return VALID_TX; } - const { - // TODO what if there's more than one function call? - // if we're to enshrine that teardown = 1 function call, then we should turn this into a single function call - [PublicKernelPhase.TEARDOWN]: [teardownFn], - } = AbstractPhaseManager.extractEnqueuedPublicCallsByPhase(tx.data, tx.enqueuedPublicFunctionCalls); - - if (!teardownFn) { - this.#log.warn( - `Rejecting tx ${Tx.getHash(tx)} because it should pay for gas but has no enqueued teardown function call`, - ); - return INVALID_TX; - } + const teardownFn = TxValidator.#extractFeeExecutionCall(tx)!; // TODO(#1204) if a generator index is used for the derived storage slot of a map, update it here as well const slot = pedersenHash([GAS_TOKEN_BALANCES_SLOT.toBuffer(), teardownFn.callContext.msgSender.toBuffer()]); const gasBalance = await this.#publicStateSource.storageRead( - getCanonicalGasTokenAddress(this.#gasPortalAddress), + getCanonicalGasTokenAddress(this.#feeValidationConfig.gasPortalAddress), slot, ); @@ -186,4 +188,55 @@ export class TxValidator { return VALID_TX; } + + async #validateFee(tx: Tx): Promise { + if (!tx.data.needsTeardown) { + // TODO check if fees are mandatory and reject this tx + this.#log.debug(`Tx ${Tx.getHash(tx)} doesn't pay for gas`); + return VALID_TX; + } + + const teardownFn = TxValidator.#extractFeeExecutionCall(tx); + if (!teardownFn) { + this.#log.warn( + `Rejecting tx ${Tx.getHash(tx)} because it should pay for gas but has no enqueued teardown function call`, + ); + return INVALID_TX; + } + + const fpcAddress = teardownFn.contractAddress; + const contractClass = await this.#contractDataSource.getContract(fpcAddress); + + if (!contractClass) { + return INVALID_TX; + } + + if (fpcAddress.equals(getCanonicalGasTokenAddress(this.#feeValidationConfig.gasPortalAddress))) { + return VALID_TX; + } + + for (const allowedContract of this.#feeValidationConfig.allowedFeePaymentContractInstances) { + if (fpcAddress.equals(allowedContract)) { + return VALID_TX; + } + } + + for (const allowedContractClass of this.#feeValidationConfig.allowedFeePaymentContractClasses) { + if (contractClass.contractClassId.equals(allowedContractClass)) { + return VALID_TX; + } + } + + return INVALID_TX; + } + + static #extractFeeExecutionCall(tx: Tx): PublicCallRequest | undefined { + const { + // TODO what if there's more than one function call? + // if we're to enshrine that teardown = 1 function call, then we should turn this into a single function call + [PublicKernelPhase.TEARDOWN]: [teardownFn], + } = AbstractPhaseManager.extractEnqueuedPublicCallsByPhase(tx.data, tx.enqueuedPublicFunctionCalls); + + return teardownFn; + } } diff --git a/yarn-project/sequencer-client/src/sequencer/tx_validator_factory.ts b/yarn-project/sequencer-client/src/sequencer/tx_validator_factory.ts new file mode 100644 index 000000000000..75a7a7b2ce37 --- /dev/null +++ b/yarn-project/sequencer-client/src/sequencer/tx_validator_factory.ts @@ -0,0 +1,32 @@ +import { AztecAddress, EthAddress, Fr, GlobalVariables } from '@aztec/circuits.js'; +import { ContractDataSource } from '@aztec/types/contracts'; +import { MerkleTreeOperations } from '@aztec/world-state'; + +import { WorldStateDB, WorldStatePublicDB } from '../simulator/public_executor.js'; +import { TxValidator } from './tx_validator.js'; + +export class TxValidatorFactory { + constructor( + private merkleTreeDb: MerkleTreeOperations, + private contractDataSource: ContractDataSource, + private gasPortalAddress: EthAddress, + ) {} + + buildTxValidator( + globalVariables: GlobalVariables, + allowedFeePaymentContractClasses: Fr[], + allowedFeePaymentContractInstances: AztecAddress[], + ): TxValidator { + return new TxValidator( + new WorldStateDB(this.merkleTreeDb), + new WorldStatePublicDB(this.merkleTreeDb), + this.contractDataSource, + globalVariables, + { + allowedFeePaymentContractClasses, + allowedFeePaymentContractInstances, + gasPortalAddress: this.gasPortalAddress, + }, + ); + } +} diff --git a/yarn-project/sequencer-client/src/simulator/public_executor.ts b/yarn-project/sequencer-client/src/simulator/public_executor.ts index c93c6d7e6c83..1590b05400b9 100644 --- a/yarn-project/sequencer-client/src/simulator/public_executor.ts +++ b/yarn-project/sequencer-client/src/simulator/public_executor.ts @@ -1,10 +1,4 @@ -import { - L1ToL2MessageSource, - MerkleTreeId, - NullifierMembershipWitness, - Tx, - UnencryptedL2Log, -} from '@aztec/circuit-types'; +import { MerkleTreeId, NullifierMembershipWitness, Tx, UnencryptedL2Log } from '@aztec/circuit-types'; import { AztecAddress, ContractClassRegisteredEvent, @@ -198,7 +192,7 @@ export class WorldStatePublicDB implements PublicStateDB { * Implements WorldState db using a world state database. */ export class WorldStateDB implements CommitmentsDB { - constructor(private db: MerkleTreeOperations, private l1ToL2MessageSource: L1ToL2MessageSource) {} + constructor(private db: MerkleTreeOperations) {} public async getNullifierMembershipWitnessAtLatestBlock( nullifier: Fr,