Skip to content

Commit

Permalink
feat: sequencer checks list of allowed FPCs
Browse files Browse the repository at this point in the history
  • Loading branch information
alexghr committed Mar 19, 2024
1 parent 56af753 commit b62d1ca
Show file tree
Hide file tree
Showing 13 changed files with 233 additions and 62 deletions.
1 change: 0 additions & 1 deletion yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/circuit-types/src/interfaces/configs.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { AztecAddress, EthAddress } from '@aztec/circuits.js';
import { AztecAddress, EthAddress, Fr } from '@aztec/circuits.js';

/**
* The sequencer configuration.
Expand All @@ -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[];
}
5 changes: 5 additions & 0 deletions yarn-project/end-to-end/src/e2e_dapp_subscription.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
PrivateFeePaymentMethod,
PublicFeePaymentMethod,
SentTx,
getContractClassFromArtifact,
} from '@aztec/aztec.js';
import { DefaultDappEntrypoint } from '@aztec/entrypoints/dapp';
import {
Expand Down Expand Up @@ -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),
Expand Down
7 changes: 5 additions & 2 deletions yarn-project/end-to-end/src/e2e_fees.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand All @@ -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);
Expand Down
10 changes: 3 additions & 7 deletions yarn-project/sequencer-client/src/client/sequencer-client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
Expand All @@ -94,8 +90,8 @@ export class SequencerClient {
l2BlockSource,
l1ToL2MessageSource,
publicProcessorFactory,
new TxValidatorFactory(merkleTreeDb, contractDataSource, config.l1Contracts.gasPortalAddress),
config,
config.l1Contracts.gasPortalAddress,
);

await sequencer.start();
Expand Down
8 changes: 7 additions & 1 deletion yarn-project/sequencer-client/src/config.ts
Original file line number Diff line number Diff line change
@@ -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';

Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
: [],
};
}
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -31,7 +31,6 @@ export class PublicProcessorFactory {
constructor(
private merkleTree: MerkleTreeOperations,
private contractDataSource: ContractDataSource,
private l1Tol2MessagesDataSource: L1ToL2MessageSource,
private simulator: SimulationProvider,
) {}

Expand All @@ -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,
Expand Down
12 changes: 12 additions & 0 deletions yarn-project/sequencer-client/src/sequencer/sequencer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<L1Publisher>;
Expand Down Expand Up @@ -77,6 +78,16 @@ describe('sequencer', () => {
getBlockNumber: () => Promise.resolve(lastBlockNumber),
});

const txValidationFactory = mock<TxValidatorFactory>({
// accept everything!
buildTxValidator: () =>
({
validateTxs(txs: any[]) {
return Promise.resolve([txs, []]);
},
} as any),
});

sequencer = new TestSubject(
publisher,
globalVariableBuilder,
Expand All @@ -86,6 +97,7 @@ describe('sequencer', () => {
l2BlockSource,
l1ToL2MessageSource,
publicProcessorFactory,
txValidationFactory,
);
});

Expand Down
27 changes: 14 additions & 13 deletions yarn-project/sequencer-client/src/sequencer/sequencer.ts
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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
Expand All @@ -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,
Expand All @@ -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);
Expand All @@ -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;
}
}

/**
Expand Down Expand Up @@ -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<bigint | undefined> {
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
Expand Down
67 changes: 66 additions & 1 deletion yarn-project/sequencer-client/src/sequencer/tx_validator.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand All @@ -26,12 +27,18 @@ describe('TxValidator', () => {
let globalVariables: GlobalVariables;
let nullifierSource: MockProxy<NullifierSource>;
let publicStateSource: MockProxy<PublicStateSource>;
let contractDataSource: MockProxy<ContractDataSource>;
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<NullifierSource>({
getNullifierIndex: mockFn().mockImplementation(() => {
return Promise.resolve(undefined);
Expand All @@ -46,8 +53,20 @@ describe('TxValidator', () => {
}
}),
});
contractDataSource = mock<ContractDataSource>({
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', () => {
Expand Down Expand Up @@ -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();
Expand Down
Loading

0 comments on commit b62d1ca

Please sign in to comment.