Skip to content
Open
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
12 changes: 12 additions & 0 deletions spartan/aztec-node/templates/_pod-template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,18 @@ spec:
- name: NETWORK
value: "{{ .Values.global.aztecNetwork }}"
{{- end }}
{{- if .Values.global.allowOverridingNetworkConfig }}
- name: ALLOW_OVERRIDING_NETWORK_CONFIG
value: "{{ .Values.global.allowOverridingNetworkConfig }}"
{{- end }}
{{- if .Values.global.aztecSlotDuration }}
- name: AZTEC_SLOT_DURATION
value: "{{ .Values.global.aztecSlotDuration }}"
{{- end }}
{{- if .Values.global.aztecEpochDuration }}
- name: AZTEC_EPOCH_DURATION
value: "{{ .Values.global.aztecEpochDuration }}"
{{- end }}
- name: NODE_OPTIONS
value: {{ join " " .Values.node.nodeJsOptions | quote }}
- name: AZTEC_PORT
Expand Down
5 changes: 5 additions & 0 deletions spartan/aztec-node/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ global:
aztecRollupVersion: "canonical"
# -- Network name - this is a predefined network - alpha-testnet, devnet
aztecNetwork: ""
# -- Allow this deployment's consensus-critical env vars to diverge from the generated defaults for aztecNetwork
allowOverridingNetworkConfig: ""
# -- Slot/epoch durations to pass through when a network's deployed rollup diverges from the generated defaults
aztecSlotDuration: ""
aztecEpochDuration: ""
# -- Custom network - (not recommended) - Only for custom testnet usecases, (must have deployed your own protocol contracts first)
customAztecNetwork:
l1ChainId:
Expand Down
3 changes: 3 additions & 0 deletions spartan/environments/devnet.env
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ USE_NETWORK_CONFIG=${USE_NETWORK_CONFIG:-false}

DEPLOY_INTERNAL_BOOTNODE=false

# devnet intentionally diverges from the generated devnet network defaults (36s slots vs 72s, committee size 1
# vs 48), so the consensus-config enforcement must permit these overrides instead of failing at startup.
ALLOW_OVERRIDING_NETWORK_CONFIG=true
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET=1
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO=1
AZTEC_SLOT_DURATION=36
Expand Down
9 changes: 9 additions & 0 deletions spartan/environments/network-defaults.yml
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,10 @@ _prodlike: &prodlike
SEQ_BUILD_CHECKPOINT_IF_EMPTY: true
# 6 second block times
SEQ_BLOCK_DURATION_MS: 6000
# Maximum number of block sub-slots per checkpoint; must equal what the default proposer budgets derive.
MAX_BLOCKS_PER_CHECKPOINT: 10
# Grace period for received checkpoint proposals to materialize locally (2 block durations).
CHECKPOINT_PROPOSAL_SYNC_GRACE_SECONDS: 12

#---------------------------------------------------------------------------
# Database Map Sizes (in KB)
Expand Down Expand Up @@ -212,6 +216,11 @@ networks:
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET: 1
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO: 1
AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS: 1
# Quorums made explicit to match the Solidity vm.envOr default (round_size / 2 + 1), unchanged behavior.
# Slashing: AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS (4) * AZTEC_EPOCH_DURATION (8) = 32 slots; 32 / 2 + 1 = 17.
AZTEC_SLASHING_QUORUM: 17
# Governance: AZTEC_GOVERNANCE_PROPOSER_ROUND_SIZE (300) slots; 300 / 2 + 1 = 151.
AZTEC_GOVERNANCE_PROPOSER_QUORUM: 151
# Network identity
L1_CHAIN_ID: 11155111 # Sepolia
# Genesis state
Expand Down
3 changes: 3 additions & 0 deletions spartan/environments/testnet.env
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ NAMESPACE=${NAMESPACE:-testnet}
NETWORK=testnet

REAL_VERIFIER=true
# testnet intentionally diverges from the generated testnet network defaults (slashing round size 2 epochs vs
# 4), so the consensus-config enforcement must permit these overrides instead of failing at startup.
ALLOW_OVERRIDING_NETWORK_CONFIG=true
AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE=48
AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE=48
AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN=10
Expand Down
9 changes: 9 additions & 0 deletions spartan/scripts/deploy_network.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,11 @@ BASE_STATE_PATH="${CLUSTER}/${NAMESPACE}"
# Don't try and retrieve contract addresses, instead allow deployed infra to read from network config
USE_NETWORK_CONFIG=${USE_NETWORK_CONFIG:-false}

# Allow this deployment's consensus-critical env vars to diverge from the generated network defaults that the
# aztec entrypoint enforces for a known NETWORK. Networks that intentionally override those defaults (e.g.
# devnet's 36s slots) set this to true in their env file; left unset it stays empty so enforcement is loud.
ALLOW_OVERRIDING_NETWORK_CONFIG=${ALLOW_OVERRIDING_NETWORK_CONFIG:-}

# GCP variables, unused if running on kind
GCP_PROJECT_ID=${GCP_PROJECT_ID:-testnet-440309}
GCP_REGION=${GCP_REGION:-us-west1-a}
Expand Down Expand Up @@ -466,6 +471,7 @@ AZTEC_PROVING_COST_PER_MANA = ${AZTEC_PROVING_COST_PER_MANA:-null}
AZTEC_EXIT_DELAY_SECONDS = ${AZTEC_EXIT_DELAY_SECONDS:-null}
ETHERSCAN_API_KEY = ${ETHERSCAN_API_KEY_TF}
NETWORK = $(tf_str "${NETWORK:-}")
ALLOW_OVERRIDING_NETWORK_CONFIG = $(tf_str "${ALLOW_OVERRIDING_NETWORK_CONFIG:-}")
JOB_NAME = "deploy-rollup-contracts"
JOB_BACKOFF_LIMIT = 3
JOB_TTL_SECONDS_AFTER_FINISHED = 3600
Expand Down Expand Up @@ -607,6 +613,9 @@ DEPLOY_INTERNAL_BOOTNODE = ${DEPLOY_INTERNAL_BOOTNODE:-true}
PROVER_REAL_PROOFS = ${PROVER_REAL_PROOFS}
TRANSACTIONS_DISABLED = ${TRANSACTIONS_DISABLED:-null}
NETWORK = $(tf_str "${NETWORK:-}")
ALLOW_OVERRIDING_NETWORK_CONFIG = $(tf_str "${ALLOW_OVERRIDING_NETWORK_CONFIG:-}")
AZTEC_SLOT_DURATION = ${AZTEC_SLOT_DURATION:-null}
AZTEC_EPOCH_DURATION = ${AZTEC_EPOCH_DURATION:-null}
STORE_SNAPSHOT_URL = ${STORE_SNAPSHOT_URL_TF}
BOT_RESOURCE_PROFILE = "${BOT_RESOURCE_PROFILE}"
BOT_MNEMONIC = "${LABS_INFRA_MNEMONIC}"
Expand Down
3 changes: 3 additions & 0 deletions spartan/terraform/deploy-aztec-infra/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,9 @@ locals {
"global.aztecImage.pullPolicy" = local.is_kind ? "IfNotPresent" : "Always"
"global.useGcloudLogging" = true
"global.aztecNetwork" = var.NETWORK
"global.allowOverridingNetworkConfig" = var.ALLOW_OVERRIDING_NETWORK_CONFIG
"global.aztecSlotDuration" = var.AZTEC_SLOT_DURATION
"global.aztecEpochDuration" = var.AZTEC_EPOCH_DURATION
"global.customAztecNetwork.registryContractAddress" = var.REGISTRY_CONTRACT_ADDRESS
"global.customAztecNetwork.feeAssetHandlerContractAddress" = var.FEE_ASSET_HANDLER_CONTRACT_ADDRESS
"global.customAztecNetwork.l1ChainId" = var.L1_CHAIN_ID
Expand Down
18 changes: 18 additions & 0 deletions spartan/terraform/deploy-aztec-infra/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -549,6 +549,24 @@ variable "NETWORK" {
nullable = true
}

variable "ALLOW_OVERRIDING_NETWORK_CONFIG" {
description = "Allow consensus-critical env vars to diverge from the generated network defaults for NETWORK"
type = string
nullable = true
}

variable "AZTEC_SLOT_DURATION" {
description = "Aztec slot duration; passed to nodes so they match a rollup deployed with a non-default value"
type = string
nullable = true
}

variable "AZTEC_EPOCH_DURATION" {
description = "Aztec epoch duration; passed to nodes so they match a rollup deployed with a non-default value"
type = string
nullable = true
}

variable "STORE_SNAPSHOT_URL" {
description = "Location to store snapshots in"
type = string
Expand Down
1 change: 1 addition & 0 deletions spartan/terraform/deploy-rollup-contracts/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ locals {
# Environment variables for the container (omit keys with null values)
env_vars = { for k, v in {
NETWORK = var.NETWORK
ALLOW_OVERRIDING_NETWORK_CONFIG = var.ALLOW_OVERRIDING_NETWORK_CONFIG
AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET = var.AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET
AZTEC_LAG_IN_EPOCHS_FOR_RANDAO = var.AZTEC_LAG_IN_EPOCHS_FOR_RANDAO
AZTEC_SLOT_DURATION = var.AZTEC_SLOT_DURATION
Expand Down
6 changes: 6 additions & 0 deletions spartan/terraform/deploy-rollup-contracts/variables.tf
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,12 @@ variable "NETWORK" {
nullable = true
}

variable "ALLOW_OVERRIDING_NETWORK_CONFIG" {
description = "Allow consensus-critical env vars to diverge from the generated network defaults for NETWORK"
type = string
nullable = true
}

variable "VERIFY_CONTRACTS" {
description = "Verify contracts on Etherscan"
type = bool
Expand Down
41 changes: 33 additions & 8 deletions yarn-project/aztec-node/src/aztec-node/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ import {
} from '@aztec/stdlib/messaging';
import type { CheckpointAttestation } from '@aztec/stdlib/p2p';
import type { Offense } from '@aztec/stdlib/slashing';
import { DEFAULT_MIN_BLOCK_DURATION } from '@aztec/stdlib/timetable';
import type { NullifierLeafPreimage, PublicDataTreeLeafPreimage } from '@aztec/stdlib/trees';
import { MerkleTreeId, NullifierMembershipWitness, PublicDataWitness } from '@aztec/stdlib/trees';
import {
Expand Down Expand Up @@ -590,9 +589,10 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
Object.assign(config, l1ContractsAddresses);

const rollupContract = new RollupContract(publicClient, config.rollupAddress.toString());
const [l1GenesisTime, slotDuration, rollupVersionFromRollup, rollupManaLimit] = await Promise.all([
const [l1GenesisTime, slotDuration, epochDuration, rollupVersionFromRollup, rollupManaLimit] = await Promise.all([
rollupContract.getL1GenesisTime(),
rollupContract.getSlotDuration(),
rollupContract.getEpochDuration(),
rollupContract.getVersion(),
rollupContract.getManaLimit().then(Number),
] as const);
Expand All @@ -615,14 +615,13 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
// Track started resources so we can clean up on partial failure during node creation.
const started: { stop?(): Promise<void> | void }[] = [];
try {
// Default the consensus materialization grace from the block build duration when unset, so the archiver
// gives received checkpoint proposals roughly two build slots to validate and enter proposed state.
config.checkpointProposalSyncGraceSeconds ??=
config.blockDurationMs !== undefined
? 2 * Math.ceil(config.blockDurationMs / 1000)
: 2 * DEFAULT_MIN_BLOCK_DURATION;
config.skipOrphanProposedBlockPruning ||= !!config.useAutomineSequencer;

AztecNodeService.checkConfigMatchesRollup(config, {
slotDuration: Number(slotDuration),
epochDuration: Number(epochDuration),
});

// Create world-state first so we can retrieve the initial header before constructing the archiver.
const nativeWs = await createWorldState(config, options.genesis);
const initialHeader = nativeWs.getInitialHeader();
Expand Down Expand Up @@ -1053,6 +1052,32 @@ export class AztecNodeService implements AztecNode, AztecNodeAdmin, AztecNodeDeb
}
}

/**
* Verifies the node's configured L1 timing matches the rollup contract it is pointed at, for the fields the
* node's own config carries. Each comparison is guarded against an undefined config value, so a config that
* does not carry a field is not checked. Throws a single error listing every mismatch. Runs in the shared
* startup path for every node role.
*/
private static checkConfigMatchesRollup(
config: AztecNodeConfig,
rollup: { slotDuration: number; epochDuration: number },
): void {
const mismatches: string[] = [];
if (config.aztecSlotDuration !== undefined && config.aztecSlotDuration !== rollup.slotDuration) {
mismatches.push(`aztecSlotDuration is ${config.aztecSlotDuration} but the rollup reports ${rollup.slotDuration}`);
}
if (config.aztecEpochDuration !== undefined && config.aztecEpochDuration !== rollup.epochDuration) {
mismatches.push(
`aztecEpochDuration is ${config.aztecEpochDuration} but the rollup reports ${rollup.epochDuration}`,
);
}
if (mismatches.length > 0) {
throw new Error(
`The node's configured L1 timing does not match the rollup contract it is pointed at: ${mismatches.join('; ')}`,
);
}
}

/**
* Returns the sequencer client instance.
* @returns The sequencer client instance.
Expand Down
91 changes: 91 additions & 0 deletions yarn-project/cli/src/config/chain_l2_config.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
import { getConsensusConfigFromNetworkEnv, validateNetworkConsensusConfig } from '@aztec/stdlib/config';
import {
DEFAULT_CHECKPOINT_PROPOSAL_INIT_TIME,
DEFAULT_CHECKPOINT_PROPOSAL_PREPARE_TIME,
DEFAULT_MIN_BLOCK_DURATION,
DEFAULT_P2P_PROPAGATION_TIME,
ProposerTimetable,
} from '@aztec/stdlib/timetable';

import { type ConsensusComplete, enrichEnvironmentWithChainName } from './chain_l2_config.js';
import { devnetConfig, mainnetConfig, testnetConfig } from './generated/networks.js';

const generatedConfigs = {
devnet: devnetConfig,
testnet: testnetConfig,
mainnet: mainnetConfig,
} as const;

describe('generated network configs', () => {
for (const [name, config] of Object.entries(generatedConfigs)) {
describe(name, () => {
it('passes consensus config validation', () => {
expect(validateNetworkConsensusConfig(getConsensusConfigFromNetworkEnv(config))).toEqual([]);
});

it('declares MAX_BLOCKS_PER_CHECKPOINT equal to what the default proposer budgets derive', () => {
const consensus = getConsensusConfigFromNetworkEnv(config);
const computed = new ProposerTimetable({
l1Constants: {
l1GenesisTime: 0n,
slotDuration: consensus.aztecSlotDuration,
ethereumSlotDuration: consensus.ethereumSlotDuration,
},
blockDuration: consensus.blockDurationMs / 1000,
minBlockDuration: DEFAULT_MIN_BLOCK_DURATION,
p2pPropagationTime: DEFAULT_P2P_PROPAGATION_TIME,
checkpointProposalPrepareTime: DEFAULT_CHECKPOINT_PROPOSAL_PREPARE_TIME,
checkpointProposalInitTime: DEFAULT_CHECKPOINT_PROPOSAL_INIT_TIME,
checkpointProposalSyncGrace: consensus.checkpointProposalSyncGraceSeconds,
}).getMaxBlocksPerCheckpoint();
expect(computed).toBe(config.MAX_BLOCKS_PER_CHECKPOINT);
});
});
}
});

// Compile-time gate: tsc itself enforces that a complete config satisfies ConsensusComplete while a config
// missing a consensus-critical var does not. If the negative assertion ever stops erroring, the unused
// `@ts-expect-error` directive turns into a build error, flagging that the gate has silently weakened. This
// function is never called; its name is `_`-prefixed so lint ignores it as an intentionally unused binding.
function _consensusCompleteCompileGate() {
const _complete: ConsensusComplete = mainnetConfig satisfies ConsensusComplete;

const { MAX_BLOCKS_PER_CHECKPOINT: _dropped, ...incomplete } = mainnetConfig;
// @ts-expect-error a config missing a consensus-critical var must not satisfy ConsensusComplete
const _incomplete: ConsensusComplete = incomplete satisfies ConsensusComplete;
}

describe('enrichEnvironmentWithChainName', () => {
const originalEnv = { ...process.env };

beforeEach(() => {
process.env = { ...originalEnv };
delete process.env.SEQ_BLOCK_DURATION_MS;
delete process.env.ALLOW_OVERRIDING_NETWORK_CONFIG;
delete process.env.DATA_DIRECTORY;
});

afterEach(() => {
process.env = { ...originalEnv };
});

it('throws when a consensus-critical env var conflicts with the network config', () => {
process.env.SEQ_BLOCK_DURATION_MS = '3000';
expect(() => enrichEnvironmentWithChainName('testnet')).toThrow(/SEQ_BLOCK_DURATION_MS/);
});

it('keeps the operator value and continues when ALLOW_OVERRIDING_NETWORK_CONFIG is set', () => {
process.env.SEQ_BLOCK_DURATION_MS = '3000';
process.env.ALLOW_OVERRIDING_NETWORK_CONFIG = '1';
expect(() => enrichEnvironmentWithChainName('testnet')).not.toThrow();
expect(process.env.SEQ_BLOCK_DURATION_MS).toBe('3000');
});

it('canonicalizes a numerically-equal consensus value to the network form', () => {
// '6e3' equals the network's SEQ_BLOCK_DURATION_MS=6000 numerically; enrichment applies the canonical form.
process.env.SEQ_BLOCK_DURATION_MS = '6e3';
enrichEnvironmentWithChainName('testnet');
expect(process.env.SEQ_BLOCK_DURATION_MS).toBe('6000');
});
});
15 changes: 14 additions & 1 deletion yarn-project/cli/src/config/chain_l2_config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import type { NetworkNames } from '@aztec/foundation/config';
import { createLogger } from '@aztec/foundation/log';
import { type ConsensusEnvVar, checkConsensusEnvOverrides } from '@aztec/stdlib/config';

import path from 'path';

Expand All @@ -12,6 +14,12 @@ const NetworkConfigs: Partial<Record<NetworkNames, NetworkConfigEnv>> = {
mainnet: mainnetConfig,
};

/** Every generated network config must define every consensus-critical env var. */
export type ConsensusComplete = Record<ConsensusEnvVar, string | number | boolean>;
({ devnetConfig, testnetConfig, mainnetConfig }) satisfies Record<string, ConsensusComplete>;

const log = createLogger('cli:chain_l2_config');

function enrichEnvironmentWithNetworkConfig(config: NetworkConfigEnv): void {
for (const [key, value] of Object.entries(config)) {
if (process.env[key] === undefined && value !== undefined) {
Expand All @@ -31,7 +39,9 @@ function getDefaultDataDir(networkName: NetworkNames): string {
* and DefaultSlasherConfig (which match the 'defaults' section of defaults.yml).
*
* For deployed networks: applies network configuration from generated defaults.yml,
* merging base defaults with network-specific overrides.
* merging base defaults with network-specific overrides. Before merging, enforces that operators have not
* overridden any consensus-critical env var with a value diverging from the network config (throwing unless
* ALLOW_OVERRIDING_NETWORK_CONFIG is set), so all nodes of a network agree on consensus-critical values.
*
* @param networkName - The network name
*/
Expand All @@ -49,6 +59,9 @@ export function enrichEnvironmentWithChainName(networkName: NetworkNames) {
const configKey = /^v\d+-devnet-\d+$/.test(networkName) ? 'devnet' : networkName;
const generatedConfig = NetworkConfigs[configKey];
if (generatedConfig) {
// The check is pure; this layer owns env mutation, so apply its canonical writes before enriching.
const canonical = checkConsensusEnvOverrides(generatedConfig, process.env, msg => log.warn(msg));
Object.assign(process.env, canonical);
enrichEnvironmentWithNetworkConfig(generatedConfig);
}

Expand Down
14 changes: 14 additions & 0 deletions yarn-project/constants/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ import {
// Typescript-land-only constants
export const SPONSORED_FPC_SALT = BigInt(0);

/**
* Network-minimum per-block budget multiplier for L2 gas / tx count. Operators may configure a higher value,
* but never lower: a node admitting txs under a smaller multiplier would accept work it can never pack.
*/
export const MIN_PER_BLOCK_ALLOCATION_MULTIPLIER = 1.2;

/**
* Network-minimum per-block budget multiplier for DA gas / blob fields. See
* {@link MIN_PER_BLOCK_ALLOCATION_MULTIPLIER}. The DA-specific operator knob and its runtime enforcement land
* with the network tx admission limits (#23947); until then this constant is documentation of the network
* minimum only.
*/
export const MIN_PER_BLOCK_DA_ALLOCATION_MULTIPLIER = 1.5;

/**
* The most DA gas a single tx can consume. A tx's effects cannot encode more than
* MAX_TX_BLOB_DATA_SIZE_IN_FIELDS fields in a blob, each costing DA_GAS_PER_FIELD, so this is the maximum
Expand Down
1 change: 1 addition & 0 deletions yarn-project/foundation/src/config/env_var.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ export type EnvVar =
| 'MNEMONIC'
| 'NETWORK'
| 'NETWORK_CONFIG_LOCATION'
| 'ALLOW_OVERRIDING_NETWORK_CONFIG'
| 'USE_GCLOUD_LOGGING'
| 'OTEL_EXPORTER_OTLP_METRICS_ENDPOINT'
| 'OTEL_EXPORTER_OTLP_TRACES_ENDPOINT'
Expand Down
Loading
Loading