From 560453be535420b74d7a297c07f8886b2e931f52 Mon Sep 17 00:00:00 2001 From: Facundo Date: Mon, 6 Jul 2026 06:40:02 -0300 Subject: [PATCH 1/5] refactor: move L1 network defaults source of truth to l1-contracts (#24434) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Prep for the foundation / node / fairies repo split: make `l1-contracts` own the canonical L1 contract configuration defaults instead of deriving them from `spartan/environments/network-defaults.yml`. The new source of truth is `l1-contracts/scripts/network-defaults.json`, published via `@aztec/l1-artifacts` and consumed directly by both the Solidity deploy scripts and `@aztec/ethereum`. This breaks the dependency to `spartan <- l1-contracts`. Worth noting - The defaults in `@aztec/ethereum` read from the JSON in `@aztec/l1-artifacts` and NOT from the yml files. - The yml files are for kubernetes deployment and are separately maintained. - The local network config stays the same. The ultimate fallback is to the defaults in `@aztec/ethereum` which as mentioned before now come from the JSON. ## Changes - Add `l1-contracts/scripts/network-defaults.json` as the committed, canonical set of L1 contract defaults. This replaces the gitignored `l1-contracts/generated/default.json` that used to be generated from the yaml at bootstrap. It also adds the parameters `RollupConfiguration.sol` reads that were previously only defaulted in Solidity (`AZTEC_SLASHING_QUORUM`, `AZTEC_ENTRY_QUEUE_*`). - Publish the JSON through `@aztec/l1-artifacts` (new package export + copied into the bundled foundry subtree by `copy-foundry-artifacts.sh`). - `@aztec/ethereum`'s config now imports the JSON from `@aztec/l1-artifacts` directly (`import … with { type: 'json' }`). Deleted the generated `l1-contracts-defaults.ts` and its generator (`scripts/generate.sh`), and dropped `ethereum` from the yarn-project bootstrap generate step. - `l1-contracts/scripts/load_network_defaults.sh` now reads the JSON — network-agnostic, no network argument, no yaml. `spartan`'s `deploy_rollup_upgrade.sh` switches to spartan's own network-aware yaml loader for its per-network needs. - `foundry.toml` fs_permissions and the deploy `.t.sol` scripts read from `scripts/network-defaults.json`. - The `l1-contracts` section of `network-defaults.yml` is now a hand-synced mirror of the JSON (it still feeds the `slasher` / `cli` generators and the `networks` presets). Docs updated: `spartan/CLAUDE.md`, the yaml header, and the governance rollup-upgrade tutorial. - `RollupConfiguration.sol` now reads parameters via `vm.envUint/envBool` (required) instead of `vm.envOr` (silent Solidity fallback). Consequently the deploy-script tests (`DeployAztecL1Contracts.t.sol`, `DeployRollupForUpgrade.t.sol`) now forward those env vars from the JSON in `setUp()`. ## Behaviour change in l1-contracts tests The tests now use the values in the network-agnostic JSON and therefore do NOT use the same values as before (devnet). ## Testing - `l1-contracts`: `forge test` passes for both deploy suites — `DeployAztecL1ContractsTest::test_SmokeTest` and `DeployRollupForUpgradeTest::test_DeployThenUpgrade`. - `@aztec/ethereum`: builds cleanly; `DefaultL1ContractsConfig` loads from the JSON at runtime with exact `bigint` threshold values (no precision loss). Closes https://linear.app/aztec-labs/issue/A-1358/decouple-spartan-network-configuration-and-l1-contracts --- .../testing_governance_rollup_upgrade.md | 1 - l1-contracts/.rebuild_patterns | 1 + l1-contracts/bootstrap.sh | 3 - l1-contracts/foundry.toml | 4 +- l1-contracts/l1-artifacts/package.json | 1 + .../scripts/copy-foundry-artifacts.sh | 4 +- .../script/deploy/RollupConfiguration.sol | 12 ++-- l1-contracts/scripts/load_network_defaults.sh | 21 ++---- l1-contracts/scripts/network-defaults.json | 34 +++++++++ l1-contracts/scripts/stress_test_deploy.sh | 2 +- l1-contracts/scripts/test_rollup_upgrade.sh | 6 +- .../test/script/DeployAztecL1Contracts.t.sol | 19 ++++- .../test/script/DeployRollupForUpgrade.t.sol | 19 ++++- spartan/CLAUDE.md | 41 ++++++++--- spartan/environments/network-defaults.yml | 71 ++++++++++--------- spartan/scripts/deploy_rollup_upgrade.sh | 2 +- spartan/scripts/load_network_defaults.sh | 28 ++++++++ yarn-project/bootstrap.sh | 1 - .../end-to-end/src/e2e_p2p/add_rollup.test.ts | 5 ++ yarn-project/ethereum/package.json | 1 - yarn-project/ethereum/scripts/generate.sh | 32 --------- yarn-project/ethereum/src/config.ts | 41 ++++++++++- .../ethereum/src/deploy_aztec_l1_contracts.ts | 5 ++ yarn-project/ethereum/src/queries.ts | 6 ++ yarn-project/foundation/src/config/env_var.ts | 5 ++ 25 files changed, 250 insertions(+), 115 deletions(-) create mode 100644 l1-contracts/scripts/network-defaults.json create mode 100755 spartan/scripts/load_network_defaults.sh delete mode 100755 yarn-project/ethereum/scripts/generate.sh diff --git a/docs/docs-developers/docs/tutorials/testing_governance_rollup_upgrade.md b/docs/docs-developers/docs/tutorials/testing_governance_rollup_upgrade.md index d57fa538d4d6..9a31d9468348 100644 --- a/docs/docs-developers/docs/tutorials/testing_governance_rollup_upgrade.md +++ b/docs/docs-developers/docs/tutorials/testing_governance_rollup_upgrade.md @@ -79,7 +79,6 @@ cp ~/.svm/0.8.30/solc-0.8.30 ./solc-0.8.30 # Copy the HonkVerifier to the generated directory (required for build) mkdir -p generated cp src/HonkVerifier.sol generated/HonkVerifier.sol -echo '{}' > generated/default.json # Remove zkpassport-dependent files (not needed for rollup deployment) rm -f src/mock/StakingAssetHandler.sol diff --git a/l1-contracts/.rebuild_patterns b/l1-contracts/.rebuild_patterns index 2a2127a3f324..4e2f9749eee5 100644 --- a/l1-contracts/.rebuild_patterns +++ b/l1-contracts/.rebuild_patterns @@ -7,6 +7,7 @@ ^l1-contracts/foundry.lock ^l1-contracts/package.json ^l1-contracts/scripts/forge_broadcast.js +^l1-contracts/scripts/network-defaults.json ^l1-contracts/l1-artifacts/scripts/.* ^l1-contracts/l1-artifacts/package.json ^l1-contracts/l1-artifacts/tsconfig.json diff --git a/l1-contracts/bootstrap.sh b/l1-contracts/bootstrap.sh index e81f39cd7cd8..e5dab22bf352 100755 --- a/l1-contracts/bootstrap.sh +++ b/l1-contracts/bootstrap.sh @@ -87,9 +87,6 @@ function build_verifier { if ! cache_download $artifact; then mkdir -p generated - # Generate network defaults from spartan (canonical source of truth for config values) - yq -o json 'explode(.) | ."l1-contracts" // {}' ../spartan/environments/network-defaults.yml > generated/default.json - # Copy from noir-projects. Bootstrap must have ran in noir-projects. local rollup_verifier_path=../noir-projects/noir-protocol-circuits/target/keys/rollup_root_verifier.sol if [ -f "$rollup_verifier_path" ]; then diff --git a/l1-contracts/foundry.toml b/l1-contracts/foundry.toml index c2e78b80f0f9..66413819bfa5 100644 --- a/l1-contracts/foundry.toml +++ b/l1-contracts/foundry.toml @@ -61,8 +61,8 @@ fs_permissions = [ { access = "read", path = "./test/fixtures/boosted_rewards/shares.json" }, { access = "read", path = "./test/fixtures/boosted_rewards/activity_scores.json" }, { access = "read", path = "./script/registration_data.json" }, - # Network defaults - copied from spartan/environments/default.json by bootstrap.sh - { access = "read", path = "./generated/default.json" } + # Network defaults - the canonical L1 contract config values (source of truth). + { access = "read", path = "./scripts/network-defaults.json" } ] no_match_contract = "(ScreamAndShoutTest|UniswapPortalTest|MerkleCheck)" diff --git a/l1-contracts/l1-artifacts/package.json b/l1-contracts/l1-artifacts/package.json index 9dfc02b904f5..f631b686965b 100644 --- a/l1-contracts/l1-artifacts/package.json +++ b/l1-contracts/l1-artifacts/package.json @@ -3,6 +3,7 @@ "version": "0.1.0", "type": "module", "exports": { + "./network-defaults.json": "./l1-contracts/scripts/network-defaults.json", "./*": "./dest/*.js", ".": "./dest/index.js" }, diff --git a/l1-contracts/l1-artifacts/scripts/copy-foundry-artifacts.sh b/l1-contracts/l1-artifacts/scripts/copy-foundry-artifacts.sh index c3213e34135a..4d1b06e218c9 100755 --- a/l1-contracts/l1-artifacts/scripts/copy-foundry-artifacts.sh +++ b/l1-contracts/l1-artifacts/scripts/copy-foundry-artifacts.sh @@ -24,9 +24,11 @@ mkdir -p "l1-contracts/test/script" cp -p "$src/test/shouting.t.sol" "l1-contracts/test/" cp -p "$src"/test/script/*.sol "l1-contracts/test/script/" cp -p "$src"/{foundry.toml,foundry.lock,package.json,solc-*} "l1-contracts/" -# Copy the forge broadcast wrapper (now a plain .js source file). +# Copy the forge broadcast wrapper (now a plain .js source file) and the network defaults +# (read at deploy time via foundry fs_permissions / vm.readFile). mkdir -p "l1-contracts/scripts" cp -p "$src/scripts/forge_broadcast.js" "l1-contracts/scripts/" +cp -p "$src/scripts/network-defaults.json" "l1-contracts/scripts/" abs_dest=$(pwd)/l1-contracts # Keep only the foundry relevant files from lib (cd "$src" && find lib \( -name "*.sol" -o -name "remappings.txt" -o -name "foundry.toml" \) -exec cp --parents -t "$abs_dest" {} +) diff --git a/l1-contracts/script/deploy/RollupConfiguration.sol b/l1-contracts/script/deploy/RollupConfiguration.sol index 644c9a10b208..21ba8495c1b0 100644 --- a/l1-contracts/script/deploy/RollupConfiguration.sol +++ b/l1-contracts/script/deploy/RollupConfiguration.sol @@ -76,11 +76,11 @@ contract RollupConfiguration is IRollupConfiguration, Test { function getStakingQueueConfiguration() external view returns (StakingQueueConfig memory) { return StakingQueueConfig({ - bootstrapValidatorSetSize: vm.envOr("AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE", uint256(0)), - bootstrapFlushSize: vm.envOr("AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE", uint256(0)), - normalFlushSizeMin: vm.envOr("AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN", uint256(48)), - normalFlushSizeQuotient: vm.envOr("AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT", uint256(2)), - maxQueueFlushSize: vm.envOr("AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE", uint256(48)) + bootstrapValidatorSetSize: vm.envUint("AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE"), + bootstrapFlushSize: vm.envUint("AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE"), + normalFlushSizeMin: vm.envUint("AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN"), + normalFlushSizeQuotient: vm.envUint("AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT"), + maxQueueFlushSize: vm.envUint("AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE") }); } @@ -118,7 +118,7 @@ contract RollupConfiguration is IRollupConfiguration, Test { config.slashingExecutionDelayInRounds = vm.envUint("AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS"); config.slashAmounts = _getSlashAmounts(); config.slashingOffsetInRounds = _getSlashingOffset(); - config.slasherEnabled = vm.envOr("AZTEC_SLASHER_ENABLED", true); + config.slasherEnabled = vm.envBool("AZTEC_SLASHER_ENABLED"); config.slashingVetoer = vm.envAddress("AZTEC_SLASHING_VETOER"); config.slashingDisableDuration = vm.envUint("AZTEC_SLASHING_DISABLE_DURATION"); config.manaTarget = vm.envUint("AZTEC_MANA_TARGET"); diff --git a/l1-contracts/scripts/load_network_defaults.sh b/l1-contracts/scripts/load_network_defaults.sh index b9f16bcc8dfb..76bcc1307e9c 100755 --- a/l1-contracts/scripts/load_network_defaults.sh +++ b/l1-contracts/scripts/load_network_defaults.sh @@ -1,28 +1,21 @@ #!/usr/bin/env bash set -euo pipefail -# Load L1 contract defaults from network-defaults.yml for a given network. -# Exports AZTEC_* and ETHEREUM_* env vars with YAML anchor inheritance resolved. +# Load the canonical L1 contract defaults from network-defaults.json. +# Exports every key in the file as an env var (all AZTEC_* / ETHEREUM_*). # # Usage: -# source ./scripts/load_network_defaults.sh -# -# Networks: mainnet, testnet, devnet +# source ./scripts/load_network_defaults.sh -network="${1:?Usage: $0 }" script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" -network_defaults="${script_dir}/../../spartan/environments/network-defaults.yml" +network_defaults="${script_dir}/network-defaults.json" if [[ ! -f "$network_defaults" ]]; then - echo "ERROR: network-defaults.yml not found at $network_defaults" >&2 + echo "ERROR: network-defaults.json not found at $network_defaults" >&2 exit 1 fi -# explode(.) resolves YAML anchors (<<: *prodlike inheritance) -# Output as props, filter comments, normalize spacing +# jq preserves large integer literals verbatim (e.g. 100000000000000000000), which vm.envUint requires. while IFS='=' read -r key value; do export "$key"="$value" -done < <(yq -o=props "explode(.) | .networks.$network | with_entries(select(.key | test(\"^AZTEC_|^ETHEREUM_\")))" "$network_defaults" \ - | grep -v '^#' \ - | grep -v '^$' \ - | sed 's/ = /=/') +done < <(jq -r 'to_entries[] | "\(.key)=\(.value)"' "$network_defaults") diff --git a/l1-contracts/scripts/network-defaults.json b/l1-contracts/scripts/network-defaults.json new file mode 100644 index 000000000000..3d04bcf9ab46 --- /dev/null +++ b/l1-contracts/scripts/network-defaults.json @@ -0,0 +1,34 @@ +{ + "ETHEREUM_SLOT_DURATION": 12, + "AZTEC_SLOT_DURATION": 72, + "AZTEC_EPOCH_DURATION": 32, + "AZTEC_TARGET_COMMITTEE_SIZE": 48, + "AZTEC_LAG_IN_EPOCHS_FOR_VALIDATOR_SET": 2, + "AZTEC_LAG_IN_EPOCHS_FOR_RANDAO": 2, + "AZTEC_ACTIVATION_THRESHOLD": 100000000000000000000, + "AZTEC_EJECTION_THRESHOLD": 50000000000000000000, + "AZTEC_LOCAL_EJECTION_THRESHOLD": 98000000000000000000, + "AZTEC_EXIT_DELAY_SECONDS": 172800, + "AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE": 0, + "AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE": 0, + "AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN": 48, + "AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT": 2, + "AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE": 48, + "AZTEC_INBOX_LAG": 1, + "AZTEC_PROOF_SUBMISSION_EPOCHS": 1, + "AZTEC_MANA_TARGET": 100000000, + "AZTEC_PROVING_COST_PER_MANA": 100, + "AZTEC_INITIAL_ETH_PER_FEE_ASSET": 10000000, + "AZTEC_SLASHER_ENABLED": true, + "AZTEC_SLASHING_ROUND_SIZE_IN_EPOCHS": 4, + "AZTEC_SLASHING_QUORUM": 65, + "AZTEC_SLASHING_LIFETIME_IN_ROUNDS": 5, + "AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS": 0, + "AZTEC_SLASHING_OFFSET_IN_ROUNDS": 2, + "AZTEC_SLASHING_VETOER": "0x0000000000000000000000000000000000000000", + "AZTEC_SLASHING_DISABLE_DURATION": 432000, + "AZTEC_SLASH_AMOUNT_SMALL": 10000000000000000000, + "AZTEC_SLASH_AMOUNT_MEDIUM": 20000000000000000000, + "AZTEC_SLASH_AMOUNT_LARGE": 50000000000000000000, + "AZTEC_GOVERNANCE_PROPOSER_ROUND_SIZE": 300 +} diff --git a/l1-contracts/scripts/stress_test_deploy.sh b/l1-contracts/scripts/stress_test_deploy.sh index 9e0e6791958c..7207ba420b74 100755 --- a/l1-contracts/scripts/stress_test_deploy.sh +++ b/l1-contracts/scripts/stress_test_deploy.sh @@ -19,7 +19,7 @@ mkdir -p "$RESULTS_DIR" echo "=== Stress test: $TOTAL_RUNS runs across $WORKERS workers ===" echo "=== Results dir: $RESULTS_DIR ===" -source ./scripts/load_network_defaults.sh devnet +source ./scripts/load_network_defaults.sh PRIVATE_KEY="0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80" diff --git a/l1-contracts/scripts/test_rollup_upgrade.sh b/l1-contracts/scripts/test_rollup_upgrade.sh index 404808aedd6b..b0ffe578d5b4 100755 --- a/l1-contracts/scripts/test_rollup_upgrade.sh +++ b/l1-contracts/scripts/test_rollup_upgrade.sh @@ -1,12 +1,12 @@ #!/usr/bin/env bash set -euo pipefail -# Test rollup upgrade deployment on local anvil with devnet defaults. +# Test rollup upgrade deployment on local anvil with the base l1-contracts defaults. cd "$(dirname "$0")/.." -echo "=== Loading devnet defaults ===" -source ./scripts/load_network_defaults.sh devnet +echo "=== Loading L1 contract defaults ===" +source ./scripts/load_network_defaults.sh cleanup() { if [[ -n "${anvil_pid:-}" ]]; then diff --git a/l1-contracts/test/script/DeployAztecL1Contracts.t.sol b/l1-contracts/test/script/DeployAztecL1Contracts.t.sol index 81091c29653d..b450e0a778b4 100644 --- a/l1-contracts/test/script/DeployAztecL1Contracts.t.sol +++ b/l1-contracts/test/script/DeployAztecL1Contracts.t.sol @@ -21,11 +21,10 @@ contract DeployAztecL1ContractsTest is Test { return vm.envOr("FORGE_COVERAGE", false); } - // Load environment variables from generated/default.json - // This file is copied from spartan/environments/default.json by bootstrap.sh + // Load environment variables from scripts/network-defaults.json (the canonical L1 config defaults). function setUp() public skipWhenCoverage { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/generated/default.json"); + string memory path = string.concat(root, "/scripts/network-defaults.json"); string memory json = vm.readFile(path); // Timing config @@ -43,6 +42,20 @@ contract DeployAztecL1ContractsTest is Test { vm.setEnv("AZTEC_LOCAL_EJECTION_THRESHOLD", json.readString(".AZTEC_LOCAL_EJECTION_THRESHOLD")); vm.setEnv("AZTEC_EXIT_DELAY_SECONDS", vm.toString(json.readUint(".AZTEC_EXIT_DELAY_SECONDS"))); + // Entry queue config + vm.setEnv( + "AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE", + vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE")) + ); + vm.setEnv( + "AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE")) + ); + vm.setEnv("AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN"))); + vm.setEnv( + "AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT")) + ); + vm.setEnv("AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE"))); + // Fees config vm.setEnv("AZTEC_MANA_TARGET", vm.toString(json.readUint(".AZTEC_MANA_TARGET"))); vm.setEnv("AZTEC_PROVING_COST_PER_MANA", vm.toString(json.readUint(".AZTEC_PROVING_COST_PER_MANA"))); diff --git a/l1-contracts/test/script/DeployRollupForUpgrade.t.sol b/l1-contracts/test/script/DeployRollupForUpgrade.t.sol index 77de6f117b9c..11714808a0d3 100644 --- a/l1-contracts/test/script/DeployRollupForUpgrade.t.sol +++ b/l1-contracts/test/script/DeployRollupForUpgrade.t.sol @@ -32,11 +32,10 @@ contract DeployRollupForUpgradeTest is Test { return vm.envOr("FORGE_COVERAGE", false); } - // Load environment variables from generated/default.json - // This file is copied from spartan/environments/default.json by bootstrap.sh + // Load environment variables from scripts/network-defaults.json (the canonical L1 config defaults). function setUp() public skipWhenCoverage { string memory root = vm.projectRoot(); - string memory path = string.concat(root, "/generated/default.json"); + string memory path = string.concat(root, "/scripts/network-defaults.json"); string memory json = vm.readFile(path); // Timing config @@ -54,6 +53,20 @@ contract DeployRollupForUpgradeTest is Test { vm.setEnv("AZTEC_LOCAL_EJECTION_THRESHOLD", json.readString(".AZTEC_LOCAL_EJECTION_THRESHOLD")); vm.setEnv("AZTEC_EXIT_DELAY_SECONDS", vm.toString(json.readUint(".AZTEC_EXIT_DELAY_SECONDS"))); + // Entry queue config + vm.setEnv( + "AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE", + vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE")) + ); + vm.setEnv( + "AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE")) + ); + vm.setEnv("AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN"))); + vm.setEnv( + "AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT")) + ); + vm.setEnv("AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE", vm.toString(json.readUint(".AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE"))); + // Fees config vm.setEnv("AZTEC_MANA_TARGET", vm.toString(json.readUint(".AZTEC_MANA_TARGET"))); vm.setEnv("AZTEC_PROVING_COST_PER_MANA", vm.toString(json.readUint(".AZTEC_PROVING_COST_PER_MANA"))); diff --git a/spartan/CLAUDE.md b/spartan/CLAUDE.md index 17d1bb8ea682..595f326640f3 100644 --- a/spartan/CLAUDE.md +++ b/spartan/CLAUDE.md @@ -48,6 +48,7 @@ The main entry point is `terraform/deploy-aztec-infra/`: ### Helm Charts **aztec-node** (base chart): + - Deployable as Deployment or StatefulSet - Configurable via `node.env` for environment variables - Pre-start scripts for dynamic configuration @@ -55,15 +56,18 @@ The main entry point is `terraform/deploy-aztec-infra/`: - Pod template in `templates/_pod-template.yaml` **aztec-validator** (extends aztec-node): + - Wrapper chart with `aztec-node` as dependency (aliased as `validator`) - Adds validator-specific ConfigMap (`env.configmap.yaml`) - Configures mnemonic, validators-per-node, publishers-per-replica **aztec-prover-stack**: + - Multi-component: prover node, broker, and agent replicas - Each component has its own sub-values (`node`, `broker`, `agent`) **aztec-postgres**: + - Simple PostgreSQL StatefulSet using official `postgres:16-alpine` image - Used for validator HA signing coordination - No third-party chart dependencies (avoids Bitnami, etc.) @@ -115,11 +119,13 @@ module "validator_ha_postgres" { ``` The module: + - Deploys the `aztec-postgres` Helm chart - Runs database migrations via a Kubernetes Job (`aztec migrate-ha-db up`) - Outputs `database_url` for validators to connect Validators receive the database URL via environment variables: + - `VALIDATOR_HA_SIGNING_ENABLED=true` - `VALIDATOR_HA_DATABASE_URL=postgresql://...` - `VALIDATOR_HA_NODE_ID` (auto-set from pod name) @@ -131,24 +137,25 @@ Validators receive the database URL via environment variables: `environments/network-defaults.yml` is a **code generation source**, not a runtime config file. It centralizes "baked-in" defaults for the yarn-project packages. **What it defines:** -- `l1-contracts`: L1 smart contract parameters (timing, validator thresholds, slashing) + +- `l1-contracts`: L1 smart contract parameters (timing, validator thresholds, slashing). This section defines what `l1-contracts/script/deploy/RollupConfiguration.sol` ingests. - `slasher`: Slasher node operational settings (penalties, offense tracking) - `networks`: Preset configurations for `devnet`, `testnet`, and `mainnet` **Generated outputs:** -- `yarn-project/ethereum/src/generated/l1-contracts-defaults.ts` -- `yarn-project/slasher/src/generated/slasher-defaults.ts` -- `yarn-project/cli/src/config/generated/networks.ts` -- `l1-contracts/generated/default.json` + +- `yarn-project/slasher/src/generated/slasher-defaults.ts` (from `slasher`) +- `yarn-project/cli/src/config/generated/networks.ts` (from `networks`) **Regenerate after editing:** + ```bash -cd yarn-project/ethereum && yarn generate cd yarn-project/slasher && yarn generate cd yarn-project/cli && yarn generate -cd l1-contracts && ./bootstrap.sh ``` +**L1 contract config source of truth:** L1 contract defaults live in `l1-contracts/scripts/network-defaults.json` (owned by l1-contracts), published via `@aztec/l1-artifacts`. `@aztec/ethereum`'s config imports that JSON directly, and the Solidity deploy scripts read it via foundry `vm.readFile`. The JSON values are only used for testing flows. + ### Deployment Environment Files Environment files in `environments/*.env` provide deployment-specific values: @@ -170,6 +177,7 @@ These are loaded by deployment scripts and passed to Terraform. ### Passing Environment Variables to Pods Via Terraform `custom_settings`: + ```hcl "validator.node.env.MY_VAR" = var.MY_VALUE ``` @@ -179,11 +187,13 @@ This maps to Helm values that populate the pod's env section. ### Conditional Deployments Use ternary operators in the `helm_releases` map: + ```hcl prover = tonumber(var.PROVER_REPLICAS) > 0 ? { ... } : null ``` For dynamic multi-release generation (e.g., HA validators), use `for` expressions: + ```hcl validator_releases = tonumber(var.VALIDATOR_REPLICAS) > 0 ? { for idx in range(1 + var.VALIDATOR_HA_REPLICAS) : @@ -194,6 +204,7 @@ validator_releases = tonumber(var.VALIDATOR_REPLICAS) > 0 ? { ### Values Layering Values are applied in order (later overrides earlier): + 1. `common.yaml` 2. `{component}.yaml` 3. `{component}-resources-{profile}.yaml` @@ -203,11 +214,13 @@ Values are applied in order (later overrides earlier): ### Service Discovery Internal services use Kubernetes DNS: + ``` http://{release-name}-{component}.{namespace}.svc.cluster.local:{port} ``` Example web3signer URL: + ``` http://staging-signer-web3signer.staging.svc.cluster.local:9000/ ``` @@ -227,6 +240,7 @@ When `VALIDATOR_HA_REPLICAS > 0`, validators are deployed as **multiple Helm rel - `VALIDATOR_HA_REPLICAS=2` → 3 releases (primary + 2 HA) Example with `VALIDATOR_HA_REPLICAS=1`: + ``` validator-0 & validator-ha-1-0 share attesters 0-11 validator-1 & validator-ha-1-1 share attesters 12-23 @@ -272,6 +286,7 @@ Publishers are allocated **per replica (pod)**, not per attester key. Each relea ``` Example with 4 replicas, 4 publishers/replica, base index 5000: + - Primary (idx=0): `PUBLISHER_KEY_INDEX_START = 5000` - HA-1 (idx=1): `PUBLISHER_KEY_INDEX_START = 5000 + (1 * 4 * 4) = 5016` @@ -283,27 +298,36 @@ PUBLISHER_KEY_INDEX=$((POD_INDEX * VALIDATOR_PUBLISHERS_PER_REPLICA + PUBLISHER_ ``` The keystore uses **schema v2** with a top-level `publisher` array shared by all validators on the pod: + ```json -{"schemaVersion": 2, "publisher": ["0x1", "0x2", "0x3", "0x4"], "validators": [{"attester": "..."}]} +{ + "schemaVersion": 2, + "publisher": ["0x1", "0x2", "0x3", "0x4"], + "validators": [{ "attester": "..." }] +} ``` This ensures each release uses non-overlapping publisher key ranges while decoupling publisher count from attester count. **HA coordination:** + - Both releases connect to shared PostgreSQL via `VALIDATOR_HA_DATABASE_URL` - Database prevents double-signing by the same attester - If one pod dies, its HA partner continues signing ### Provers + - Generate validity proofs for epochs - Broker distributes proving jobs to agents - Agents can scale horizontally ### RPC Nodes + - Serve public API endpoints - Optional ingress with GCP backend config ### Boot Nodes + - P2P bootstrap for network discovery - Internal boot node optional (can use external) @@ -314,6 +338,7 @@ This ensures each release uses non-overlapping publisher key ranges while decoup 3. **New Helm chart**: Add to `spartan/` root (follow aztec-keystore pattern) For new modules, follow the web3signer pattern: + - `main.tf`: Helm release(s) and supporting resources - `variables.tf`: Input variables - `outputs.tf`: Service URLs and other outputs diff --git a/spartan/environments/network-defaults.yml b/spartan/environments/network-defaults.yml index 4d0f251951d4..ad8e77e83f42 100644 --- a/spartan/environments/network-defaults.yml +++ b/spartan/environments/network-defaults.yml @@ -6,7 +6,6 @@ # These defaults affect: # - End-to-end tests # - CLI usage without environment variables -# - Default values in TypeScript config mappings # # They are NOT read at runtime from this file. To override values for # an existing deployment, use environment variables or a .env file. @@ -16,22 +15,17 @@ # still override individual values. # # Generated outputs: -# - yarn-project/ethereum/src/generated/l1-contracts-defaults.ts # - yarn-project/slasher/src/generated/slasher-defaults.ts # - yarn-project/cli/src/config/generated/networks/{network}.json -# - l1-contracts/generated/default.json # # To regenerate after editing: -# yarn-project/ethereum: yarn generate # yarn-project/slasher: yarn generate # yarn-project/cli: yarn generate -# l1-contracts: ./bootstrap.sh #=============================================================================== # L1 CONTRACTS DEFAULTS #=============================================================================== # These configure Aztec L1 smart contracts and are used during deployment. -# Values here must match Solidity vm.envOr defaults in RollupConfiguration.sol. l1-contracts: &l1-contracts-defaults #--------------------------------------------------------------------------- @@ -106,14 +100,27 @@ l1-contracts: &l1-contracts-defaults # Number of slots in a governance proposer round. AZTEC_GOVERNANCE_PROPOSER_ROUND_SIZE: 300 + #--------------------------------------------------------------------------- + # Entry Queue Configuration + #--------------------------------------------------------------------------- + # Size of the validator set at or below which the entry queue uses the bootstrap flush size. + AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE: 0 + # Number of validators admitted from the entry queue per flush during the bootstrap phase. + AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE: 0 + # Minimum number of validators admitted from the entry queue per flush. + AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN: 48 + # Divisor applied to the queue size to derive the normal per-flush admission count. + AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT: 2 + # Maximum number of validators admitted from the entry queue per flush. + AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE: 48 + #=============================================================================== # SLASHER NODE DEFAULTS #=============================================================================== # These configure the slasher node's operational behavior. # Used by yarn-project/slasher for penalty calculation and offense tracking. -slasher: &slasher - # Rounds after which an offense expires. +slasher: &slasher # Rounds after which an offense expires. SLASH_OFFENSE_EXPIRATION_ROUNDS: 4 # Maximum size of slashing payload. SLASH_MAX_PAYLOAD_SIZE: 80 @@ -215,16 +222,16 @@ networks: AZTEC_LAG_IN_EPOCHS_FOR_RANDAO: 1 AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS: 1 # Network identity - L1_CHAIN_ID: 11155111 # Sepolia + L1_CHAIN_ID: 11155111 # Sepolia # Genesis state - TEST_ACCOUNTS: true # Fund test accounts with fee juice - SPONSORED_FPC: true # Fund sponsored FPC with fee juice + TEST_ACCOUNTS: true # Fund test accounts with fee juice + SPONSORED_FPC: true # Fund sponsored FPC with fee juice TRANSACTIONS_DISABLED: false # Sequencer SEQ_MAX_TX_PER_BLOCK: 18 # Prover - PROVER_REAL_PROOFS: false # Use mock proofs - PXE_PROVER_ENABLED: false # Disable PXE proving + PROVER_REAL_PROOFS: false # Use mock proofs + PXE_PROVER_ENABLED: false # Disable PXE proving # Sync SYNC_SNAPSHOTS_URLS: "" SKIP_ARCHIVER_INITIAL_SYNC: false @@ -265,12 +272,12 @@ networks: AZTEC_GOVERNANCE_PROPOSER_ROUND_SIZE: 100 AZTEC_GOVERNANCE_PROPOSER_QUORUM: 60 AZTEC_MANA_TARGET: 75000000 - AZTEC_PROVING_COST_PER_MANA: 12500000 # AZIP-16: halved to track v5 prover-cost reduction + AZTEC_PROVING_COST_PER_MANA: 12500000 # AZIP-16: halved to track v5 prover-cost reduction AZTEC_SLASH_AMOUNT_SMALL: 100000e18 - AZTEC_SLASH_AMOUNT_MEDIUM: 250000e18 # AZIP-16: MEDIUM/LARGE raised 2.5x vs SMALL + AZTEC_SLASH_AMOUNT_MEDIUM: 250000e18 # AZIP-16: MEDIUM/LARGE raised 2.5x vs SMALL AZTEC_SLASH_AMOUNT_LARGE: 250000e18 # Network identity - L1_CHAIN_ID: 11155111 # Sepolia + L1_CHAIN_ID: 11155111 # Sepolia # Genesis state TEST_ACCOUNTS: false SPONSORED_FPC: false @@ -288,13 +295,13 @@ networks: SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD: 1 SLASH_INACTIVITY_PENALTY: 100000e18 SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY: 100000e18 - SLASH_DUPLICATE_PROPOSAL_PENALTY: 250000e18 # AZIP-16: activated at LARGE - SLASH_DUPLICATE_ATTESTATION_PENALTY: 250000e18 # AZIP-16: activated at LARGE - SLASH_PROPOSE_DESCENDANT_OF_CHECKPOINT_WITH_INVALID_ATTESTATIONS_PENALTY: 0 # AZIP-16: deferred to subsequent release - SLASH_ATTEST_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 0 # AZIP-16: deferred to subsequent release + SLASH_DUPLICATE_PROPOSAL_PENALTY: 250000e18 # AZIP-16: activated at LARGE + SLASH_DUPLICATE_ATTESTATION_PENALTY: 250000e18 # AZIP-16: activated at LARGE + SLASH_PROPOSE_DESCENDANT_OF_CHECKPOINT_WITH_INVALID_ATTESTATIONS_PENALTY: 0 # AZIP-16: deferred to subsequent release + SLASH_ATTEST_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 0 # AZIP-16: deferred to subsequent release SLASH_UNKNOWN_PENALTY: 100000e18 SLASH_INVALID_BLOCK_PENALTY: 100000e18 - SLASH_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 100000e18 # AZIP-16: activated at SMALL + SLASH_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 100000e18 # AZIP-16: activated at SMALL SLASH_GRACE_PERIOD_L2_SLOTS: 64 ENABLE_VERSION_CHECK: true @@ -306,8 +313,8 @@ networks: AZTEC_EJECTION_THRESHOLD: 100000e18 AZTEC_LOCAL_EJECTION_THRESHOLD: 190000e18 AZTEC_SLASH_AMOUNT_SMALL: 2000e18 - AZTEC_SLASH_AMOUNT_MEDIUM: 5000e18 # AZIP-16 - AZTEC_SLASH_AMOUNT_LARGE: 5000e18 # AZIP-16 + AZTEC_SLASH_AMOUNT_MEDIUM: 5000e18 # AZIP-16 + AZTEC_SLASH_AMOUNT_LARGE: 5000e18 # AZIP-16 AZTEC_SLASHING_LIFETIME_IN_ROUNDS: 34 AZTEC_SLASHING_EXECUTION_DELAY_IN_ROUNDS: 28 AZTEC_SLASHING_VETOER: "0xBbB4aF368d02827945748b28CD4b2D42e4A37480" @@ -315,16 +322,16 @@ networks: AZTEC_GOVERNANCE_PROPOSER_QUORUM: 600 AZTEC_GOVERNANCE_PROPOSER_ROUND_SIZE: 1000 AZTEC_MANA_TARGET: 75000000 - AZTEC_PROVING_COST_PER_MANA: 12500000 # AZIP-16: halved to track v5 prover-cost reduction - AZTEC_EXIT_DELAY_SECONDS: 345600 # 4 days - AZTEC_SLASHING_DISABLE_DURATION: 259200 # 3 days + AZTEC_PROVING_COST_PER_MANA: 12500000 # AZIP-16: halved to track v5 prover-cost reduction + AZTEC_EXIT_DELAY_SECONDS: 345600 # 4 days + AZTEC_SLASHING_DISABLE_DURATION: 259200 # 3 days AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE: 500 AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE: 500 AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN: 1 AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT: 400 AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE: 4 # Network identity - L1_CHAIN_ID: 1 # Ethereum mainnet + L1_CHAIN_ID: 1 # Ethereum mainnet # Genesis state - no test accounts, no sponsored FPC TEST_ACCOUNTS: false SPONSORED_FPC: false @@ -348,11 +355,11 @@ networks: SLASH_INACTIVITY_CONSECUTIVE_EPOCH_THRESHOLD: 2 SLASH_INACTIVITY_PENALTY: 2000e18 SLASH_PROPOSE_INVALID_ATTESTATIONS_PENALTY: 2000e18 - SLASH_DUPLICATE_PROPOSAL_PENALTY: 5000e18 # AZIP-16: activated at LARGE - SLASH_DUPLICATE_ATTESTATION_PENALTY: 5000e18 # AZIP-16: activated at LARGE - SLASH_PROPOSE_DESCENDANT_OF_CHECKPOINT_WITH_INVALID_ATTESTATIONS_PENALTY: 0 # AZIP-16: deferred to subsequent release - SLASH_ATTEST_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 0 # AZIP-16: deferred to subsequent release + SLASH_DUPLICATE_PROPOSAL_PENALTY: 5000e18 # AZIP-16: activated at LARGE + SLASH_DUPLICATE_ATTESTATION_PENALTY: 5000e18 # AZIP-16: activated at LARGE + SLASH_PROPOSE_DESCENDANT_OF_CHECKPOINT_WITH_INVALID_ATTESTATIONS_PENALTY: 0 # AZIP-16: deferred to subsequent release + SLASH_ATTEST_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 0 # AZIP-16: deferred to subsequent release SLASH_UNKNOWN_PENALTY: 2000e18 SLASH_INVALID_BLOCK_PENALTY: 2000e18 - SLASH_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 2000e18 # AZIP-16: activated at SMALL + SLASH_INVALID_CHECKPOINT_PROPOSAL_PENALTY: 2000e18 # AZIP-16: activated at SMALL SLASH_GRACE_PERIOD_L2_SLOTS: 1200 diff --git a/spartan/scripts/deploy_rollup_upgrade.sh b/spartan/scripts/deploy_rollup_upgrade.sh index 3053a8b816bd..2df1413cdcef 100755 --- a/spartan/scripts/deploy_rollup_upgrade.sh +++ b/spartan/scripts/deploy_rollup_upgrade.sh @@ -71,7 +71,7 @@ log "L1 Network: $L1_NETWORK" # Load network defaults if [[ -n "${NETWORK:-}" ]]; then log "Loading L1 contract defaults from network-defaults.yml for $NETWORK" - source "${repo_root}/l1-contracts/scripts/load_network_defaults.sh" "$NETWORK" + source "${repo_root}/spartan/scripts/load_network_defaults.sh" "$NETWORK" else # No NETWORK specified - load base l1-contracts defaults, env vars will override log "Loading base l1-contracts defaults (env vars will override)" diff --git a/spartan/scripts/load_network_defaults.sh b/spartan/scripts/load_network_defaults.sh new file mode 100755 index 000000000000..23923a28a175 --- /dev/null +++ b/spartan/scripts/load_network_defaults.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Load L1 contract defaults from network-defaults.yml for a given network. +# Exports AZTEC_* and ETHEREUM_* env vars with YAML anchor inheritance resolved. +# +# Usage: +# source ./scripts/load_network_defaults.sh +# +# Networks: mainnet, testnet, devnet + +network="${1:?Usage: $0 }" +script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +network_defaults="${script_dir}/../environments/network-defaults.yml" + +if [[ ! -f "$network_defaults" ]]; then + echo "ERROR: network-defaults.yml not found at $network_defaults" >&2 + exit 1 +fi + +# explode(.) resolves YAML anchors (<<: *prodlike inheritance) +# Output as props, filter comments, normalize spacing +while IFS='=' read -r key value; do + export "$key"="$value" +done < <(yq -o=props "explode(.) | .networks.$network | with_entries(select(.key | test(\"^AZTEC_|^ETHEREUM_\")))" "$network_defaults" \ + | grep -v '^#' \ + | grep -v '^$' \ + | sed 's/ = /=/') diff --git a/yarn-project/bootstrap.sh b/yarn-project/bootstrap.sh index cef8303e3249..e61409d3e4c2 100755 --- a/yarn-project/bootstrap.sh +++ b/yarn-project/bootstrap.sh @@ -129,7 +129,6 @@ function compile_all { accounts \ aztec.js \ cli \ - ethereum \ slasher \ stdlib \ ivc-integration \ diff --git a/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts b/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts index 3ed31f0279e9..7938a4450a44 100644 --- a/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts +++ b/yarn-project/end-to-end/src/e2e_p2p/add_rollup.test.ts @@ -199,6 +199,11 @@ describe('e2e_p2p_add_rollup', () => { slashAmountLarge: t.ctx.aztecNodeConfig.slashAmountLarge, localEjectionThreshold: t.ctx.aztecNodeConfig.localEjectionThreshold, governanceVotingDuration: t.ctx.aztecNodeConfig.governanceVotingDuration, + entryQueueBootstrapValidatorSetSize: t.ctx.aztecNodeConfig.entryQueueBootstrapValidatorSetSize, + entryQueueBootstrapFlushSize: t.ctx.aztecNodeConfig.entryQueueBootstrapFlushSize, + entryQueueFlushSizeMin: t.ctx.aztecNodeConfig.entryQueueFlushSizeMin, + entryQueueFlushSizeQuotient: t.ctx.aztecNodeConfig.entryQueueFlushSizeQuotient, + entryQueueMaxFlushSize: t.ctx.aztecNodeConfig.entryQueueMaxFlushSize, }, ); diff --git a/yarn-project/ethereum/package.json b/yarn-project/ethereum/package.json index a2fc6f4321b4..9e64078e21a1 100644 --- a/yarn-project/ethereum/package.json +++ b/yarn-project/ethereum/package.json @@ -40,7 +40,6 @@ "build": "yarn clean && ../scripts/tsc.sh", "build:dev": "../scripts/tsc.sh --watch", "clean": "rm -rf ./dest .tsbuildinfo", - "generate": "./scripts/generate.sh", "start:dev": "concurrently -k \"../scripts/tsc.sh --watch\" \"nodemon --watch dest --exec yarn start\"", "start": "node ./dest/index.js", "test": "NODE_NO_WARNINGS=1 node --experimental-vm-modules ../node_modules/.bin/jest --passWithNoTests --maxWorkers=${JEST_MAX_WORKERS:-8}", diff --git a/yarn-project/ethereum/scripts/generate.sh b/yarn-project/ethereum/scripts/generate.sh deleted file mode 100755 index a394c2da79b0..000000000000 --- a/yarn-project/ethereum/scripts/generate.sh +++ /dev/null @@ -1,32 +0,0 @@ -#!/usr/bin/env bash -# Generates l1-contracts-defaults.ts from network-defaults.yml. -# -# Source: spartan/environments/network-defaults.yml -> l1-contracts -# Output: yarn-project/ethereum/src/generated/l1-contracts-defaults.ts -set -euo pipefail - -cd $(git rev-parse --show-toplevel) - -mkdir -p yarn-project/ethereum/src/generated - -echo "Generating l1-contracts-defaults.ts from spartan/environments/network-defaults.yml..." - -# Generate TypeScript file with typed defaults -cat > yarn-project/ethereum/src/generated/l1-contracts-defaults.ts << 'HEADER' -// Auto-generated from spartan/environments/network-defaults.yml -// Do not edit manually - run yarn generate to regenerate - -/** Default L1 contracts configuration values from network-defaults.yml */ -export const l1ContractsDefaultEnv = { -HEADER - -# Extract l1-contracts section and format as TypeScript object properties (single quotes for strings) -yq -o json '."l1-contracts"' spartan/environments/network-defaults.yml | \ - jq -r "to_entries | .[] | \" \\(.key): \\(.value | if type == \"string\" then \"'\\(.)'\" else . end),\"" \ - >> yarn-project/ethereum/src/generated/l1-contracts-defaults.ts - -cat >> yarn-project/ethereum/src/generated/l1-contracts-defaults.ts << 'FOOTER' -} as const; -FOOTER - -echo "Done!" diff --git a/yarn-project/ethereum/src/config.ts b/yarn-project/ethereum/src/config.ts index adddb0e16708..8f3fde60eaf0 100644 --- a/yarn-project/ethereum/src/config.ts +++ b/yarn-project/ethereum/src/config.ts @@ -9,8 +9,8 @@ import { optionalNumberConfigHelper, } from '@aztec/foundation/config'; import { EthAddress } from '@aztec/foundation/eth-address'; +import l1ContractsDefaultEnv from '@aztec/l1-artifacts/network-defaults.json' with { type: 'json' }; -import { l1ContractsDefaultEnv } from './generated/l1-contracts-defaults.js'; import { type L1TxUtilsConfig, l1TxUtilsConfigMappings } from './l1_tx_utils/config.js'; export type GenesisStateConfig = { @@ -81,11 +81,21 @@ export type L1ContractsConfig = { initialEthPerFeeAsset: bigint; /** The number of seconds to wait for an exit */ exitDelaySeconds: number; + /** Validator set size at or below which the entry queue uses the bootstrap flush size. */ + entryQueueBootstrapValidatorSetSize: number; + /** Number of validators admitted from the entry queue per flush during the bootstrap phase. */ + entryQueueBootstrapFlushSize: number; + /** Minimum number of validators admitted from the entry queue per flush. */ + entryQueueFlushSizeMin: number; + /** Divisor applied to the queue size to derive the normal per-flush admission count. */ + entryQueueFlushSizeQuotient: number; + /** Maximum number of validators admitted from the entry queue per flush. */ + entryQueueMaxFlushSize: number; } & L1TxUtilsConfig; /** * Config mappings for L1ContractsConfig. - * Default values come from generated l1-contracts-defaults.json (source: defaults.yml). + * Default values come from l1-contracts/scripts/network-defaults.json (published via @aztec/l1-artifacts). * Real deployments use forge scripts which require explicit env vars (vm.envUint). */ export const l1ContractsConfigMappings: ConfigMappingsType = { @@ -237,12 +247,37 @@ export const l1ContractsConfigMappings: ConfigMappingsType = description: 'The delay before a validator can exit the set', ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_EXIT_DELAY_SECONDS), }, + entryQueueBootstrapValidatorSetSize: { + env: 'AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE', + description: 'Validator set size at or below which the entry queue uses the bootstrap flush size.', + ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE), + }, + entryQueueBootstrapFlushSize: { + env: 'AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE', + description: 'Number of validators admitted from the entry queue per flush during the bootstrap phase.', + ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE), + }, + entryQueueFlushSizeMin: { + env: 'AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN', + description: 'Minimum number of validators admitted from the entry queue per flush.', + ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN), + }, + entryQueueFlushSizeQuotient: { + env: 'AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT', + description: 'Divisor applied to the queue size to derive the normal per-flush admission count.', + ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT), + }, + entryQueueMaxFlushSize: { + env: 'AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE', + description: 'Maximum number of validators admitted from the entry queue per flush.', + ...numberConfigHelper(l1ContractsDefaultEnv.AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE), + }, ...omitConfigMappings(l1TxUtilsConfigMappings, ['ethereumSlotDuration']), }; /** * Default L1 contracts configuration derived from l1ContractsConfigMappings. - * Source of truth: spartan/environments/defaults.yml -> defaults.l1-contracts + * Source of truth: l1-contracts/scripts/network-defaults.json (published via @aztec/l1-artifacts). */ export const DefaultL1ContractsConfig = getDefaultConfig(l1ContractsConfigMappings); diff --git a/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts b/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts index b348dfafdce5..785765bd9556 100644 --- a/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts +++ b/yarn-project/ethereum/src/deploy_aztec_l1_contracts.ts @@ -589,6 +589,11 @@ export function getDeployRollupForUpgradeEnvVars( AZTEC_SLASH_AMOUNT_SMALL: args.slashAmountSmall.toString(), AZTEC_SLASH_AMOUNT_MEDIUM: args.slashAmountMedium.toString(), AZTEC_SLASH_AMOUNT_LARGE: args.slashAmountLarge.toString(), + AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE: args.entryQueueBootstrapValidatorSetSize.toString(), + AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE: args.entryQueueBootstrapFlushSize.toString(), + AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN: args.entryQueueFlushSizeMin.toString(), + AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT: args.entryQueueFlushSizeQuotient.toString(), + AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE: args.entryQueueMaxFlushSize.toString(), } as const; } diff --git a/yarn-project/ethereum/src/queries.ts b/yarn-project/ethereum/src/queries.ts index 115150cbf31b..fde4ceb0561d 100644 --- a/yarn-project/ethereum/src/queries.ts +++ b/yarn-project/ethereum/src/queries.ts @@ -186,5 +186,11 @@ export async function getL1ContractsConfig( slashAmountMedium: slashingAmounts[1], slashAmountLarge: slashingAmounts[2], initialEthPerFeeAsset: DefaultL1ContractsConfig.initialEthPerFeeAsset, + // Not exposed by the rollup contract; fall back to defaults like the other non-on-chain fields above. + entryQueueBootstrapValidatorSetSize: DefaultL1ContractsConfig.entryQueueBootstrapValidatorSetSize, + entryQueueBootstrapFlushSize: DefaultL1ContractsConfig.entryQueueBootstrapFlushSize, + entryQueueFlushSizeMin: DefaultL1ContractsConfig.entryQueueFlushSizeMin, + entryQueueFlushSizeQuotient: DefaultL1ContractsConfig.entryQueueFlushSizeQuotient, + entryQueueMaxFlushSize: DefaultL1ContractsConfig.entryQueueMaxFlushSize, }; } diff --git a/yarn-project/foundation/src/config/env_var.ts b/yarn-project/foundation/src/config/env_var.ts index ec88d15d2c34..e3af3c017297 100644 --- a/yarn-project/foundation/src/config/env_var.ts +++ b/yarn-project/foundation/src/config/env_var.ts @@ -333,6 +333,11 @@ export type EnvVar = | 'AZTEC_GOVERNANCE_PROPOSER_ROUND_SIZE' | 'AZTEC_GOVERNANCE_VOTING_DURATION' | 'AZTEC_EXIT_DELAY_SECONDS' + | 'AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE' + | 'AZTEC_ENTRY_QUEUE_BOOTSTRAP_FLUSH_SIZE' + | 'AZTEC_ENTRY_QUEUE_FLUSH_SIZE_MIN' + | 'AZTEC_ENTRY_QUEUE_FLUSH_SIZE_QUOTIENT' + | 'AZTEC_ENTRY_QUEUE_MAX_FLUSH_SIZE' | 'L1_GAS_LIMIT_BUFFER_PERCENTAGE' | 'L1_GAS_PRICE_MAX' | 'L1_FEE_PER_GAS_GWEI_MAX' From ca6a9a50c5650ee64ca90c09d9bd0d6000cd43c0 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Jul 2026 13:46:16 +0100 Subject: [PATCH 2/5] feat: expose prover node RPC (#24531) Fix A-1365 --- spartan/environments/testnet.env | 3 + spartan/scripts/create_api_key.sh | 34 ++++ spartan/scripts/deploy_network.sh | 6 +- spartan/terraform/deploy-aztec-infra/main.tf | 63 ++++++-- .../terraform/deploy-aztec-infra/variables.tf | 42 +++-- spartan/terraform/modules/rpc-gateway/main.tf | 145 ++++++++++++++++-- .../modules/rpc-gateway/variables.tf | 2 + 7 files changed, 255 insertions(+), 40 deletions(-) create mode 100755 spartan/scripts/create_api_key.sh diff --git a/spartan/environments/testnet.env b/spartan/environments/testnet.env index a3f959ace726..597fe0d7df41 100644 --- a/spartan/environments/testnet.env +++ b/spartan/environments/testnet.env @@ -6,6 +6,9 @@ NAMESPACE=${NAMESPACE:-testnet} NETWORK=testnet RPC_GATEWAY_ENABLED=false +PROVER_NODE_RPC_GATEWAY_ENABLED=false +PROVER_NODE_RPC_GATEWAY_HOSTS='["prover.testnet.rpc.aztec-labs.com"]' +PROVER_NODE_RPC_GATEWAY_API_KEY_SECRET_NAMES='["testnet-prover-rpc-consumer-client1"]' REAL_VERIFIER=true AZTEC_ENTRY_QUEUE_BOOTSTRAP_VALIDATOR_SET_SIZE=48 diff --git a/spartan/scripts/create_api_key.sh b/spartan/scripts/create_api_key.sh new file mode 100755 index 000000000000..403f98fe84a6 --- /dev/null +++ b/spartan/scripts/create_api_key.sh @@ -0,0 +1,34 @@ +#!/usr/bin/env bash + +set -euo pipefail + +API_KEY_NAME="${1:-}" +CLIENT_NAME="${2:-}" + +function die { + echo $@ >&2 + exit 1 +} + +if [[ -z "$API_KEY_NAME" ]]; then + die "Missing API key secret name" +fi + +if [[ -z "$CLIENT_NAME" ]]; then + die "Missing client name." +fi + +LAST="$(gcloud secrets list --filter "$API_KEY_NAME-client" --sort-by "~createTime" --format=json | jq -r ".[0].name")" +if [[ "$LAST" == "null" ]]; then + openssl rand -hex 16 | tr -d '\n' | gcloud secrets create "${API_KEY_NAME}-client1" --data-file=- --set-annotations="client_name=$CLIENT_NAME" +else + + if [[ "$LAST" =~ -client([0-9]+)$ ]]; then + PREV="${BASH_REMATCH[1]}" + NEXT="$((10#$PREV + 1))" + else + die "Could not parse client number from: $LAST" + fi + + openssl rand -hex 16 | tr -d '\n' | gcloud secrets create "${API_KEY_NAME}-client${NEXT}" --data-file=- --set-annotations="client_name=$CLIENT_NAME" +fi diff --git a/spartan/scripts/deploy_network.sh b/spartan/scripts/deploy_network.sh index f1eada0c62f6..098cbc709f18 100755 --- a/spartan/scripts/deploy_network.sh +++ b/spartan/scripts/deploy_network.sh @@ -711,7 +711,11 @@ append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" RPC_GATEWAY_A append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" RPC_GATEWAY_ALLOW_ANONYMOUS append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" RPC_GATEWAY_ANONYMOUS_RATE_LIMIT_MINUTE append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" RPC_GATEWAY_API_KEY_HEADER_NAME string -append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" RPC_GATEWAY_CONSUMERS +append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" PROVER_NODE_RPC_GATEWAY_ENABLED +append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" PROVER_NODE_RPC_GATEWAY_HOSTS +append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" PROVER_NODE_RPC_GATEWAY_PATH string +append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" PROVER_NODE_RPC_GATEWAY_STRIP_PATH +append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" PROVER_NODE_RPC_GATEWAY_API_KEY_SECRET_NAMES append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" RPC_GATEWAY_CREATE_DNS append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" RPC_GATEWAY_DNS_ZONE_NAME string append_optional_tfvar "${DEPLOY_AZTEC_INFRA_DIR}/terraform.tfvars" RPC_GATEWAY_DNS_TTL diff --git a/spartan/terraform/deploy-aztec-infra/main.tf b/spartan/terraform/deploy-aztec-infra/main.tf index 9127eae126cc..0be9f9875ff0 100644 --- a/spartan/terraform/deploy-aztec-infra/main.tf +++ b/spartan/terraform/deploy-aztec-infra/main.tf @@ -113,26 +113,57 @@ locals { # Detect local kind context (e.g., "kind-kind") to gate Service types is_kind = can(regex("^kind", var.K8S_CLUSTER_CONTEXT)) - rpc_gateway_simple_consumers = { - for secret_name in var.RPC_GATEWAY_API_KEY_SECRET_NAMES : secret_name => { + kong_gateway_enabled = var.RPC_GATEWAY_ENABLED || var.PROVER_NODE_RPC_GATEWAY_ENABLED + + rpc_gateway_consumer_group = "rpc" + prover_node_rpc_gateway_consumer_group = "prover-node-rpc" + + rpc_gateway_auth_secret_names = var.RPC_GATEWAY_ENABLED ? var.RPC_GATEWAY_API_KEY_SECRET_NAMES : [] + prover_node_rpc_gateway_auth_secret_names = var.PROVER_NODE_RPC_GATEWAY_ENABLED ? var.PROVER_NODE_RPC_GATEWAY_API_KEY_SECRET_NAMES : [] + rpc_gateway_consumer_secret_names = toset(concat(local.rpc_gateway_auth_secret_names, local.prover_node_rpc_gateway_auth_secret_names)) + + rpc_gateway_consumers = { + for secret_name in local.rpc_gateway_consumer_secret_names : secret_name => { username = secret_name gcp_secret_manager_secret_name = secret_name rate_limit_minute = 0 + consumer_groups = compact([ + contains(local.rpc_gateway_auth_secret_names, secret_name) ? local.rpc_gateway_consumer_group : "", + contains(local.prover_node_rpc_gateway_auth_secret_names, secret_name) ? local.prover_node_rpc_gateway_consumer_group : "", + ]) } } - rpc_gateway_consumers = merge(local.rpc_gateway_simple_consumers, var.RPC_GATEWAY_CONSUMERS) - - rpc_gateway_routes = { - canonical = { - hosts = var.RPC_GATEWAY_HOSTS - route_namespace = var.NAMESPACE - upstream_service_name = "${var.RELEASE_PREFIX}-rpc-aztec-node" - upstream_service_port = 8080 - auth_mode = var.RPC_GATEWAY_ALLOW_ANONYMOUS ? "keyed_with_anonymous" : "keyed_only" - anonymous_rate_limit_minute = var.RPC_GATEWAY_ANONYMOUS_RATE_LIMIT_MINUTE - } - } + rpc_gateway_routes = merge( + var.RPC_GATEWAY_ENABLED ? { + rpc = { + hosts = var.RPC_GATEWAY_HOSTS + route_namespace = var.NAMESPACE + upstream_service_name = "${var.RELEASE_PREFIX}-rpc-aztec-node" + upstream_service_port = 8080 + auth_mode = var.RPC_GATEWAY_ALLOW_ANONYMOUS ? "keyed_with_anonymous" : "keyed_only" + anonymous_rate_limit_minute = var.RPC_GATEWAY_ANONYMOUS_RATE_LIMIT_MINUTE + path = "/" + path_type = "Prefix" + strip_path = false + allowed_consumer_groups = [local.rpc_gateway_consumer_group] + } + } : {}, + var.PROVER_NODE_RPC_GATEWAY_ENABLED ? { + "prover-node-rpc" = { + hosts = var.PROVER_NODE_RPC_GATEWAY_HOSTS + route_namespace = var.NAMESPACE + upstream_service_name = "${var.RELEASE_PREFIX}-prover-node" + upstream_service_port = 8080 + auth_mode = "keyed_only" + anonymous_rate_limit_minute = 0 + path = var.PROVER_NODE_RPC_GATEWAY_PATH + path_type = "Prefix" + strip_path = var.PROVER_NODE_RPC_GATEWAY_STRIP_PATH + allowed_consumer_groups = [local.prover_node_rpc_gateway_consumer_group] + } + } : {} + ) internal_boot_node_url = var.DEPLOY_INTERNAL_BOOTNODE ? "http://${var.RELEASE_PREFIX}-p2p-bootstrap-node.${var.NAMESPACE}.svc.cluster.local:8080" : "" @@ -786,7 +817,7 @@ resource "helm_release" "releases" { } module "rpc_gateway" { - count = var.RPC_GATEWAY_ENABLED ? 1 : 0 + count = local.kong_gateway_enabled ? 1 : 0 source = "../modules/rpc-gateway" @@ -829,7 +860,7 @@ module "rpc_gateway" { } module "rpc_gateway_metrics_collector" { - count = var.RPC_GATEWAY_ENABLED && var.RPC_GATEWAY_KONG_OTEL_METRICS_GCP_SECRET_NAME != "" ? 1 : 0 + count = local.kong_gateway_enabled && var.RPC_GATEWAY_KONG_OTEL_METRICS_GCP_SECRET_NAME != "" ? 1 : 0 source = "../modules/otel-metrics-collector" diff --git a/spartan/terraform/deploy-aztec-infra/variables.tf b/spartan/terraform/deploy-aztec-infra/variables.tf index db4ec2318891..4adde5aeba1b 100644 --- a/spartan/terraform/deploy-aztec-infra/variables.tf +++ b/spartan/terraform/deploy-aztec-infra/variables.tf @@ -711,7 +711,7 @@ variable "RPC_GATEWAY_HOSTS" { } variable "RPC_GATEWAY_API_KEY_SECRET_NAMES" { - description = "GCP Secret Manager secret names containing API keys allowed by the RPC gateway. Raw key values must not go here." + description = "GCP Secret Manager secret names containing API keys allowed by the node RPC gateway." type = list(string) default = [] } @@ -734,16 +734,6 @@ variable "RPC_GATEWAY_API_KEY_HEADER_NAME" { default = "x-aztec-api-key" } -variable "RPC_GATEWAY_CONSUMERS" { - description = "Kong consumers keyed by team name. Each value must use exactly one credential source." - type = map(object({ - username = string - gcp_secret_manager_secret_name = string - rate_limit_minute = number - })) - default = {} -} - variable "RPC_GATEWAY_KONG_NAMESPACE" { description = "Optional namespace for the Kong Helm release. Defaults to NAMESPACE." type = string @@ -865,6 +855,36 @@ variable "RPC_GATEWAY_GCP_MANAGED_CERTIFICATE_ENABLED" { default = true } +variable "PROVER_NODE_RPC_GATEWAY_ENABLED" { + description = "Enable an API-key-only Kong route for the prover-node JSON-RPC service. When RPC_GATEWAY_ENABLED=true, this adds a route to the same gateway." + type = bool + default = false +} + +variable "PROVER_NODE_RPC_GATEWAY_HOSTS" { + description = "Hostnames served by the prover-node RPC gateway. Required when PROVER_NODE_RPC_GATEWAY_ENABLED=true." + type = list(string) + default = [] +} + +variable "PROVER_NODE_RPC_GATEWAY_PATH" { + description = "Path prefix for the prover-node RPC route. Use / for a dedicated host." + type = string + default = "/" +} + +variable "PROVER_NODE_RPC_GATEWAY_STRIP_PATH" { + description = "Whether Kong should strip PROVER_NODE_RPC_GATEWAY_PATH before proxying to the prover node." + type = bool + default = false +} + +variable "PROVER_NODE_RPC_GATEWAY_API_KEY_SECRET_NAMES" { + description = "GCP Secret Manager secret names containing API keys allowed by the prover-node RPC gateway. Raw key values must not go here." + type = list(string) + default = [] +} + variable "PROVER_FAILED_PROOF_STORE" { description = "Optional GCS/URI to store failed proofs from the prover" type = string diff --git a/spartan/terraform/modules/rpc-gateway/main.tf b/spartan/terraform/modules/rpc-gateway/main.tf index 1007e76566fe..fd844668318c 100644 --- a/spartan/terraform/modules/rpc-gateway/main.tf +++ b/spartan/terraform/modules/rpc-gateway/main.tf @@ -43,11 +43,17 @@ locals { if route.auth_mode == "keyed_with_anonymous" } + routes_with_consumer_groups = { + for name, route in var.ROUTES : name => route + if length(route.allowed_consumer_groups) > 0 + } + route_plugin_names = { for name, route in var.ROUTES : name => join(",", compact([ "${var.RELEASE_PREFIX}-${name}-${var.ROUTE_RESOURCE_SUFFIX}-path-api-key", "${var.RELEASE_PREFIX}-${name}-${var.ROUTE_RESOURCE_SUFFIX}-key-auth", + length(route.allowed_consumer_groups) > 0 ? "${var.RELEASE_PREFIX}-${name}-${var.ROUTE_RESOURCE_SUFFIX}-acl" : "", "${var.RELEASE_PREFIX}-${name}-${var.ROUTE_RESOURCE_SUFFIX}-prometheus" ])) } @@ -66,6 +72,41 @@ locals { name => "${var.RELEASE_PREFIX}-${name}-${var.ROUTE_RESOURCE_SUFFIX}-key-auth" } + consumer_group_credentials = { + for credential in flatten([ + for consumer_name, consumer in var.CONSUMERS : [ + for group in consumer.consumer_groups : { + key = "${consumer_name}/${group}" + consumer_name = consumer_name + group = group + } + ] + ]) : credential.key => credential + } + + consumer_acl_credential_secret_names = { + for key, credential in local.consumer_group_credentials : + key => "${var.RELEASE_PREFIX}-${credential.consumer_name}-${credential.group}-${var.ROUTE_RESOURCE_SUFFIX}-acl" + } + + anonymous_consumer_group_credentials = { + for credential in flatten([ + for route_name, route in local.routes_with_anonymous : [ + for group in route.allowed_consumer_groups : { + key = "${route_name}/${group}" + route_name = route_name + route_namespace = route.route_namespace + group = group + } + ] + ]) : credential.key => credential + } + + anonymous_consumer_acl_credential_secret_names = { + for key, credential in local.anonymous_consumer_group_credentials : + key => "${var.RELEASE_PREFIX}-${credential.route_name}-anonymous-${credential.group}-${var.ROUTE_RESOURCE_SUFFIX}-acl" + } + consumers_with_rate_limit = { for name, consumer in var.CONSUMERS : name => consumer @@ -320,6 +361,29 @@ resource "kubernetes_manifest" "key_auth_plugin" { depends_on = [helm_release.kong, kubernetes_manifest.anonymous_consumer] } +resource "kubernetes_manifest" "acl_plugin" { + for_each = local.routes_with_consumer_groups + + manifest = { + apiVersion = "configuration.konghq.com/v1" + kind = "KongPlugin" + metadata = { + name = "${var.RELEASE_PREFIX}-${each.key}-${var.ROUTE_RESOURCE_SUFFIX}-acl" + namespace = each.value.route_namespace + annotations = { + "kubernetes.io/ingress.class" = local.kong_ingress_class + } + } + plugin = "acl" + config = { + allow = each.value.allowed_consumer_groups + hide_groups_header = true + } + } + + depends_on = [helm_release.kong] +} + resource "kubernetes_manifest" "prometheus_plugin" { for_each = var.ROUTES @@ -467,6 +531,28 @@ resource "kubernetes_manifest" "consumer_key_external_secret" { depends_on = [helm_release.kong] } +resource "kubernetes_manifest" "consumer_acl_secret" { + for_each = local.consumer_group_credentials + + manifest = { + apiVersion = "v1" + kind = "Secret" + metadata = { + name = local.consumer_acl_credential_secret_names[each.key] + namespace = var.CONSUMER_NAMESPACE + labels = { + "konghq.com/credential" = "acl" + } + } + type = "Opaque" + stringData = { + group = each.value.group + } + } + + depends_on = [helm_release.kong] +} + resource "kubernetes_manifest" "consumer" { for_each = var.CONSUMERS @@ -488,12 +574,16 @@ resource "kubernetes_manifest" "consumer" { } } : {} ) - username = each.value.username != "" ? each.value.username : each.key - credentials = [local.consumer_credential_secret_names[each.key]] + username = each.value.username != "" ? each.value.username : each.key + credentials = concat( + [local.consumer_credential_secret_names[each.key]], + [for group in each.value.consumer_groups : local.consumer_acl_credential_secret_names["${each.key}/${group}"]] + ) } depends_on = [ kubernetes_manifest.consumer_key_external_secret, + kubernetes_manifest.consumer_acl_secret, kubernetes_manifest.consumer_rate_limit_plugin, ] } @@ -523,24 +613,54 @@ resource "kubernetes_manifest" "anonymous_rate_limit_plugin" { depends_on = [helm_release.kong] } -resource "kubernetes_manifest" "anonymous_consumer" { - for_each = local.routes_with_anonymous +resource "kubernetes_manifest" "anonymous_consumer_acl_secret" { + for_each = local.anonymous_consumer_group_credentials manifest = { - apiVersion = "configuration.konghq.com/v1" - kind = "KongConsumer" + apiVersion = "v1" + kind = "Secret" metadata = { - name = "${var.RELEASE_PREFIX}-${each.key}-anonymous" + name = local.anonymous_consumer_acl_credential_secret_names[each.key] namespace = each.value.route_namespace - annotations = { - "kubernetes.io/ingress.class" = local.kong_ingress_class - "konghq.com/plugins" = "${var.RELEASE_PREFIX}-${each.key}-anonymous-${var.ROUTE_RESOURCE_SUFFIX}-rate-limit" + labels = { + "konghq.com/credential" = "acl" } } - username = "${var.RELEASE_PREFIX}-${each.key}-anonymous" + type = "Opaque" + stringData = { + group = each.value.group + } } - depends_on = [kubernetes_manifest.anonymous_rate_limit_plugin] + depends_on = [helm_release.kong] +} + +resource "kubernetes_manifest" "anonymous_consumer" { + for_each = local.routes_with_anonymous + + manifest = merge( + { + apiVersion = "configuration.konghq.com/v1" + kind = "KongConsumer" + metadata = { + name = "${var.RELEASE_PREFIX}-${each.key}-anonymous" + namespace = each.value.route_namespace + annotations = { + "kubernetes.io/ingress.class" = local.kong_ingress_class + "konghq.com/plugins" = "${var.RELEASE_PREFIX}-${each.key}-anonymous-${var.ROUTE_RESOURCE_SUFFIX}-rate-limit" + } + } + username = "${var.RELEASE_PREFIX}-${each.key}-anonymous" + }, + length(each.value.allowed_consumer_groups) > 0 ? { + credentials = [for group in each.value.allowed_consumer_groups : local.anonymous_consumer_acl_credential_secret_names["${each.key}/${group}"]] + } : {} + ) + + depends_on = [ + kubernetes_manifest.anonymous_rate_limit_plugin, + kubernetes_manifest.anonymous_consumer_acl_secret, + ] } resource "kubernetes_service_v1" "metrics" { @@ -633,6 +753,7 @@ resource "kubernetes_manifest" "rpc_route" { depends_on = [ kubernetes_manifest.path_api_key_plugin, kubernetes_manifest.key_auth_plugin, + kubernetes_manifest.acl_plugin, kubernetes_manifest.prometheus_plugin, kubernetes_manifest.upstream_policy, ] diff --git a/spartan/terraform/modules/rpc-gateway/variables.tf b/spartan/terraform/modules/rpc-gateway/variables.tf index e205bc94822e..84ebe7c48455 100644 --- a/spartan/terraform/modules/rpc-gateway/variables.tf +++ b/spartan/terraform/modules/rpc-gateway/variables.tf @@ -170,6 +170,7 @@ variable "ROUTES" { path = optional(string, "/") path_type = optional(string, "Prefix") strip_path = optional(bool, false) + allowed_consumer_groups = optional(list(string), []) })) validation { @@ -212,6 +213,7 @@ variable "CONSUMERS" { username = string gcp_secret_manager_secret_name = string rate_limit_minute = number + consumer_groups = optional(list(string), []) })) default = {} } From c325472474626214e10fb2e782fa0a16a14502a3 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Jul 2026 13:46:40 +0100 Subject: [PATCH 3/5] chore: update staging api keys (#24533) . --- spartan/environments/staging-internal.env | 2 +- spartan/environments/staging-public.env | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/spartan/environments/staging-internal.env b/spartan/environments/staging-internal.env index 481ceb99fce4..69da3a5a1fd6 100644 --- a/spartan/environments/staging-internal.env +++ b/spartan/environments/staging-internal.env @@ -60,7 +60,7 @@ RPC_REPLICAS=1 RPC_GATEWAY_ENABLED=true RPC_GATEWAY_KONG_OTEL_METRICS_GCP_SECRET_NAME=otel-collector-url RPC_GATEWAY_HOSTS='["staging-internal.rpc.aztec-labs.com"]' -RPC_GATEWAY_API_KEY_SECRET_NAMES='["staging-rpc-internal-api-key"]' +RPC_GATEWAY_API_KEY_SECRET_NAMES='["staging-rpc-consumer-client1"]' RPC_GATEWAY_ALLOW_ANONYMOUS=false # Prover diff --git a/spartan/environments/staging-public.env b/spartan/environments/staging-public.env index 4c8593f7a9eb..7749f4f3533a 100644 --- a/spartan/environments/staging-public.env +++ b/spartan/environments/staging-public.env @@ -57,6 +57,11 @@ VALIDATOR_PUBLISHER_MNEMONIC_START_INDEX=5000 VALIDATOR_RESOURCE_PROFILE="prod" RPC_REPLICAS=1 +RPC_GATEWAY_ENABLED=true +RPC_GATEWAY_KONG_OTEL_METRICS_GCP_SECRET_NAME=otel-collector-url +RPC_GATEWAY_HOSTS='["staging-public.rpc.aztec-labs.com"]' +RPC_GATEWAY_API_KEY_SECRET_NAMES='["staging-rpc-consumer-client1"]' +RPC_GATEWAY_ALLOW_ANONYMOUS=false # Prover PROVER_RESOURCE_PROFILE="prod" From af421476183b98bf8bf11f29e7cdaf390723e351 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Jul 2026 16:36:57 +0100 Subject: [PATCH 4/5] chore: roll pods only when necessary (#24540) . --- spartan/aztec-node/templates/_pod-template.yaml | 12 ++++++++++-- spartan/aztec-node/values.yaml | 1 + spartan/terraform/deploy-aztec-infra/main.tf | 4 ---- 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/spartan/aztec-node/templates/_pod-template.yaml b/spartan/aztec-node/templates/_pod-template.yaml index 6837e0f5bba5..45a0853c93e9 100644 --- a/spartan/aztec-node/templates/_pod-template.yaml +++ b/spartan/aztec-node/templates/_pod-template.yaml @@ -7,8 +7,16 @@ metadata: {{- include "chart.selectorLabels" . | nindent 4 }} app: node annotations: - {{- /* Recreate the pods on every upgrade in order to refresh configmaps and secrets */}} - rollme: {{ randAlphaNum 5 | quote }} + checksum/scripts: {{ include (print $.Template.BasePath "/scripts.configmap.yaml") . | sha256sum | quote }} + {{- if .Values.node.secret.envEnabled }} + checksum/env-secret: {{ include (print $.Template.BasePath "/env.secret.yaml") . | sha256sum | quote }} + {{- end }} + {{- if .Values.node.configMap.envEnabled }} + checksum/env-config: {{ toJson .Values | sha256sum | quote }} + {{- end }} + {{- with .Values.node.podAnnotations }} + {{- toYaml . | nindent 4 }} + {{- end }} spec: {{- if .Values.serviceAccount.create }} serviceAccountName: {{ include "chart.serviceAccountName" . }} diff --git a/spartan/aztec-node/values.yaml b/spartan/aztec-node/values.yaml index 1ffd0f47c15c..c3acbda3ad7c 100644 --- a/spartan/aztec-node/values.yaml +++ b/spartan/aztec-node/values.yaml @@ -87,6 +87,7 @@ node: tag: "" env: {} + podAnnotations: {} envFrom: configMapEnabled: false diff --git a/spartan/terraform/deploy-aztec-infra/main.tf b/spartan/terraform/deploy-aztec-infra/main.tf index 0be9f9875ff0..760986c26fd0 100644 --- a/spartan/terraform/deploy-aztec-infra/main.tf +++ b/spartan/terraform/deploy-aztec-infra/main.tf @@ -728,8 +728,6 @@ resource "helm_release" "p2p_bootstrap" { namespace = var.NAMESPACE create_namespace = true upgrade_install = true - force_update = true - recreate_pods = true reuse_values = false timeout = lookup(local.p2p_bootstrap_release, "timeout", 600) wait = local.p2p_bootstrap_release.wait @@ -772,8 +770,6 @@ resource "helm_release" "releases" { namespace = var.NAMESPACE create_namespace = true upgrade_install = true - force_update = true - recreate_pods = true reuse_values = false timeout = lookup(each.value, "timeout", 600) wait = each.value.wait From 7027e22dc07042bc919b1911e725a3de15cb18cc Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 6 Jul 2026 17:22:43 +0100 Subject: [PATCH 5/5] fix: cors (#24544) Fix A-1402 --- spartan/terraform/deploy-rpc/README.md | 2 ++ spartan/terraform/modules/rpc-gateway/main.tf | 36 +++++++++++++++++++ .../terraform/modules/rpc-gateway/outputs.tf | 5 +++ 3 files changed, 43 insertions(+) diff --git a/spartan/terraform/deploy-rpc/README.md b/spartan/terraform/deploy-rpc/README.md index 068a075dc1cf..c5d3a2b3478a 100644 --- a/spartan/terraform/deploy-rpc/README.md +++ b/spartan/terraform/deploy-rpc/README.md @@ -21,3 +21,5 @@ RPC node environment is configured through each RPC entry's single `env` map. Co API key consumers are Terraform inputs, but API key values are not. For each `CONSUMERS` entry, provide `gcp_secret_manager_secret_name`. Set `ALLOW_ANONYMOUS = true` on the environment module to allow anonymous usage, with `ANONYMOUS_RATE_LIMIT_MINUTE` controlling rate limit. RPC gateway routes accept `https://host/` in addition to the configured API key header. Kong copies the first path segment into the auth header before `key-auth` runs, then strips that segment before proxying to the upstream service. + +Kong answers browser CORS preflights for gateway routes with wildcard origins and allows the configured API key header. diff --git a/spartan/terraform/modules/rpc-gateway/main.tf b/spartan/terraform/modules/rpc-gateway/main.tf index fd844668318c..f7381e774802 100644 --- a/spartan/terraform/modules/rpc-gateway/main.tf +++ b/spartan/terraform/modules/rpc-gateway/main.tf @@ -48,9 +48,16 @@ locals { if length(route.allowed_consumer_groups) > 0 } + default_cors_allowed_headers = distinct(compact([ + "Accept", + "Content-Type", + var.API_KEY_HEADER_NAME, + ])) + route_plugin_names = { for name, route in var.ROUTES : name => join(",", compact([ + "${var.RELEASE_PREFIX}-${name}-${var.ROUTE_RESOURCE_SUFFIX}-cors", "${var.RELEASE_PREFIX}-${name}-${var.ROUTE_RESOURCE_SUFFIX}-path-api-key", "${var.RELEASE_PREFIX}-${name}-${var.ROUTE_RESOURCE_SUFFIX}-key-auth", length(route.allowed_consumer_groups) > 0 ? "${var.RELEASE_PREFIX}-${name}-${var.ROUTE_RESOURCE_SUFFIX}-acl" : "", @@ -330,6 +337,34 @@ resource "kubernetes_manifest" "path_api_key_plugin" { depends_on = [helm_release.kong] } +resource "kubernetes_manifest" "cors_plugin" { + for_each = var.ROUTES + + manifest = { + apiVersion = "configuration.konghq.com/v1" + kind = "KongPlugin" + metadata = { + name = "${var.RELEASE_PREFIX}-${each.key}-${var.ROUTE_RESOURCE_SUFFIX}-cors" + namespace = each.value.route_namespace + annotations = { + "kubernetes.io/ingress.class" = local.kong_ingress_class + } + } + plugin = "cors" + config = { + origins = ["*"] + methods = ["GET", "POST", "OPTIONS"] + headers = local.default_cors_allowed_headers + exposed_headers = [] + credentials = false + max_age = 3600 + preflight_continue = false + } + } + + depends_on = [helm_release.kong] +} + resource "kubernetes_manifest" "key_auth_plugin" { for_each = var.ROUTES @@ -751,6 +786,7 @@ resource "kubernetes_manifest" "rpc_route" { } depends_on = [ + kubernetes_manifest.cors_plugin, kubernetes_manifest.path_api_key_plugin, kubernetes_manifest.key_auth_plugin, kubernetes_manifest.acl_plugin, diff --git a/spartan/terraform/modules/rpc-gateway/outputs.tf b/spartan/terraform/modules/rpc-gateway/outputs.tf index 07486957b9a7..f7a081ac5403 100644 --- a/spartan/terraform/modules/rpc-gateway/outputs.tf +++ b/spartan/terraform/modules/rpc-gateway/outputs.tf @@ -28,6 +28,11 @@ output "path_api_key_plugin_names" { value = { for name, plugin in kubernetes_manifest.path_api_key_plugin : name => plugin.manifest.metadata.name } } +output "cors_plugin_names" { + description = "KongPlugin names for CORS handling, keyed by route." + value = { for name, plugin in kubernetes_manifest.cors_plugin : name => plugin.manifest.metadata.name } +} + output "prometheus_plugin_names" { description = "KongPlugin names for per-consumer Prometheus metrics, keyed by route." value = { for name, plugin in kubernetes_manifest.prometheus_plugin : name => plugin.manifest.metadata.name }