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
29 changes: 27 additions & 2 deletions docs/docs-operate/operators/reference/changelog/v4.md
Original file line number Diff line number Diff line change
Expand Up @@ -87,13 +87,23 @@ The byte-based block size limit has been removed and replaced with field-based b
--maxDABlockGas <value> ($SEQ_MAX_DA_BLOCK_GAS)
```

**New:**
**New (proposer):**

```bash
--gasPerBlockAllocationMultiplier <value> ($SEQ_GAS_PER_BLOCK_ALLOCATION_MULTIPLIER)
--maxTxsPerCheckpoint <value> ($SEQ_MAX_TX_PER_CHECKPOINT)
```

**Migration**: Remove `SEQ_MAX_BLOCK_SIZE_IN_BYTES` from your configuration. Per-block L2 and DA gas budgets are now derived automatically as `(checkpointLimit / maxBlocks) * multiplier`, where the multiplier defaults to 2. You can still override `SEQ_MAX_L2_BLOCK_GAS` and `SEQ_MAX_DA_BLOCK_GAS` explicitly, but they will be capped at the checkpoint-level limits.
**New (validator):**

```bash
--validateMaxL2BlockGas <value> ($VALIDATOR_MAX_L2_BLOCK_GAS)
--validateMaxDABlockGas <value> ($VALIDATOR_MAX_DA_BLOCK_GAS)
--validateMaxTxsPerBlock <value> ($VALIDATOR_MAX_TX_PER_BLOCK)
--validateMaxTxsPerCheckpoint <value> ($VALIDATOR_MAX_TX_PER_CHECKPOINT)
```

**Migration**: Remove `SEQ_MAX_BLOCK_SIZE_IN_BYTES` from your configuration. Per-block L2 and DA gas budgets are now derived automatically as `(checkpointLimit / maxBlocks) * multiplier`, where the multiplier defaults to 2. You can still override `SEQ_MAX_L2_BLOCK_GAS` and `SEQ_MAX_DA_BLOCK_GAS` explicitly, but they will be capped at the checkpoint-level limits. Validators can now set independent per-block and per-checkpoint limits via the `VALIDATOR_` env vars; when not set, only checkpoint-level protocol limits are enforced.

### Setup phase allow list requires function selectors

Expand Down Expand Up @@ -201,6 +211,21 @@ P2P_RPC_PRICE_BUMP_PERCENTAGE=10 # default: 10 (percent)

Set to `0` to disable the percentage-based bump (still requires strictly higher fee).

### Validator-specific block limits

Validators can now enforce per-block and per-checkpoint limits independently from the sequencer (proposer) limits. This allows operators to accept proposals that exceed their own proposer settings, or to reject proposals that are too large even if the proposer's limits allow them.

**Configuration:**

```bash
VALIDATOR_MAX_L2_BLOCK_GAS=<value> # Max L2 gas per block for validation
VALIDATOR_MAX_DA_BLOCK_GAS=<value> # Max DA gas per block for validation
VALIDATOR_MAX_TX_PER_BLOCK=<value> # Max txs per block for validation
VALIDATOR_MAX_TX_PER_CHECKPOINT=<value> # Max txs per checkpoint for validation
```

When not set, no per-block limit is enforced for that dimension — only checkpoint-level protocol limits apply. These do not fall back to the `SEQ_` values.

### 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.
Expand Down
11 changes: 9 additions & 2 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,9 +346,16 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, Traceable {
// We'll accumulate sentinel watchers here
const watchers: Watcher[] = [];

// Create FullNodeCheckpointsBuilder for block proposal handling and tx validation
// Create FullNodeCheckpointsBuilder for block proposal handling and tx validation.
// Override maxTxsPerCheckpoint with the validator-specific limit if set.
const validatorCheckpointsBuilder = new FullNodeCheckpointsBuilder(
{ ...config, l1GenesisTime, slotDuration: Number(slotDuration), rollupManaLimit },
{
...config,
l1GenesisTime,
slotDuration: Number(slotDuration),
rollupManaLimit,
maxTxsPerCheckpoint: config.validateMaxTxsPerCheckpoint,
},
worldStateSynchronizer,
archiver,
dateProvider,
Expand Down
4 changes: 4 additions & 0 deletions yarn-project/foundation/src/config/env_var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,6 +278,10 @@ export type EnvVar =
| 'TRANSACTIONS_DISABLED'
| 'VALIDATOR_ATTESTATIONS_POLLING_INTERVAL_MS'
| 'VALIDATOR_DISABLED'
| 'VALIDATOR_MAX_DA_BLOCK_GAS'
| 'VALIDATOR_MAX_L2_BLOCK_GAS'
| 'VALIDATOR_MAX_TX_PER_BLOCK'
| 'VALIDATOR_MAX_TX_PER_CHECKPOINT'
| 'VALIDATOR_PRIVATE_KEYS'
| 'VALIDATOR_PRIVATE_KEY'
| 'VALIDATOR_REEXECUTE'
Expand Down
9 changes: 9 additions & 0 deletions yarn-project/p2p/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export interface P2PConfig
TxCollectionConfig,
TxFileStoreConfig,
Pick<SequencerConfig, 'blockDurationMs' | 'expectedBlockProposalsPerSlot' | 'maxTxsPerBlock'> {
/** Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set. */
validateMaxTxsPerBlock?: number;

/** A flag dictating whether the P2P subsystem should be enabled. */
p2pEnabled: boolean;

Expand Down Expand Up @@ -199,6 +202,12 @@ export interface P2PConfig
export const DEFAULT_P2P_PORT = 40400;

export const p2pConfigMappings: ConfigMappingsType<P2PConfig> = {
validateMaxTxsPerBlock: {
env: 'VALIDATOR_MAX_TX_PER_BLOCK',
description:
'Maximum transactions per block for validation. Overrides maxTxsPerBlock for gossip validation when set.',
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
},
p2pEnabled: {
env: 'P2P_ENABLED',
description: 'A flag dictating whether the P2P subsystem should be enabled.',
Expand Down
2 changes: 1 addition & 1 deletion yarn-project/p2p/src/services/libp2p/libp2p_service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ export class LibP2PService extends WithTracer implements P2PService {

const proposalValidatorOpts = {
txsPermitted: !config.disableTransactions,
maxTxsPerBlock: config.maxTxsPerBlock,
maxTxsPerBlock: config.validateMaxTxsPerBlock,
};
this.blockProposalValidator = new BlockProposalValidator(epochCache, proposalValidatorOpts);
this.checkpointProposalValidator = new CheckpointProposalValidator(epochCache, proposalValidatorOpts);
Expand Down
19 changes: 17 additions & 2 deletions yarn-project/stdlib/src/interfaces/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,22 @@ export type ValidatorClientConfig = ValidatorHASignerConfig & {

/** Agree to attest to equivocated checkpoint proposals (for testing purposes only) */
attestToEquivocatedProposals?: boolean;

/** Maximum L2 gas per block for validation. Proposals exceeding this limit are rejected. */
validateMaxL2BlockGas?: number;

/** Maximum DA gas per block for validation. Proposals exceeding this limit are rejected. */
validateMaxDABlockGas?: number;

/** Maximum transactions per block for validation. Proposals exceeding this limit are rejected. */
validateMaxTxsPerBlock?: number;

/** Maximum transactions per checkpoint for validation. Proposals exceeding this limit are rejected. */
validateMaxTxsPerCheckpoint?: number;
};

export type ValidatorClientFullConfig = ValidatorClientConfig &
Pick<SequencerConfig, 'txPublicSetupAllowListExtend' | 'broadcastInvalidBlockProposal' | 'maxTxsPerBlock'> &
Pick<SequencerConfig, 'txPublicSetupAllowListExtend' | 'broadcastInvalidBlockProposal'> &
Pick<
SlasherConfig,
'slashBroadcastedInvalidBlockPenalty' | 'slashDuplicateProposalPenalty' | 'slashDuplicateAttestationPenalty'
Expand All @@ -86,14 +98,17 @@ export const ValidatorClientConfigSchema = zodFor<Omit<ValidatorClientConfig, 'v
skipCheckpointProposalValidation: z.boolean().optional(),
skipPushProposedBlocksToArchiver: z.boolean().optional(),
attestToEquivocatedProposals: z.boolean().optional(),
validateMaxL2BlockGas: z.number().optional(),
validateMaxDABlockGas: z.number().optional(),
validateMaxTxsPerBlock: z.number().optional(),
validateMaxTxsPerCheckpoint: z.number().optional(),
}),
);

export const ValidatorClientFullConfigSchema = zodFor<Omit<ValidatorClientFullConfig, 'validatorPrivateKeys'>>()(
ValidatorClientConfigSchema.extend({
txPublicSetupAllowListExtend: z.array(AllowedElementSchema).optional(),
broadcastInvalidBlockProposal: z.boolean().optional(),
maxTxsPerBlock: z.number().optional(),
slashBroadcastedInvalidBlockPenalty: schemas.BigInt,
slashDuplicateProposalPenalty: schemas.BigInt,
slashDuplicateAttestationPenalty: schemas.BigInt,
Expand Down
6 changes: 5 additions & 1 deletion yarn-project/validator-client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ Per-block budgets prevent one block from consuming the entire checkpoint budget.

**Proposer**: `computeBlockLimits()` derives budgets at startup as `min(checkpointLimit, ceil(checkpointLimit / maxBlocks * multiplier))`, where `maxBlocks` comes from the timetable and `multiplier` defaults to 2. The multiplier greater than 1 allows early blocks to use more than their even share of the checkpoint budget, since different blocks hit different limit dimensions (L2 gas, DA gas, blob fields) — a strict even split would waste capacity. Operators can override via `SEQ_MAX_L2_BLOCK_GAS` / `SEQ_MAX_DA_BLOCK_GAS` / `SEQ_MAX_TX_PER_BLOCK` (capped at checkpoint limits). Per-block TX limits follow the same derivation pattern when `SEQ_MAX_TX_PER_CHECKPOINT` is set.

**Validator**: Does not enforce per-block gas budgets. Only checkpoint-level limits are checked, so that proposers can freely distribute capacity across blocks within a checkpoint.
**Validator**: Optionally enforces per-block limits via `VALIDATOR_MAX_L2_BLOCK_GAS`, `VALIDATOR_MAX_DA_BLOCK_GAS`, and `VALIDATOR_MAX_TX_PER_BLOCK`. When set, these are passed to `buildBlock` during re-execution and to `validateCheckpoint` for final validation. When unset, no per-block limit is enforced for that dimension (checkpoint-level protocol limits still apply). These are independent of the `SEQ_` vars so operators can tune proposer and validation limits separately.

**Checkpoint-level capping**: `CheckpointBuilder.capLimitsByCheckpointBudgets()` always runs before tx processing, capping per-block limits by `checkpointBudget - sum(used by prior blocks)` for all three gas dimensions and for transaction count (when `SEQ_MAX_TX_PER_CHECKPOINT` is set). This applies to both proposer and validator paths.

Expand All @@ -260,6 +260,10 @@ Per-block budgets prevent one block from consuming the entire checkpoint budget.
| `SEQ_MAX_TX_PER_BLOCK` | *none* | Per-block tx count. If `SEQ_MAX_TX_PER_CHECKPOINT` is set and per-block is not, derived as `ceil(checkpointLimit / maxBlocks * multiplier)`. |
| `SEQ_MAX_TX_PER_CHECKPOINT` | *none* | Total txs across all blocks in a checkpoint. When set, per-block tx limit is derived from it (unless explicitly overridden) and checkpoint-level capping is enforced. |
| `SEQ_GAS_PER_BLOCK_ALLOCATION_MULTIPLIER` | 2 | Multiplier for per-block budget computation. |
| `VALIDATOR_MAX_L2_BLOCK_GAS` | *none* | Per-block L2 gas limit for validation. Proposals exceeding this are rejected. |
| `VALIDATOR_MAX_DA_BLOCK_GAS` | *none* | Per-block DA gas limit for validation. Proposals exceeding this are rejected. |
| `VALIDATOR_MAX_TX_PER_BLOCK` | *none* | Per-block tx count limit for validation. Proposals exceeding this are rejected. |
| `VALIDATOR_MAX_TX_PER_CHECKPOINT` | *none* | Per-checkpoint tx count limit for validation. Proposals exceeding this are rejected. |

## Testing Patterns

Expand Down
7 changes: 7 additions & 0 deletions yarn-project/validator-client/src/block_proposal_handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import type { P2P, PeerId } from '@aztec/p2p';
import { BlockProposalValidator } from '@aztec/p2p/msg_validators';
import type { BlockData, L2Block, L2BlockSink, L2BlockSource } from '@aztec/stdlib/block';
import { getEpochAtSlot, getTimestampForSlot } from '@aztec/stdlib/epoch-helpers';
import { Gas } from '@aztec/stdlib/gas';
import type { ITxProvider, ValidatorClientFullConfig, WorldStateSynchronizer } from '@aztec/stdlib/interfaces/server';
import { type L1ToL2MessageSource, computeInHashFromL1ToL2Messages } from '@aztec/stdlib/messaging';
import type { BlockProposal } from '@aztec/stdlib/p2p';
Expand Down Expand Up @@ -480,9 +481,15 @@ export class BlockProposalHandler {

// Build the new block
const deadline = this.getReexecutionDeadline(slot, config);
const maxBlockGas =
this.config.validateMaxL2BlockGas !== undefined || this.config.validateMaxDABlockGas !== undefined
? new Gas(this.config.validateMaxDABlockGas ?? Infinity, this.config.validateMaxL2BlockGas ?? Infinity)
: undefined;
const result = await checkpointBuilder.buildBlock(txs, blockNumber, blockHeader.globalVariables.timestamp, {
deadline,
expectedEndState: blockHeader.state,
maxTransactions: this.config.validateMaxTxsPerBlock,
maxBlockGas,
});

const { block, failedTxs } = result;
Expand Down
20 changes: 20 additions & 0 deletions yarn-project/validator-client/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,26 @@ export const validatorClientConfigMappings: ConfigMappingsType<ValidatorClientCo
description: 'Agree to attest to equivocated checkpoint proposals (for testing purposes only)',
...booleanConfigHelper(false),
},
validateMaxL2BlockGas: {
env: 'VALIDATOR_MAX_L2_BLOCK_GAS',
description: 'Maximum L2 block gas for validation. Proposals exceeding this limit are rejected.',
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
},
validateMaxDABlockGas: {
env: 'VALIDATOR_MAX_DA_BLOCK_GAS',
description: 'Maximum DA block gas for validation. Proposals exceeding this limit are rejected.',
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
},
validateMaxTxsPerBlock: {
env: 'VALIDATOR_MAX_TX_PER_BLOCK',
description: 'Maximum transactions per block for validation. Proposals exceeding this limit are rejected.',
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
},
validateMaxTxsPerCheckpoint: {
env: 'VALIDATOR_MAX_TX_PER_CHECKPOINT',
description: 'Maximum transactions per checkpoint for validation. Proposals exceeding this limit are rejected.',
parseEnv: (val: string) => (val ? parseInt(val, 10) : undefined),
},
...validatorHASignerConfigMappings,
};

Expand Down
2 changes: 1 addition & 1 deletion yarn-project/validator-client/src/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export function createBlockProposalHandler(
const metrics = new ValidatorMetrics(deps.telemetry);
const blockProposalValidator = new BlockProposalValidator(deps.epochCache, {
txsPermitted: !config.disableTransactions,
maxTxsPerBlock: config.maxTxsPerBlock,
maxTxsPerBlock: config.validateMaxTxsPerBlock,
});
return new BlockProposalHandler(
deps.checkpointsBuilder,
Expand Down
8 changes: 5 additions & 3 deletions yarn-project/validator-client/src/validator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
const metrics = new ValidatorMetrics(telemetry);
const blockProposalValidator = new BlockProposalValidator(epochCache, {
txsPermitted: !config.disableTransactions,
maxTxsPerBlock: config.maxTxsPerBlock,
maxTxsPerBlock: config.validateMaxTxsPerBlock,
});
const blockProposalHandler = new BlockProposalHandler(
checkpointsBuilder,
Expand Down Expand Up @@ -771,8 +771,10 @@ export class ValidatorClient extends (EventEmitter as new () => WatcherEmitter)
try {
validateCheckpoint(computedCheckpoint, {
rollupManaLimit: this.checkpointsBuilder.getConfig().rollupManaLimit,
maxDABlockGas: undefined,
maxL2BlockGas: undefined,
maxDABlockGas: this.config.validateMaxDABlockGas,
maxL2BlockGas: this.config.validateMaxL2BlockGas,
maxTxsPerBlock: this.config.validateMaxTxsPerBlock,
maxTxsPerCheckpoint: this.config.validateMaxTxsPerCheckpoint,
});
} catch (err) {
this.log.warn(`Checkpoint validation failed: ${err}`, proposalInfo);
Expand Down
Loading