Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -644,7 +644,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
2 changes: 2 additions & 0 deletions yarn-project/aztec/src/cli/texts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ export const cliTexts = {
'transactionPollingIntervalMS:SEQ_TX_POLLING_INTERVAL_MS - number - The interval in ms to wait before polling for new transactions. Default: 1000\n' +
'acvmBinaryPath:ACVM_BINARY_PATH - string - The full path to an instance of the acvm cli application. If not provided will fallback to WASM circuit simulation\n' +
'acvmWorkingDirectory:ACVM_WORKING_DIRECTORY - string - A directory to use for temporary files used by the acvm application. If not provided WASM circuit simulation will be used\n' +
'allowedFeePaymentContractClasses:SEQ_FPC_CLASSES - string[] - Which fee payment contract classes the sequencer allows' +
'allowedFeePaymentContractInstances:SEQ_FPC_INSTANCES - string[] - Which fee payment contracts the sequencer allows.' +
contractAddresses,
prover:
'Starts a Prover with options. If started additionally to --node, the Prover will attach to that node.\n' +
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 @@ -15,7 +15,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 @@ -41,7 +41,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 @@ -63,6 +63,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 @@ -10,6 +10,7 @@ import { getGlobalVariableBuilder } from '../global_variable_builder/index.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';

/**
* Encapsulates the full sequencer and publisher.
Expand Down Expand Up @@ -43,12 +44,7 @@ export class SequencerClient {
const globalsBuilder = getGlobalVariableBuilder(config);
const merkleTreeDb = worldStateSynchronizer.getLatest();

const publicProcessorFactory = new PublicProcessorFactory(
merkleTreeDb,
contractDataSource,
l1ToL2MessageSource,
simulationProvider,
);
const publicProcessorFactory = new PublicProcessorFactory(merkleTreeDb, contractDataSource, simulationProvider);

const sequencer = new Sequencer(
publisher,
Expand All @@ -59,8 +55,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) : [],
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you just check that this works from the command line? When setting up a node in the aztec package.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new env vars works correctly but the aztec start sequencer options (e.g. aztec start --sequencer allowedFeePaymentContractClasses=0xabcdef,feeRecipient=0x12345) are not parsed at all so the sequencer gets them as strings instead of addresses/arrays.

This will require a bit of refactoring, I'll open a separate PR with a fix to parse all of the sequenecer's config options.

allowedFeePaymentContractInstances: SEQ_FPC_INSTANCES
? SEQ_FPC_INSTANCES.split(',').map(AztecAddress.fromString)
: [],
};
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import {
FailedTx,
L1ToL2MessageSource,
ProcessedTx,
SimulationError,
Tx,
Expand Down Expand Up @@ -31,7 +30,6 @@ export class PublicProcessorFactory {
constructor(
private merkleTree: MerkleTreeOperations,
private contractDataSource: ContractDataSource,
private l1Tol2MessagesDataSource: L1ToL2MessageSource,
private simulator: SimulationProvider,
) {}

Expand All @@ -50,7 +48,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 @@ -21,6 +21,7 @@ import {
makeEmptyProof,
} from '@aztec/circuits.js';
import { P2P, P2PClientState } from '@aztec/p2p';
import { ContractDataSource } from '@aztec/types/contracts';
import { MerkleTreeOperations, WorldStateRunningState, WorldStateSynchronizer } from '@aztec/world-state';

import { MockProxy, mock, mockFn } from 'jest-mock-extended';
Expand All @@ -29,6 +30,7 @@ import { GlobalVariableBuilder } from '../global_variable_builder/global_builder
import { L1Publisher } from '../index.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 @@ -86,6 +88,12 @@ describe('sequencer', () => {
getBlockNumber: () => Promise.resolve(lastBlockNumber),
});

// all txs use the same allowed FPC class
const fpcClassId = Fr.random();
const contractSource = mock<ContractDataSource>({
getContractClass: mockFn().mockResolvedValue(fpcClassId),
});

sequencer = new TestSubject(
publisher,
globalVariableBuilder,
Expand All @@ -95,6 +103,10 @@ describe('sequencer', () => {
l2BlockSource,
l1ToL2MessageSource,
publicProcessorFactory,
new TxValidatorFactory(merkleTreeOps, contractSource, EthAddress.random()),
{
allowedFeePaymentContractClasses: [fpcClassId],
},
);
});

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, ProcessedTx, Tx } from '@aztec/circuit-types';
import { L1ToL2MessageSource, L2Block, L2BlockSource, ProcessedTx, Tx } from '@aztec/circuit-types';
import { BlockProver, PROVING_STATUS } from '@aztec/circuit-types/interfaces';
import { L2BlockBuiltStats } from '@aztec/circuit-types/stats';
import { AztecAddress, EthAddress, GlobalVariables } from '@aztec/circuits.js';
Expand All @@ -11,10 +11,10 @@ import { WorldStateStatus, WorldStateSynchronizer } from '@aztec/world-state';

import { GlobalVariableBuilder } from '../global_variable_builder/global_builder.js';
import { L1Publisher } from '../publisher/l1-publisher.js';
import { WorldStatePublicDB } from '../simulator/public_executor.js';
import { SequencerConfig } from './config.js';
import { PublicProcessorFactory } from './public_processor.js';
import { TxValidator } from './tx_validator.js';
import { TxValidatorFactory } from './tx_validator_factory.js';

/**
* Sequencer client
Expand All @@ -35,6 +35,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 @@ -45,8 +47,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 @@ -73,6 +75,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 @@ -170,17 +178,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