diff --git a/.test_patterns.yml b/.test_patterns.yml index b7dd16b0bc6c..bca446a5b8a7 100644 --- a/.test_patterns.yml +++ b/.test_patterns.yml @@ -86,6 +86,11 @@ tests: error_regex: "combined_a.get_value() > fq_ct::modulus" owners: - *suyash + # http://ci.aztec-labs.com/1593f7c89e22b51b + - regex: stdlib_primitives_tests stdlibBiggroupSecp256k1/1.WnafSecp256k1StaggerOutOfRangeFails + error_regex: "biggroup_nafs: stagger fragment is not in range" + owners: + - *luke # noir # Something to do with how I run the tests now. Think these are fine in nextest. diff --git a/yarn-project/p2p/src/msg_validators/block_proposal_validator/block_proposal_validator.test.ts b/yarn-project/p2p/src/msg_validators/block_proposal_validator/block_proposal_validator.test.ts index 514a182c96c5..c273d4e75b98 100644 --- a/yarn-project/p2p/src/msg_validators/block_proposal_validator/block_proposal_validator.test.ts +++ b/yarn-project/p2p/src/msg_validators/block_proposal_validator/block_proposal_validator.test.ts @@ -3,6 +3,7 @@ import { Secp256k1Signer } from '@aztec/foundation/crypto'; import { Fr } from '@aztec/foundation/fields'; import { PeerErrorSeverity } from '@aztec/stdlib/p2p'; import { makeBlockProposal, makeL2BlockHeader } from '@aztec/stdlib/testing'; +import { TxHash } from '@aztec/stdlib/tx'; import { mock } from 'jest-mock-extended'; @@ -14,7 +15,7 @@ describe('BlockProposalValidator', () => { beforeEach(() => { epochCache = mock(); - validator = new BlockProposalValidator(epochCache); + validator = new BlockProposalValidator(epochCache, { txsPermitted: true }); }); it('returns high tolerance error if slot number is not current or next slot', async () => { @@ -146,4 +147,75 @@ describe('BlockProposalValidator', () => { const result = await validator.validate(mockProposal); expect(result).toBeUndefined(); }); + + describe('transaction permission validation', () => { + it('returns mid tolerance error if txs not permitted and proposal contains txHashes', async () => { + const currentProposer = Secp256k1Signer.random(); + const validatorWithTxsDisabled = new BlockProposalValidator(epochCache, { txsPermitted: false }); + + // Create a block proposal with transaction hashes + const mockProposal = makeBlockProposal({ + header: makeL2BlockHeader(1, 100, 100), + signer: currentProposer, + txHashes: [TxHash.random(), TxHash.random()], // Include some tx hashes + }); + + // Mock epoch cache to return valid proposer (so only tx permission check fails) + (epochCache.getProposerAttesterAddressInCurrentOrNextSlot as jest.Mock).mockResolvedValue({ + currentSlot: 100n, + nextSlot: 101n, + currentProposer: currentProposer.address, + nextProposer: Fr.random(), + }); + + const result = await validatorWithTxsDisabled.validate(mockProposal); + expect(result).toBe(PeerErrorSeverity.MidToleranceError); + }); + + it('returns undefined if txs not permitted but proposal has no txHashes', async () => { + const currentProposer = Secp256k1Signer.random(); + const validatorWithTxsDisabled = new BlockProposalValidator(epochCache, { txsPermitted: false }); + + // Create a block proposal without transaction hashes + const mockProposal = makeBlockProposal({ + header: makeL2BlockHeader(1, 100, 100), + signer: currentProposer, + txHashes: [], // Empty tx hashes array + }); + + // Mock epoch cache for valid case + (epochCache.getProposerAttesterAddressInCurrentOrNextSlot as jest.Mock).mockResolvedValue({ + currentSlot: 100n, + nextSlot: 101n, + currentProposer: currentProposer.address, + nextProposer: Fr.random(), + }); + + const result = await validatorWithTxsDisabled.validate(mockProposal); + expect(result).toBeUndefined(); + }); + + it('returns undefined if txs permitted and proposal contains txHashes', async () => { + const currentProposer = Secp256k1Signer.random(); + // validator already created with txsPermitted = true in beforeEach + + // Create a block proposal with transaction hashes + const mockProposal = makeBlockProposal({ + header: makeL2BlockHeader(1, 100, 100), + signer: currentProposer, + txHashes: [TxHash.random(), TxHash.random()], // Include some tx hashes + }); + + // Mock epoch cache for valid case + (epochCache.getProposerAttesterAddressInCurrentOrNextSlot as jest.Mock).mockResolvedValue({ + currentSlot: 100n, + nextSlot: 101n, + currentProposer: currentProposer.address, + nextProposer: Fr.random(), + }); + + const result = await validator.validate(mockProposal); + expect(result).toBeUndefined(); + }); + }); }); diff --git a/yarn-project/p2p/src/msg_validators/block_proposal_validator/block_proposal_validator.ts b/yarn-project/p2p/src/msg_validators/block_proposal_validator/block_proposal_validator.ts index 8d9ce467d854..ab7bcf17c2cc 100644 --- a/yarn-project/p2p/src/msg_validators/block_proposal_validator/block_proposal_validator.ts +++ b/yarn-project/p2p/src/msg_validators/block_proposal_validator/block_proposal_validator.ts @@ -6,9 +6,11 @@ import { type BlockProposal, type P2PValidator, PeerErrorSeverity } from '@aztec export class BlockProposalValidator implements P2PValidator { private epochCache: EpochCacheInterface; private logger: Logger; + private txsPermitted: boolean; - constructor(epochCache: EpochCacheInterface) { + constructor(epochCache: EpochCacheInterface, opts: { txsPermitted: boolean }) { this.epochCache = epochCache; + this.txsPermitted = opts.txsPermitted; this.logger = createLogger('p2p:block_proposal_validator'); } @@ -21,6 +23,14 @@ export class BlockProposalValidator implements P2PValidator { return PeerErrorSeverity.MidToleranceError; } + // Check if transactions are permitted when the proposal contains transaction hashes + if (!this.txsPermitted && block.txHashes.length > 0) { + this.logger.debug( + `Penalizing peer for block proposal with ${block.txHashes.length} transaction(s) when transactions are not permitted`, + ); + return PeerErrorSeverity.MidToleranceError; + } + const { currentProposer, nextProposer, currentSlot, nextSlot } = await this.epochCache.getProposerAttesterAddressInCurrentOrNextSlot(); diff --git a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts index a9e5137d3b08..42b4b13948ce 100644 --- a/yarn-project/p2p/src/services/libp2p/libp2p_service.ts +++ b/yarn-project/p2p/src/services/libp2p/libp2p_service.ts @@ -170,7 +170,7 @@ export class LibP2PService extends ); this.attestationValidator = new AttestationValidator(epochCache); - this.blockProposalValidator = new BlockProposalValidator(epochCache); + this.blockProposalValidator = new BlockProposalValidator(epochCache, { txsPermitted: !config.disableTransactions }); this.gossipSubEventHandler = this.handleGossipSubEvent.bind(this); diff --git a/yarn-project/stdlib/src/interfaces/aztec-node-admin.test.ts b/yarn-project/stdlib/src/interfaces/aztec-node-admin.test.ts index a33126a57de1..45d94007a223 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node-admin.test.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node-admin.test.ts @@ -5,7 +5,7 @@ import { type Offense, OffenseType, type SlashPayloadRound } from '../slashing/i import { type AztecNodeAdmin, AztecNodeAdminApiSchema } from './aztec-node-admin.js'; import type { SequencerConfig } from './configs.js'; import type { ProverConfig } from './prover-client.js'; -import type { ValidatorClientConfig } from './server.js'; +import type { ValidatorClientFullConfig } from './server.js'; import type { SlasherConfig } from './slasher.js'; describe('AztecNodeAdminApiSchema', () => { @@ -126,7 +126,7 @@ class MockAztecNodeAdmin implements AztecNodeAdmin { ]); } getConfig(): Promise< - ValidatorClientConfig & SequencerConfig & ProverConfig & SlasherConfig & { maxTxPoolSize: number } + ValidatorClientFullConfig & SequencerConfig & ProverConfig & SlasherConfig & { maxTxPoolSize: number } > { return Promise.resolve({ realProofs: false, @@ -164,6 +164,7 @@ class MockAztecNodeAdmin implements AztecNodeAdmin { attestationPollingIntervalMs: 1000, validatorReexecute: true, validatorReexecuteDeadlineMs: 1000, + disableTransactions: false, }); } startSnapshotUpload(_location: string): Promise { diff --git a/yarn-project/stdlib/src/interfaces/aztec-node-admin.ts b/yarn-project/stdlib/src/interfaces/aztec-node-admin.ts index 3ce5fe13a5dc..eb8ddd322aea 100644 --- a/yarn-project/stdlib/src/interfaces/aztec-node-admin.ts +++ b/yarn-project/stdlib/src/interfaces/aztec-node-admin.ts @@ -9,7 +9,7 @@ import { type ArchiverSpecificConfig, ArchiverSpecificConfigSchema } from './arc import { type SequencerConfig, SequencerConfigSchema } from './configs.js'; import { type ProverConfig, ProverConfigSchema } from './prover-client.js'; import { type SlasherConfig, SlasherConfigSchema } from './slasher.js'; -import { ValidatorClientConfigSchema, type ValidatorClientFullConfig } from './validator.js'; +import { type ValidatorClientFullConfig, ValidatorClientFullConfigSchema } from './validator.js'; /** * Aztec node admin API. @@ -62,7 +62,7 @@ export type AztecNodeAdminConfig = ValidatorClientFullConfig & export const AztecNodeAdminConfigSchema = SequencerConfigSchema.merge(ProverConfigSchema) .merge(SlasherConfigSchema) - .merge(ValidatorClientConfigSchema) + .merge(ValidatorClientFullConfigSchema) .merge( ArchiverSpecificConfigSchema.pick({ archiverPollingIntervalMS: true, diff --git a/yarn-project/stdlib/src/interfaces/validator.ts b/yarn-project/stdlib/src/interfaces/validator.ts index 7c76af9b3b5a..fb7cc9b264fa 100644 --- a/yarn-project/stdlib/src/interfaces/validator.ts +++ b/yarn-project/stdlib/src/interfaces/validator.ts @@ -12,6 +12,7 @@ import { z } from 'zod'; import type { CommitteeAttestationsAndSigners } from '../block/index.js'; import type { CheckpointHeader } from '../rollup/checkpoint_header.js'; +import { AllowedElementSchema } from './allowed_element.js'; /** * Validator client configuration @@ -44,7 +45,13 @@ export interface ValidatorClientConfig { export type ValidatorClientFullConfig = ValidatorClientConfig & Pick & - Pick; + Pick & { + /** + * Whether transactions are disabled for this node + * @remarks This should match the property in P2PConfig. It's not picked from there to avoid circular dependencies. + */ + disableTransactions?: boolean; + }; export const ValidatorClientConfigSchema = z.object({ validatorAddresses: z.array(schemas.EthAddress).optional(), @@ -56,6 +63,13 @@ export const ValidatorClientConfigSchema = z.object({ alwaysReexecuteBlockProposals: z.boolean().optional(), }) satisfies ZodFor>; +export const ValidatorClientFullConfigSchema = ValidatorClientConfigSchema.extend({ + txPublicSetupAllowList: z.array(AllowedElementSchema).optional(), + broadcastInvalidBlockProposal: z.boolean().optional(), + slashBroadcastedInvalidBlockPenalty: schemas.BigInt, + disableTransactions: z.boolean().optional(), +}) satisfies ZodFor>; + export interface Validator { start(): Promise; updateConfig(config: Partial): void; diff --git a/yarn-project/validator-client/src/factory.ts b/yarn-project/validator-client/src/factory.ts index a1ab5cf6dd44..4d2308a7d0b0 100644 --- a/yarn-project/validator-client/src/factory.ts +++ b/yarn-project/validator-client/src/factory.ts @@ -24,7 +24,9 @@ export function createBlockProposalHandler( }, ) { const metrics = new ValidatorMetrics(deps.telemetry); - const blockProposalValidator = new BlockProposalValidator(deps.epochCache); + const blockProposalValidator = new BlockProposalValidator(deps.epochCache, { + txsPermitted: !config.disableTransactions, + }); return new BlockProposalHandler( deps.blockBuilder, deps.blockSource, diff --git a/yarn-project/validator-client/src/validator.test.ts b/yarn-project/validator-client/src/validator.test.ts index 9556401b12c7..1decb5886d56 100644 --- a/yarn-project/validator-client/src/validator.test.ts +++ b/yarn-project/validator-client/src/validator.test.ts @@ -38,7 +38,8 @@ import { type ValidatorClientConfig, validatorClientConfigMappings } from './con import { ValidatorClient } from './validator.js'; describe('ValidatorClient', () => { - let config: ValidatorClientConfig & Pick; + let config: ValidatorClientConfig & + Pick & { disableTransactions: boolean }; let validatorClient: ValidatorClient; let p2pClient: MockProxy; let blockSource: MockProxy; @@ -75,6 +76,7 @@ describe('ValidatorClient', () => { validatorReexecute: false, validatorReexecuteDeadlineMs: 6000, slashBroadcastedInvalidBlockPenalty: 1n, + disableTransactions: false, }; const keyStore: KeyStore = { diff --git a/yarn-project/validator-client/src/validator.ts b/yarn-project/validator-client/src/validator.ts index e82af6d6780b..3bd21631c3be 100644 --- a/yarn-project/validator-client/src/validator.ts +++ b/yarn-project/validator-client/src/validator.ts @@ -9,13 +9,7 @@ import { DateProvider } from '@aztec/foundation/timer'; import type { KeystoreManager } from '@aztec/node-keystore'; import type { P2P, PeerId, TxProvider } from '@aztec/p2p'; import { AuthRequest, AuthResponse, BlockProposalValidator, ReqRespSubProtocol } from '@aztec/p2p'; -import { - OffenseType, - type SlasherConfig, - WANT_TO_SLASH_EVENT, - type Watcher, - type WatcherEmitter, -} from '@aztec/slasher'; +import { OffenseType, WANT_TO_SLASH_EVENT, type Watcher, type WatcherEmitter } from '@aztec/slasher'; import type { AztecAddress } from '@aztec/stdlib/aztec-address'; import type { CommitteeAttestationsAndSigners, L2BlockSource } from '@aztec/stdlib/block'; import type { IFullNodeBlockBuilder, Validator, ValidatorClientFullConfig } from '@aztec/stdlib/interfaces/server'; @@ -30,7 +24,6 @@ import { EventEmitter } from 'events'; import type { TypedDataDefinition } from 'viem'; import { BlockProposalHandler, type BlockProposalValidationFailureReason } from './block_proposal_handler.js'; -import type { ValidatorClientConfig } from './config.js'; import { ValidationService } from './duties/validation_service.js'; import { NodeKeystoreAdapter } from './key_store/node_keystore_adapter.js'; import { ValidatorMetrics } from './metrics.js'; @@ -140,7 +133,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter) } static new( - config: ValidatorClientConfig & Pick, + config: ValidatorClientFullConfig, blockBuilder: IFullNodeBlockBuilder, epochCache: EpochCache, p2pClient: P2P, @@ -152,7 +145,9 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter) telemetry: TelemetryClient = getTelemetryClient(), ) { const metrics = new ValidatorMetrics(telemetry); - const blockProposalValidator = new BlockProposalValidator(epochCache); + const blockProposalValidator = new BlockProposalValidator(epochCache, { + txsPermitted: !config.disableTransactions, + }); const blockProposalHandler = new BlockProposalHandler( blockBuilder, blockSource,