diff --git a/docs/docs-operate/operators/reference/changelog/v4.md b/docs/docs-operate/operators/reference/changelog/v4.md index de6d28118215..dc8e0cce3d35 100644 --- a/docs/docs-operate/operators/reference/changelog/v4.md +++ b/docs/docs-operate/operators/reference/changelog/v4.md @@ -70,6 +70,30 @@ The `getL2Tips()` RPC endpoint now returns a restructured response with addition - Replace `tips.latest` with `tips.proposed` - For `checkpointed`, `proven`, and `finalized` tips, access block info via `.block` (e.g., `tips.proven.block.number`) +### Setup phase allow list requires function selectors + +The transaction setup phase allow list now enforces function selectors, restricting which specific functions can run during setup on whitelisted contracts. Previously, any public function on a whitelisted contract or class was permitted. + +The semantics of the environment variable `TX_PUBLIC_SETUP_ALLOWLIST` have changed: + +**v3.x:** + +```bash +--txPublicSetupAllowList ($TX_PUBLIC_SETUP_ALLOWLIST) +``` + +The variable fully **replaced** the hardcoded defaults. Format allowed entries without selectors: `I:address`, `C:classId`. + +**v4.0.0:** + +```bash +--txPublicSetupAllowListExtend ($TX_PUBLIC_SETUP_ALLOWLIST) +``` + +The variable now **extends** the hardcoded defaults (which are always present). Selectors are now mandatory. Format: `I:address:selector,C:classId:selector`. + +**Migration**: If you were using `TX_PUBLIC_SETUP_ALLOWLIST`, ensure all entries include function selectors. Note the variable now adds to defaults rather than replacing them. If you were not setting this variable, no action is needed — the hardcoded defaults now include the correct selectors automatically. + ## Removed features ## New features @@ -137,6 +161,10 @@ Transaction submission via RPC now returns structured rejection codes when a tra **Impact**: Improved developer experience — callers can now programmatically handle specific rejection reasons. +### Setup allow list extendable via network config + +The setup phase allow list can now be extended via the network configuration JSON (`txPublicSetupAllowListExtend` field). This allows network operators to distribute additional allowed setup functions to all nodes without requiring code changes. The local environment variable takes precedence over the network-json value. + ## Changed defaults ## Troubleshooting diff --git a/yarn-project/aztec-node/src/aztec-node/server.ts b/yarn-project/aztec-node/src/aztec-node/server.ts index 907a6a2c5e2c..5d8bc7bd69d8 100644 --- a/yarn-project/aztec-node/src/aztec-node/server.ts +++ b/yarn-project/aztec-node/src/aztec-node/server.ts @@ -342,9 +342,6 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { deps.p2pClientDeps, ); - // We should really not be modifying the config object - config.txPublicSetupAllowList = config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()); - // We'll accumulate sentinel watchers here const watchers: Watcher[] = []; @@ -618,7 +615,7 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { } public async getAllowedPublicSetup(): Promise { - return this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()); + return [...(await getDefaultAllowedSetupFunctions()), ...(this.config.txPublicSetupAllowListExtend ?? [])]; } /** @@ -1318,7 +1315,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable { blockNumber, l1ChainId: this.l1ChainId, rollupVersion: this.version, - setupAllowList: this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()), + setupAllowList: [ + ...(await getDefaultAllowedSetupFunctions()), + ...(this.config.txPublicSetupAllowListExtend ?? []), + ], gasFees: await this.getCurrentMinFees(), skipFeeEnforcement, txsPermitted: !this.config.disableTransactions, diff --git a/yarn-project/cli/src/config/network_config.ts b/yarn-project/cli/src/config/network_config.ts index 820f5f1b5da5..a8a9c59b8757 100644 --- a/yarn-project/cli/src/config/network_config.ts +++ b/yarn-project/cli/src/config/network_config.ts @@ -144,4 +144,7 @@ export async function enrichEnvironmentWithNetworkConfig(networkName: NetworkNam if (networkConfig.blockDurationMs !== undefined) { enrichVar('SEQ_BLOCK_DURATION_MS', String(networkConfig.blockDurationMs)); } + if (networkConfig.txPublicSetupAllowListExtend) { + enrichVar('TX_PUBLIC_SETUP_ALLOWLIST', networkConfig.txPublicSetupAllowListExtend); + } } diff --git a/yarn-project/foundation/src/config/network_config.ts b/yarn-project/foundation/src/config/network_config.ts index 5604eca90ff5..18cf67a2df7f 100644 --- a/yarn-project/foundation/src/config/network_config.ts +++ b/yarn-project/foundation/src/config/network_config.ts @@ -9,6 +9,7 @@ export const NetworkConfigSchema = z feeAssetHandlerAddress: z.string().optional(), l1ChainId: z.number(), blockDurationMs: z.number().positive().optional(), + txPublicSetupAllowListExtend: z.string().optional(), }) .passthrough(); // Allow additional unknown fields to pass through diff --git a/yarn-project/p2p/src/config.test.ts b/yarn-project/p2p/src/config.test.ts index f537cffab724..7c80cedbf670 100644 --- a/yarn-project/p2p/src/config.test.ts +++ b/yarn-project/p2p/src/config.test.ts @@ -5,18 +5,14 @@ import { AztecAddress } from '@aztec/stdlib/aztec-address'; import { getP2PDefaultConfig, parseAllowList } from './config.js'; describe('config', () => { - it('parses allow list', async () => { - const instance = { address: await AztecAddress.random() }; + it('parses allow list with required selectors', async () => { const instanceFunction = { address: await AztecAddress.random(), selector: FunctionSelector.random() }; - const classId = { classId: Fr.random() }; const classFunction = { classId: Fr.random(), selector: FunctionSelector.random() }; - const config = [instance, instanceFunction, classId, classFunction]; + const config = [instanceFunction, classFunction]; const configStrings = [ - `I:${instance.address}`, `I:${instanceFunction.address}:${instanceFunction.selector}`, - `C:${classId.classId}`, `C:${classFunction.classId}:${classFunction.selector}`, ]; const stringifiedAllowList = configStrings.join(','); @@ -25,6 +21,30 @@ describe('config', () => { expect(allowList).toEqual(config); }); + it('rejects instance entry without selector', async () => { + const address = await AztecAddress.random(); + expect(() => parseAllowList(`I:${address}`)).toThrow('selector is required'); + }); + + it('rejects class entry without selector', () => { + const classId = Fr.random(); + expect(() => parseAllowList(`C:${classId}`)).toThrow('selector is required'); + }); + + it('rejects entry with unknown type', () => { + expect(() => parseAllowList(`X:0x1234:0x12345678`)).toThrow('unknown type'); + }); + + it('parses empty string', () => { + expect(parseAllowList('')).toEqual([]); + }); + + it('handles whitespace in entries', async () => { + const instanceFunction = { address: await AztecAddress.random(), selector: FunctionSelector.random() }; + const allowList = parseAllowList(` I:${instanceFunction.address}:${instanceFunction.selector} `); + expect(allowList).toEqual([instanceFunction]); + }); + it('defaults missing txs collector type to new', () => { const config = getP2PDefaultConfig(); expect(config.txCollectionMissingTxsCollectorType).toBe('new'); diff --git a/yarn-project/p2p/src/config.ts b/yarn-project/p2p/src/config.ts index b7de5cc3038e..050f2b8bb233 100644 --- a/yarn-project/p2p/src/config.ts +++ b/yarn-project/p2p/src/config.ts @@ -150,8 +150,8 @@ export interface P2PConfig /** The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb. */ p2pStoreMapSizeKb?: number; - /** Which calls are allowed in the public setup phase of a tx. */ - txPublicSetupAllowList: AllowedElement[]; + /** Additional entries to extend the default setup allow list. */ + txPublicSetupAllowListExtend: AllowedElement[]; /** The maximum number of pending txs before evicting lower priority txs. */ maxPendingTxCount: number; @@ -393,12 +393,13 @@ export const p2pConfigMappings: ConfigMappingsType = { parseEnv: (val: string | undefined) => (val ? +val : undefined), description: 'The maximum possible size of the P2P DB in KB. Overwrites the general dataStoreMapSizeKb.', }, - txPublicSetupAllowList: { + txPublicSetupAllowListExtend: { env: 'TX_PUBLIC_SETUP_ALLOWLIST', parseEnv: (val: string) => parseAllowList(val), - description: 'The list of functions calls allowed to run in setup', + description: + 'Additional entries to extend the default setup allow list. Format: I:address:selector,C:classId:selector', printDefault: () => - 'AuthRegistry, FeeJuice.increase_public_balance, Token.increase_public_balance, FPC.prepare_fee', + 'Default: AuthRegistry._set_authorized, FeeJuice._increase_public_balance, Token._increase_public_balance, Token.transfer_in_public', }, maxPendingTxCount: { env: 'P2P_MAX_PENDING_TX_COUNT', @@ -523,11 +524,9 @@ export const bootnodeConfigMappings = pickConfigMappings( /** * Parses a string to a list of allowed elements. - * Each encoded is expected to be of one of the following formats - * `I:${address}` - * `I:${address}:${selector}` - * `C:${classId}` - * `C:${classId}:${selector}` + * Each entry is expected to be of one of the following formats: + * `I:${address}:${selector}` — instance (contract address) with function selector + * `C:${classId}:${selector}` — class with function selector * * @param value The string to parse * @returns A list of allowed elements @@ -540,31 +539,34 @@ export function parseAllowList(value: string): AllowedElement[] { } for (const val of value.split(',')) { - const [typeString, identifierString, selectorString] = val.split(':'); - const selector = selectorString !== undefined ? FunctionSelector.fromString(selectorString) : undefined; + const trimmed = val.trim(); + if (!trimmed) { + continue; + } + const [typeString, identifierString, selectorString] = trimmed.split(':'); + + if (!selectorString) { + throw new Error( + `Invalid allow list entry "${trimmed}": selector is required. Expected format: I:address:selector or C:classId:selector`, + ); + } + + const selector = FunctionSelector.fromString(selectorString); if (typeString === 'I') { - if (selector) { - entries.push({ - address: AztecAddress.fromString(identifierString), - selector, - }); - } else { - entries.push({ - address: AztecAddress.fromString(identifierString), - }); - } + entries.push({ + address: AztecAddress.fromString(identifierString), + selector, + }); } else if (typeString === 'C') { - if (selector) { - entries.push({ - classId: Fr.fromHexString(identifierString), - selector, - }); - } else { - entries.push({ - classId: Fr.fromHexString(identifierString), - }); - } + entries.push({ + classId: Fr.fromHexString(identifierString), + selector, + }); + } else { + throw new Error( + `Invalid allow list entry "${trimmed}": unknown type "${typeString}". Expected "I" (instance) or "C" (class).`, + ); } } diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/allowed_public_setup.ts b/yarn-project/p2p/src/msg_validators/tx_validator/allowed_public_setup.ts index 64578772ea82..b18fa82c4853 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/allowed_public_setup.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/allowed_public_setup.ts @@ -1,33 +1,47 @@ -import { FPCContract } from '@aztec/noir-contracts.js/FPC'; import { TokenContractArtifact } from '@aztec/noir-contracts.js/Token'; import { ProtocolContractAddress } from '@aztec/protocol-contracts'; +import { FunctionSelector } from '@aztec/stdlib/abi'; import { getContractClassFromArtifact } from '@aztec/stdlib/contract'; import type { AllowedElement } from '@aztec/stdlib/interfaces/server'; -let defaultAllowedSetupFunctions: AllowedElement[] | undefined = undefined; +let defaultAllowedSetupFunctions: AllowedElement[] | undefined; + +/** Returns the default list of functions allowed to run in the setup phase of a transaction. */ export async function getDefaultAllowedSetupFunctions(): Promise { if (defaultAllowedSetupFunctions === undefined) { + const tokenClassId = (await getContractClassFromArtifact(TokenContractArtifact)).id; + const setAuthorizedInternalSelector = await FunctionSelector.fromSignature('_set_authorized((Field),Field,bool)'); + const setAuthorizedSelector = await FunctionSelector.fromSignature('set_authorized(Field,bool)'); + const increaseBalanceSelector = await FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'); + const transferInPublicSelector = await FunctionSelector.fromSignature( + 'transfer_in_public((Field),(Field),u128,Field)', + ); + defaultAllowedSetupFunctions = [ - // needed for authwit support + // AuthRegistry: needed for authwit support via private path (set_authorized_private enqueues _set_authorized) + { + address: ProtocolContractAddress.AuthRegistry, + selector: setAuthorizedInternalSelector, + }, + // AuthRegistry: needed for authwit support via public path (PublicFeePaymentMethod calls set_authorized directly) { address: ProtocolContractAddress.AuthRegistry, + selector: setAuthorizedSelector, }, - // needed for claiming on the same tx as a spend + // FeeJuice: needed for claiming on the same tx as a spend (claim_and_end_setup enqueues this) { address: ProtocolContractAddress.FeeJuice, - // We can't restrict the selector because public functions get routed via dispatch. - // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'), + selector: increaseBalanceSelector, }, - // needed for private transfers via FPC + // Token: needed for private transfers via FPC (transfer_to_public enqueues this) { - classId: (await getContractClassFromArtifact(TokenContractArtifact)).id, - // We can't restrict the selector because public functions get routed via dispatch. - // selector: FunctionSelector.fromSignature('_increase_public_balance((Field),u128)'), + classId: tokenClassId, + selector: increaseBalanceSelector, }, + // Token: needed for public transfers via FPC (fee_entrypoint_public enqueues this) { - classId: (await getContractClassFromArtifact(FPCContract.artifact)).id, - // We can't restrict the selector because public functions get routed via dispatch. - // selector: FunctionSelector.fromSignature('prepare_fee((Field),Field,(Field),Field)'), + classId: tokenClassId, + selector: transferInPublicSelector, }, ]; } diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts index 966aaf930f2e..33636eb27d0a 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.test.ts @@ -3,7 +3,11 @@ import type { FunctionSelector } from '@aztec/stdlib/abi'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { ContractDataSource } from '@aztec/stdlib/contract'; import { makeAztecAddress, makeSelector, mockTx } from '@aztec/stdlib/testing'; -import { TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED, type Tx } from '@aztec/stdlib/tx'; +import { + TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED, + TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT, + type Tx, +} from '@aztec/stdlib/tx'; import { type MockProxy, mock, mockFn } from 'jest-mock-extended'; @@ -138,4 +142,60 @@ describe('PhasesTxValidator', () => { await expectInvalid(tx, TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED); }); + + it('rejects address match with wrong selector', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + const wrongSelector = makeSelector(99); + await patchNonRevertibleFn(tx, 0, { address: allowedContract, selector: wrongSelector }); + + await expectInvalid(tx, TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED); + }); + + it('rejects class match with wrong selector', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + const wrongSelector = makeSelector(99); + const address = await patchNonRevertibleFn(tx, 0, { selector: wrongSelector }); + + contractDataSource.getContract.mockImplementationOnce((contractAddress, atTimestamp) => { + if (timestamp !== atTimestamp) { + throw new Error('Unexpected timestamp'); + } + if (address.equals(contractAddress)) { + return Promise.resolve({ + currentContractClassId: allowedContractClass, + originalContractClassId: Fr.random(), + } as any); + } else { + return Promise.resolve(undefined); + } + }); + + await expectInvalid(tx, TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED); + }); + + it('rejects with unknown contract error when contract is not found', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + const address = await patchNonRevertibleFn(tx, 0, { selector: allowedSetupSelector1 }); + + contractDataSource.getContract.mockImplementationOnce((contractAddress, atTimestamp) => { + if (timestamp !== atTimestamp) { + throw new Error('Unexpected timestamp'); + } + if (address.equals(contractAddress)) { + return Promise.resolve(undefined); + } + return Promise.resolve(undefined); + }); + + await expectInvalid(tx, TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT); + }); + + it('does not fetch contract instance when matching by address', async () => { + const tx = await mockTx(1, { numberOfNonRevertiblePublicCallRequests: 1 }); + await patchNonRevertibleFn(tx, 0, { address: allowedContract, selector: allowedSetupSelector1 }); + + await expectValid(tx); + + expect(contractDataSource.getContract).not.toHaveBeenCalled(); + }); }); diff --git a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts index 4b6370fa0477..3e8c0f9b3313 100644 --- a/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts +++ b/yarn-project/p2p/src/msg_validators/tx_validator/phases_validator.ts @@ -6,6 +6,7 @@ import { type PublicCallRequestWithCalldata, TX_ERROR_DURING_VALIDATION, TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED, + TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT, Tx, TxExecutionPhase, type TxValidationResult, @@ -45,7 +46,8 @@ export class PhasesTxValidator implements TxValidator { const setupFns = getCallRequestsWithCalldataByPhase(tx, TxExecutionPhase.SETUP); for (const setupFn of setupFns) { - if (!(await this.isOnAllowList(setupFn, this.setupAllowList))) { + const rejectionReason = await this.checkAllowList(setupFn, this.setupAllowList); + if (rejectionReason) { this.#log.verbose( `Rejecting tx ${tx.getTxHash().toString()} because it calls setup function not on allow list: ${ setupFn.request.contractAddress @@ -53,7 +55,7 @@ export class PhasesTxValidator implements TxValidator { { allowList: this.setupAllowList }, ); - return { result: 'invalid', reason: [TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED] }; + return { result: 'invalid', reason: [rejectionReason] }; } } @@ -66,53 +68,47 @@ export class PhasesTxValidator implements TxValidator { } } - private async isOnAllowList( + /** Returns a rejection reason if the call is not on the allow list, or undefined if it is allowed. */ + private async checkAllowList( publicCall: PublicCallRequestWithCalldata, allowList: AllowedElement[], - ): Promise { + ): Promise { if (publicCall.isEmpty()) { - return true; + return undefined; } const contractAddress = publicCall.request.contractAddress; const functionSelector = publicCall.functionSelector; - // do these checks first since they don't require the contract class + // Check address-based entries first since they don't require the contract class. for (const entry of allowList) { - if ('address' in entry && !('selector' in entry)) { - if (contractAddress.equals(entry.address)) { - return true; - } - } - - if ('address' in entry && 'selector' in entry) { + if ('address' in entry) { if (contractAddress.equals(entry.address) && entry.selector.equals(functionSelector)) { - return true; + return undefined; } } + } - const contractClass = await this.contractsDB.getContractInstance(contractAddress, this.timestamp); - - if (!contractClass) { - throw new Error(`Contract not found: ${contractAddress}`); + // Check class-based entries. Fetch the contract instance lazily (only once). + let contractClassId: undefined | { value: string | undefined }; + for (const entry of allowList) { + if (!('classId' in entry)) { + continue; } - if ('classId' in entry && !('selector' in entry)) { - if (contractClass.currentContractClassId.equals(entry.classId)) { - return true; + if (contractClassId === undefined) { + const instance = await this.contractsDB.getContractInstance(contractAddress, this.timestamp); + contractClassId = { value: instance?.currentContractClassId.toString() }; + if (!contractClassId.value) { + return TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT; } } - if ('classId' in entry && 'selector' in entry) { - if ( - contractClass.currentContractClassId.equals(entry.classId) && - (entry.selector === undefined || entry.selector.equals(functionSelector)) - ) { - return true; - } + if (contractClassId.value === entry.classId.toString() && entry.selector.equals(functionSelector)) { + return undefined; } } - return false; + return TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED; } } diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts index 8da82a7d195b..6066e7d2b6b3 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts @@ -1621,7 +1621,10 @@ export class LibP2PService extends WithTracer implements P2PService { nextSlotTimestamp: UInt64, ): Promise> { const gasFees = await this.getGasFees(currentBlockNumber); - const allowedInSetup = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()); + const allowedInSetup = [ + ...(await getDefaultAllowedSetupFunctions()), + ...(this.config.txPublicSetupAllowListExtend ?? []), + ]; const blockNumber = BlockNumber(currentBlockNumber + 1); return createFirstStageTxValidationsForGossipedTransactions( diff --git a/yarn-project/sequencer-client/src/config.ts b/yarn-project/sequencer-client/src/config.ts index d6aa5d93f9f5..64a7d321a2a8 100644 --- a/yarn-project/sequencer-client/src/config.ts +++ b/yarn-project/sequencer-client/src/config.ts @@ -71,7 +71,7 @@ export type SequencerClientConfig = SequencerPublisherConfig & SequencerConfig & L1ReaderConfig & ChainConfig & - Pick & + Pick & Pick; export const sequencerConfigMappings: ConfigMappingsType = { @@ -220,7 +220,7 @@ export const sequencerConfigMappings: ConfigMappingsType = { description: 'Percent probability (0 - 100) of sequencer skipping checkpoint publishing (testing only)', ...numberConfigHelper(DefaultSequencerConfig.skipPublishingCheckpointsPercent), }, - ...pickConfigMappings(p2pConfigMappings, ['txPublicSetupAllowList']), + ...pickConfigMappings(p2pConfigMappings, ['txPublicSetupAllowListExtend']), }; export const sequencerClientConfigMappings: ConfigMappingsType = { diff --git a/yarn-project/sequencer-client/src/sequencer/sequencer.ts b/yarn-project/sequencer-client/src/sequencer/sequencer.ts index 527f7144bf60..7da938a6c828 100644 --- a/yarn-project/sequencer-client/src/sequencer/sequencer.ts +++ b/yarn-project/sequencer-client/src/sequencer/sequencer.ts @@ -110,7 +110,7 @@ export class Sequencer extends (EventEmitter as new () => TypedEventEmitter) { const filteredConfig = pickFromSchema(config, SequencerConfigSchema); - this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowList')); + this.log.info(`Updated sequencer config`, omit(filteredConfig, 'txPublicSetupAllowListExtend')); this.config = merge(this.config, filteredConfig); this.timetable = new SequencerTimetable( { diff --git a/yarn-project/stdlib/src/interfaces/allowed_element.ts b/yarn-project/stdlib/src/interfaces/allowed_element.ts index b8de0fd4e661..807f21e5286f 100644 --- a/yarn-project/stdlib/src/interfaces/allowed_element.ts +++ b/yarn-project/stdlib/src/interfaces/allowed_element.ts @@ -6,18 +6,14 @@ import type { FunctionSelector } from '../abi/function_selector.js'; import type { AztecAddress } from '../aztec-address/index.js'; import { schemas, zodFor } from '../schemas/index.js'; -type AllowedInstance = { address: AztecAddress }; type AllowedInstanceFunction = { address: AztecAddress; selector: FunctionSelector }; -type AllowedClass = { classId: Fr }; type AllowedClassFunction = { classId: Fr; selector: FunctionSelector }; -export type AllowedElement = AllowedInstance | AllowedInstanceFunction | AllowedClass | AllowedClassFunction; +export type AllowedElement = AllowedInstanceFunction | AllowedClassFunction; export const AllowedElementSchema = zodFor()( z.union([ z.object({ address: schemas.AztecAddress, selector: schemas.FunctionSelector }), - z.object({ address: schemas.AztecAddress }), z.object({ classId: schemas.Fr, selector: schemas.FunctionSelector }), - z.object({ classId: schemas.Fr }), ]), ); diff --git a/yarn-project/stdlib/src/interfaces/block-builder.ts b/yarn-project/stdlib/src/interfaces/block-builder.ts index b5b5ea9a4c1a..f0c4eb780468 100644 --- a/yarn-project/stdlib/src/interfaces/block-builder.ts +++ b/yarn-project/stdlib/src/interfaces/block-builder.ts @@ -50,14 +50,17 @@ export interface PublicProcessorValidator { export type FullNodeBlockBuilderConfig = Pick & Pick & - Pick; + Pick< + SequencerConfig, + 'txPublicSetupAllowListExtend' | 'fakeProcessingDelayPerTxMs' | 'fakeThrowAfterProcessingTxCount' + >; export const FullNodeBlockBuilderConfigKeys: (keyof FullNodeBlockBuilderConfig)[] = [ 'l1GenesisTime', 'slotDuration', 'l1ChainId', 'rollupVersion', - 'txPublicSetupAllowList', + 'txPublicSetupAllowListExtend', 'fakeProcessingDelayPerTxMs', 'fakeThrowAfterProcessingTxCount', ] as const; diff --git a/yarn-project/stdlib/src/interfaces/configs.ts b/yarn-project/stdlib/src/interfaces/configs.ts index 88b1366a6889..88c7db90d3eb 100644 --- a/yarn-project/stdlib/src/interfaces/configs.ts +++ b/yarn-project/stdlib/src/interfaces/configs.ts @@ -31,8 +31,8 @@ export interface SequencerConfig { acvmWorkingDirectory?: string; /** The path to the ACVM binary */ acvmBinaryPath?: string; - /** The list of functions calls allowed to run in setup */ - txPublicSetupAllowList?: AllowedElement[]; + /** Additional entries to extend the default setup allow list. */ + txPublicSetupAllowListExtend?: AllowedElement[]; /** Max block size */ maxBlockSizeInBytes?: number; /** Payload address to vote for */ @@ -94,7 +94,7 @@ export const SequencerConfigSchema = zodFor()( feeRecipient: schemas.AztecAddress.optional(), acvmWorkingDirectory: z.string().optional(), acvmBinaryPath: z.string().optional(), - txPublicSetupAllowList: z.array(AllowedElementSchema).optional(), + txPublicSetupAllowListExtend: z.array(AllowedElementSchema).optional(), maxBlockSizeInBytes: z.number().optional(), governanceProposerPayload: schemas.EthAddress.optional(), l1PublishingTime: z.number().optional(), @@ -132,7 +132,7 @@ type SequencerConfigOptionalKeys = | 'fakeProcessingDelayPerTxMs' | 'fakeThrowAfterProcessingTxCount' | 'l1PublishingTime' - | 'txPublicSetupAllowList' + | 'txPublicSetupAllowListExtend' | 'minValidTxsPerBlock' | 'minBlocksForCheckpoint'; diff --git a/yarn-project/stdlib/src/interfaces/validator.ts b/yarn-project/stdlib/src/interfaces/validator.ts index 608d08520758..c6596a6aca72 100644 --- a/yarn-project/stdlib/src/interfaces/validator.ts +++ b/yarn-project/stdlib/src/interfaces/validator.ts @@ -62,7 +62,7 @@ export type ValidatorClientConfig = ValidatorHASignerConfig & { }; export type ValidatorClientFullConfig = ValidatorClientConfig & - Pick & + Pick & Pick< SlasherConfig, 'slashBroadcastedInvalidBlockPenalty' | 'slashDuplicateProposalPenalty' | 'slashDuplicateAttestationPenalty' @@ -91,7 +91,7 @@ export const ValidatorClientConfigSchema = zodFor>()( ValidatorClientConfigSchema.extend({ - txPublicSetupAllowList: z.array(AllowedElementSchema).optional(), + txPublicSetupAllowListExtend: z.array(AllowedElementSchema).optional(), broadcastInvalidBlockProposal: z.boolean().optional(), maxTxsPerBlock: z.number().optional(), slashBroadcastedInvalidBlockPenalty: schemas.BigInt, diff --git a/yarn-project/stdlib/src/tx/validator/error_texts.ts b/yarn-project/stdlib/src/tx/validator/error_texts.ts index 30f4867d209f..cf737a2160c4 100644 --- a/yarn-project/stdlib/src/tx/validator/error_texts.ts +++ b/yarn-project/stdlib/src/tx/validator/error_texts.ts @@ -6,6 +6,7 @@ export const TX_ERROR_GAS_LIMIT_TOO_HIGH = 'Gas limit is higher than the amount // Phases export const TX_ERROR_SETUP_FUNCTION_NOT_ALLOWED = 'Setup function not on allow list'; +export const TX_ERROR_SETUP_FUNCTION_UNKNOWN_CONTRACT = 'Setup function targets unknown contract'; // Nullifiers export const TX_ERROR_DUPLICATE_NULLIFIER_IN_TX = 'Duplicate nullifier in tx'; diff --git a/yarn-project/validator-client/src/checkpoint_builder.ts b/yarn-project/validator-client/src/checkpoint_builder.ts index c8ec5c5671fe..74059c27ce35 100644 --- a/yarn-project/validator-client/src/checkpoint_builder.ts +++ b/yarn-project/validator-client/src/checkpoint_builder.ts @@ -148,7 +148,10 @@ export class CheckpointBuilder implements ICheckpointBlockBuilder { } protected async makeBlockBuilderDeps(globalVariables: GlobalVariables, fork: MerkleTreeWriteOperations) { - const txPublicSetupAllowList = this.config.txPublicSetupAllowList ?? (await getDefaultAllowedSetupFunctions()); + const txPublicSetupAllowList = [ + ...(await getDefaultAllowedSetupFunctions()), + ...(this.config.txPublicSetupAllowListExtend ?? []), + ]; const contractsDB = new PublicContractsDB(this.contractDataSource, this.log.getBindings()); const guardedFork = new GuardedMerkleTreeOperations(fork); diff --git a/yarn-project/validator-client/src/validator.integration.test.ts b/yarn-project/validator-client/src/validator.integration.test.ts index dd30f91bd4be..811a84927be0 100644 --- a/yarn-project/validator-client/src/validator.integration.test.ts +++ b/yarn-project/validator-client/src/validator.integration.test.ts @@ -127,7 +127,7 @@ describe('ValidatorClient Integration', () => { slotDuration: l1Constants.slotDuration, l1ChainId: chainId.toNumber(), rollupVersion: version.toNumber(), - txPublicSetupAllowList: [], + txPublicSetupAllowListExtend: [], }, synchronizer, archiver,