From 2900687dc4b127a9fe101bc7035ae8f847dfcc47 Mon Sep 17 00:00:00 2001 From: Alper Gundogdu Date: Fri, 11 Sep 2026 10:14:21 +0100 Subject: [PATCH 1/2] chore(decdn_node): sync config-schema surface to decdn main @ 0b94efe4 Re-sync the decdn_node role's node.toml key surface to the current `main` of decdn/decdn (crates/common/src/config/types.rs). Every config section is `#[serde(deny_unknown_fields)]` with no serde aliases, so a stale key is a startup crash-loop. Field delta (verified against types.rs): - REMOVE: the whole `[blockchain]` swap block (swap_venue, swap_router_address, swap_quoter_address, swap_fee_tier, swap_balancer_pool, swap_pool_address) and pool_floor_signer_share_bps. - RENAME: pool_floor_signer_max_windows -> pool_floor_signer_live_windows (default now 8; lower-clamped to one window). - ADD (new optional knobs): blockchain.chain_staleness_grace_sec, cache.disk_headroom_mb, and the new `[client]` section's region_allowlist (ClientConfig). Touched: templates/node.toml.j2 (emit blocks), defaults/main.yml (vars), tasks/main.yml (asserts), README.md (knob docs), and the molecule/schema oracle: gen-schema-keys.py (add ClientConfig, bump header to 0b94efe4), regenerated files/schema-keys.txt (157 paths), converge.yml (set the new knobs, drop the removed ones), verify.yml (add `client` to the expected table set). Validated by rendering node.toml.j2 with the schema converge var set via Ansible's own template engine and running molecule/schema's exact drift-checker + tomllib parse against the regenerated schema-keys.txt: 155 emitted paths, all recognised; no removed key emitted. Co-Authored-By: Claude Opus 4.8 --- ansible/molecule/schema/converge.yml | 14 +++---- .../molecule/schema/files/gen-schema-keys.py | 3 +- ansible/molecule/schema/files/schema-keys.txt | 14 +++---- ansible/molecule/schema/verify.yml | 1 + ansible/roles/decdn_node/README.md | 29 +++++++++----- ansible/roles/decdn_node/defaults/main.yml | 36 +++++++++++------ ansible/roles/decdn_node/tasks/main.yml | 40 +++++++++++-------- .../roles/decdn_node/templates/node.toml.j2 | 36 ++++++----------- 8 files changed, 92 insertions(+), 81 deletions(-) diff --git a/ansible/molecule/schema/converge.yml b/ansible/molecule/schema/converge.yml index 4ab2754..f946194 100644 --- a/ansible/molecule/schema/converge.yml +++ b/ansible/molecule/schema/converge.yml @@ -43,16 +43,11 @@ decdn_origin_assignment_address: "0x6666666666666666666666666666666666666666" decdn_publisher_registry_address: "0x7777777777777777777777777777777777777777" decdn_usdc_address: "0x8888888888888888888888888888888888888888" - decdn_swap_venue: "uniswap-v3" - decdn_swap_router_address: "0x9999999999999999999999999999999999999999" - decdn_swap_quoter_address: "0xaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaAaA" - decdn_swap_fee_tier: 3000 - decdn_swap_balancer_pool: "0xbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbBbB" - decdn_swap_pool_address: "0xcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcCcC" decdn_origin_directory_positive_ttl_sec: 600 decdn_origin_directory_negative_ttl_sec: 60 decdn_origin_directory_cache_capacity: 8192 decdn_content_blacklist_poll_interval_sec: 300 + decdn_chain_staleness_grace_sec: 900 decdn_rpc_watchdog_interval_sec: 15 decdn_event_poll_interval_ms: 5000 decdn_rate_bounds_poll_interval_sec: 1800 @@ -63,8 +58,7 @@ decdn_buyer_working_deposit_micro_usdc: 20000000 decdn_buyer_max_approve: false decdn_pool_min_remaining_deposit_micro_usdc: 500000 - decdn_pool_floor_signer_share_bps: 3000 - decdn_pool_floor_signer_max_windows: 4 + decdn_pool_floor_signer_live_windows: 4 # --- [payment] --- decdn_rate_per_mb: 12 @@ -76,6 +70,7 @@ # --- [cache] scalars --- decdn_cache_size_mb: 20480 + decdn_disk_headroom_mb: 4096 decdn_max_blob_size_mb: 2048 decdn_max_rate_per_mb: 50 decdn_pinned_hashes: ["aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"] @@ -171,6 +166,9 @@ decdn_receipts_retained_files: 8 decdn_content_denied_hashes: ["bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"] decdn_content_denied_origins: ["0x000000000000000000000000000000000000dEaD"] + + # --- [client] --- + decdn_client_region_allowlist: ["US", "DE"] roles: - role: decdn_node diff --git a/ansible/molecule/schema/files/gen-schema-keys.py b/ansible/molecule/schema/files/gen-schema-keys.py index 10d91f3..a4ab364 100755 --- a/ansible/molecule/schema/files/gen-schema-keys.py +++ b/ansible/molecule/schema/files/gen-schema-keys.py @@ -28,6 +28,7 @@ STRUCT_PATHS = { "FileConfig": None, "IdentityConfig": "identity", + "ClientConfig": "client", "NetworkConfig": "network", "DiscoveryConfig": "network.discovery", # Keyed by NodeId, so the concrete hop is a wildcard. @@ -134,7 +135,7 @@ def main(): "# a leaf-name inventory cannot tell the two apart. See gen-schema-keys.py for\n" "# the struct -> path mapping and molecule/schema/README.md for why this exists.\n" "#\n" - f"# Synced from decdn/decdn @ d306cc5c (crate version 0.1.1): {len(paths)} paths." + f"# Synced from decdn/decdn @ 0b94efe4 (crate version 0.0.0): {len(paths)} paths." ) for path in sorted(paths): print(path) diff --git a/ansible/molecule/schema/files/schema-keys.txt b/ansible/molecule/schema/files/schema-keys.txt index 3364b49..dd791ea 100644 --- a/ansible/molecule/schema/files/schema-keys.txt +++ b/ansible/molecule/schema/files/schema-keys.txt @@ -9,11 +9,12 @@ # a leaf-name inventory cannot tell the two apart. See gen-schema-keys.py for # the struct -> path mapping and molecule/schema/README.md for why this exists. # -# Synced from decdn/decdn @ d306cc5c (crate version 0.1.1): 161 paths. +# Synced from decdn/decdn @ 0b94efe4 (crate version 0.0.0): 157 paths. blockchain.buyer_max_approve blockchain.buyer_working_deposit_micro_usdc blockchain.capacity_bond_address blockchain.chain_id +blockchain.chain_staleness_grace_sec blockchain.content_blacklist_address blockchain.content_blacklist_poll_interval_sec blockchain.eth_keystore @@ -24,8 +25,7 @@ blockchain.origin_directory_cache_capacity blockchain.origin_directory_negative_ttl_sec blockchain.origin_directory_positive_ttl_sec blockchain.payment_pool_address -blockchain.pool_floor_signer_max_windows -blockchain.pool_floor_signer_share_bps +blockchain.pool_floor_signer_live_windows blockchain.pool_min_remaining_deposit_micro_usdc blockchain.publisher_registry_address blockchain.rate_bounds_poll_interval_sec @@ -36,12 +36,6 @@ blockchain.rpc_url blockchain.rpc_watchdog_interval_sec blockchain.slash_appeal_address blockchain.slash_judge_address -blockchain.swap_balancer_pool -blockchain.swap_fee_tier -blockchain.swap_pool_address -blockchain.swap_quoter_address -blockchain.swap_router_address -blockchain.swap_venue blockchain.usdc_address cache.admission_policy cache.cache_dir @@ -51,6 +45,7 @@ cache.circuit_breaker.cooldown_ms cache.circuit_breaker.enabled cache.circuit_breaker.failure_threshold cache.circuit_breaker.half_open_max_calls +cache.disk_headroom_mb cache.eviction_high_water_pct cache.eviction_per_sweep_budget cache.eviction_policy @@ -118,6 +113,7 @@ cache.tinylfu.probation_target_pct cache.tinylfu.promotion_threshold cache.tinylfu.sketch_bytes cache.user_agent +client.region_allowlist content.denied_hashes content.denied_origins dht.rate_limit diff --git a/ansible/molecule/schema/verify.yml b/ansible/molecule/schema/verify.yml index 99841d5..e57b4d5 100644 --- a/ansible/molecule/schema/verify.yml +++ b/ansible/molecule/schema/verify.yml @@ -191,6 +191,7 @@ cache.origin_retry cache.serve_economics cache.tinylfu + client content dht.rate_limit identity diff --git a/ansible/roles/decdn_node/README.md b/ansible/roles/decdn_node/README.md index e35a316..080b528 100644 --- a/ansible/roles/decdn_node/README.md +++ b/ansible/roles/decdn_node/README.md @@ -128,12 +128,12 @@ Optional (omitted from `node.toml` unless set): or neither" rule went away with the whole-directory mirror): the daemon reads OriginAssignment for cache-miss pull-through fallback, and only validates PublisherRegistry, which `decdn publish` consumes. -- `decdn_usdc_address` and the `decdn_swap_*` knobs — CLI-only `[blockchain]` keys. - The daemon accepts but never parses them — only `decdn setup` reads them, to - swap USDC into the deCDN TOKEN and bond that (the bond is always TOKEN; native - ETH is only ever gas). They live in `node.toml` because the section is - `deny_unknown_fields` and the CLI shares the file, which also means nothing - downstream catches a malformed value — hence the role's own shape asserts. +- `decdn_usdc_address` — a CLI-only `[blockchain]` key. The daemon accepts but + never parses it — only `decdn setup` / `decdn pool` read it. It lives in + `node.toml` because the section is `deny_unknown_fields` and the CLI shares the + file, which also means nothing downstream catches a malformed value — hence the + role's own shape assert. (The former `decdn_swap_*` knobs for the on-chain swap + path were removed upstream.) - `decdn_cache_origin_kind` (`http`|`fs`|`s3`) + that kind's fields — the pull-through origin the node fetches on a cache miss. **A serving node needs one:** unset ⇒ no `[cache.origin]` and cache misses fail `NoOrigin`. For an @@ -175,10 +175,12 @@ asserts miss. See `defaults/main.yml` for every knob's upstream default, unit an - **Settlement / payment pool** — `decdn_redeem_threshold_micro_usdc`, `decdn_redeem_max_vouchers_per_tx`, `decdn_redeem_interval_secs`, `decdn_buyer_working_deposit_micro_usdc`, `decdn_buyer_max_approve` (bool), - `decdn_pool_min_remaining_deposit_micro_usdc`, and the per-signer floor pair - `decdn_pool_floor_signer_share_bps` (`1..=10000`) / - `decdn_pool_floor_signer_max_windows`. All µUSDC unless noted. -- **Cache sizing + eviction** — `decdn_cache_size_mb`, `decdn_max_blob_size_mb` + `decdn_pool_min_remaining_deposit_micro_usdc`, and the per-signer floor knob + `decdn_pool_floor_signer_live_windows` (live concurrency cap in credit windows; + daemon default `8`). All µUSDC unless noted. +- **Cache sizing + eviction** — `decdn_cache_size_mb`, `decdn_disk_headroom_mb` + (free disk the eviction driver keeps unused on the `cache_dir` volume; daemon + default `8192`, `0` opts out of the disk clamp), `decdn_max_blob_size_mb` (defaults to `cache_size_mb` upstream; must be `<=` it), `decdn_max_rate_per_mb` (buyer-side rate ceiling), `decdn_gc_interval_sec`, `decdn_fs_rescan_interval_sec`, `decdn_max_probe_holds`, `decdn_stake_lane_reserved_holds`, `decdn_pinned_hashes` @@ -208,7 +210,9 @@ asserts miss. See `defaults/main.yml` for every knob's upstream default, unit an `decdn_circuit_breaker_*` families. - **Blockchain watchers** — `decdn_rpc_watchdog_interval_sec` (`0` or `>= 10`), `decdn_event_poll_interval_ms` (`>= 250`), `decdn_content_blacklist_poll_interval_sec` - (`>= 1`), `decdn_rate_bounds_poll_interval_sec`, `decdn_fee_shares_poll_interval_sec`, + (`>= 1`), `decdn_chain_staleness_grace_sec` (seconds the node may go without a + successful chain read before it stops serving — ADR 011; `> 0`, daemon default + `1800`), `decdn_rate_bounds_poll_interval_sec`, `decdn_fee_shares_poll_interval_sec`, and the `decdn_origin_directory_*` cache knobs. - **Network** — `decdn_relay_urls` (list; the singular `relay_url` config key no longer exists). Operator-run address discovery (#818) via @@ -217,6 +221,9 @@ asserts miss. See `defaults/main.yml` for every knob's upstream default, unit an **on its own**, a resolve-only node that never publishes; and/or `decdn_discovery_peers` (a map of 64-char lowercase-hex NodeId to `{relay_url, addrs}`). Setting either mechanism drops the n0 discovery leg. + `decdn_client_region_allowlist` (a `[client]` knob) narrows which regions this + node discovers/probes **as a client** to a list of ISO 3166-1 alpha-2 codes; + `[]` ⇒ no filter. - **Abuse limits + load shedding** — the `decdn_security_*` family, `decdn_load_shed_*` (`policy` is `resource-pressure`|`always-admit`; the low-water serve mark must be `<=` the high), and the `decdn_dht_rate_limit` / diff --git a/ansible/roles/decdn_node/defaults/main.yml b/ansible/roles/decdn_node/defaults/main.yml index 6be20ee..99a7c92 100644 --- a/ansible/roles/decdn_node/defaults/main.yml +++ b/ansible/roles/decdn_node/defaults/main.yml @@ -119,32 +119,36 @@ decdn_buyer_working_deposit_micro_usdc: "" # daemon dflt 10_000_000 (10 USDC); # pinning it here changes client behaviour too. One-time max USDC approval to PaymentPool. decdn_buyer_max_approve: "" decdn_pool_min_remaining_deposit_micro_usdc: "" # daemon dflt 1_000_000 — solvency floor before a pool drains -# Per-signer floor isolation under the per-pool solvency ceiling (#1841). -decdn_pool_floor_signer_share_bps: "" # daemon dflt 2500; range 1..=10000 -decdn_pool_floor_signer_max_windows: "" # daemon dflt 0 (off) +# Per-signer LIVE concurrency floor isolation under the per-pool solvency ceiling +# (ADR 003 §Pool solvency, per-signer floor isolation). Counted in ramp-start credit +# windows: the most live un-vouchered floor reservation any one signer may hold. +decdn_pool_floor_signer_live_windows: "" # daemon dflt 8; lower-clamped to 1 window # --- Blockchain watcher tuning ("" => daemon default) ------------------------- decdn_rpc_watchdog_interval_sec: "" # daemon dflt 30; 0 disables, else >= 10 — RPC watchdog decdn_event_poll_interval_ms: "" # daemon dflt 7000; >= 250 — multiplexed getLogs poll decdn_content_blacklist_poll_interval_sec: "" # daemon dflt 600; >= 1 — blacklist re-scope cadence +# Seconds the node may go without a successful chain read before it stops serving +# (ADR 011 §Serving while chain-stale). Always evaluated (no on/off flag) and only +# consulted when content_blacklist_address is set; set it large to opt out. +decdn_chain_staleness_grace_sec: "" # daemon dflt 1800 (30 min); must be > 0 decdn_rate_bounds_poll_interval_sec: "" # daemon dflt 3600; > 0 — PaymentPool.getRateBounds refresh decdn_fee_shares_poll_interval_sec: "" # daemon dflt 3600; > 0 — FeeRouter share refresh # --- CLI-only [blockchain] keys ---------------------------------------------- -# The daemon ACCEPTS these (they are declared so a config that also drives the CLI -# still passes [blockchain]'s deny_unknown_fields) but never parses or reads them — -# only `decdn setup` does. So nothing downstream catches a malformed value, which -# is exactly why the role asserts their shape itself. +# The daemon ACCEPTS usdc_address (declared so a config that also drives the CLI +# still passes [blockchain]'s deny_unknown_fields) but never parses or reads it — +# only `decdn setup` / `decdn pool` do. So nothing downstream catches a malformed +# value, which is exactly why the role asserts its shape itself. (The former +# decdn_swap_* knobs were removed upstream along with the on-chain swap path.) decdn_usdc_address: "" -decdn_swap_venue: "" # "uniswap-v3" | "balancer-v3" -decdn_swap_router_address: "" -decdn_swap_quoter_address: "" -decdn_swap_fee_tier: "" # e.g. 3000 -decdn_swap_balancer_pool: "" -decdn_swap_pool_address: "" # --- Cache -------------------------------------------------------------------- decdn_cache_size_mb: 10240 # 10 GB +# Free disk (MB) the eviction driver keeps unused on the cache_dir volume, defended +# against ANY process, not just this cache (#1930). "" => daemon dflt 8192 (8 GiB); +# 0 opts out of the disk clamp so only cache_size_mb binds. +decdn_disk_headroom_mb: "" # NOTE: this is a deliberate OVERRIDE, not the daemon default. Upstream defaults # max_blob_size_mb to cache_size_mb; 1 GB caps any single blob at a tenth of the # cache so one large object cannot evict everything else. Must be <= cache_size_mb. @@ -291,6 +295,12 @@ decdn_receipts_retained_files: "" # daemon dflt 4; cap 100 decdn_content_denied_hashes: [] # 64-char lowercase-hex BLAKE3 hashes decdn_content_denied_origins: [] # 0x publisher addresses (checksum-agnostic; zero address rejected) +# --- Client-scoped discovery ([client]) --------------------------------------- +# Discovery-time region filter: narrows which regions this node discovers/probes as +# a CLIENT (node.toml is shared with the CLI). [] => no filter (every registered +# node considered). ISO 3166-1 alpha-2 codes, e.g. ["US", "DE"]. +decdn_client_region_allowlist: [] + # --- Readiness probe (advisory) ----------------------------------------------- # After start, the role probes http://127.0.0.1:/metrics as a # best-effort readiness signal. A probe TIMEOUT only WARNS (it does NOT fail the diff --git a/ansible/roles/decdn_node/tasks/main.yml b/ansible/roles/decdn_node/tasks/main.yml index 21b2e3f..514f85d 100644 --- a/ansible/roles/decdn_node/tasks/main.yml +++ b/ansible/roles/decdn_node/tasks/main.yml @@ -304,9 +304,10 @@ label: "{{ item.name }}" when: item.value | length > 0 -# The CLI-only [blockchain] address keys (usdc + swap venue). The daemon validates -# them under deny_unknown_fields even though only `decdn setup` / `decdn pool` read -# them, so a malformed value fails the daemon's own startup — catch it here first. +# The CLI-only [blockchain] address key (usdc_address). The daemon validates it +# under deny_unknown_fields even though only `decdn setup` / `decdn pool` read it, +# so a malformed value fails the daemon's own startup — catch it here first. (The +# former swap_* address keys were removed upstream with the on-chain swap path.) - name: Validate the optional CLI-only contract addresses when set ansible.builtin.assert: that: @@ -319,10 +320,6 @@ decdn_zero_address: "0x0000000000000000000000000000000000000000" loop: - {name: decdn_usdc_address, value: "{{ decdn_usdc_address }}"} - - {name: decdn_swap_router_address, value: "{{ decdn_swap_router_address }}"} - - {name: decdn_swap_quoter_address, value: "{{ decdn_swap_quoter_address }}"} - - {name: decdn_swap_balancer_pool, value: "{{ decdn_swap_balancer_pool }}"} - - {name: decdn_swap_pool_address, value: "{{ decdn_swap_pool_address }}"} loop_control: label: "{{ item.name }}" when: item.value | length > 0 @@ -506,13 +503,13 @@ decdn_frame_target_bytes, decdn_voucher_commit_interval_ms, decdn_redeem_threshold_micro_usdc, decdn_redeem_max_vouchers_per_tx, decdn_redeem_interval_secs, decdn_buyer_working_deposit_micro_usdc, - decdn_pool_min_remaining_deposit_micro_usdc, decdn_pool_floor_signer_share_bps, - decdn_pool_floor_signer_max_windows, decdn_rpc_watchdog_interval_sec, + decdn_pool_min_remaining_deposit_micro_usdc, decdn_pool_floor_signer_live_windows, + decdn_chain_staleness_grace_sec, decdn_rpc_watchdog_interval_sec, decdn_event_poll_interval_ms, decdn_content_blacklist_poll_interval_sec, decdn_rate_bounds_poll_interval_sec, decdn_fee_shares_poll_interval_sec, decdn_origin_directory_positive_ttl_sec, decdn_origin_directory_negative_ttl_sec, - decdn_origin_directory_cache_capacity, decdn_swap_fee_tier, - decdn_max_blob_size_mb, decdn_max_rate_per_mb, decdn_max_probe_holds, + decdn_origin_directory_cache_capacity, + decdn_max_blob_size_mb, decdn_disk_headroom_mb, decdn_max_rate_per_mb, decdn_max_probe_holds, decdn_stake_lane_reserved_holds, decdn_gc_interval_sec, decdn_fs_rescan_interval_sec, decdn_origin_probe_ttl_sec, decdn_origin_probe_negative_ttl_sec, decdn_origin_probe_fault_ttl_sec, @@ -611,7 +608,6 @@ allowed: ["off", "margin"]} - {name: decdn_load_shed_policy, value: "{{ decdn_load_shed_policy }}", allowed: ["resource-pressure", "always-admit"]} - - {name: decdn_swap_venue, value: "{{ decdn_swap_venue }}", allowed: ["uniswap-v3", "balancer-v3"]} - {name: decdn_log_format, value: "{{ decdn_log_format }}", allowed: ["pretty", "json"]} - {name: decdn_log_level, value: "{{ decdn_log_level }}", allowed: ["trace", "debug", "info", "warn", "error"]} @@ -632,6 +628,11 @@ - >- decdn_content_blacklist_poll_interval_sec in ["", none] or (decdn_content_blacklist_poll_interval_sec | int >= 1) + # chain_staleness_grace_sec must be > 0 (0 would refuse every serve — rejected + # upstream). Set it large to opt out of the stale-serve guard. + - >- + decdn_chain_staleness_grace_sec in ["", none] + or (decdn_chain_staleness_grace_sec | int >= 1) # Knobs the daemon requires strictly positive (a configured 0 is rejected — # omit the knob instead to take the default). - item in ["", none] or (item | int >= 1) @@ -640,7 +641,8 @@ An optional knob is out of the range the daemon accepts: decdn_rpc_watchdog_interval_sec (0 or >= 10), decdn_event_poll_interval_ms (>= 250), - decdn_content_blacklist_poll_interval_sec (>= 1); and each of + decdn_content_blacklist_poll_interval_sec (>= 1), + decdn_chain_staleness_grace_sec (>= 1); and each of decdn_redeem_threshold_micro_usdc, decdn_redeem_max_vouchers_per_tx, decdn_redeem_interval_secs, decdn_buyer_working_deposit_micro_usdc, decdn_rate_bounds_poll_interval_sec, decdn_fee_shares_poll_interval_sec, @@ -667,8 +669,6 @@ - {name: decdn_eviction_per_sweep_budget, value: "{{ decdn_eviction_per_sweep_budget }}", min: 1, max: 256} - {name: decdn_eviction_tick_secs, value: "{{ decdn_eviction_tick_secs }}", min: 1, max: 60} - {name: decdn_frame_target_bytes, value: "{{ decdn_frame_target_bytes }}", min: 1, max: 1048576} - - {name: decdn_pool_floor_signer_share_bps, value: "{{ decdn_pool_floor_signer_share_bps }}", - min: 1, max: 10000} - {name: decdn_tinylfu_probation_target_pct, value: "{{ decdn_tinylfu_probation_target_pct }}", min: 1, max: 50} # sketch_bytes has a HARD FLOOR: below it the daemon errors out at config load. @@ -760,12 +760,20 @@ - >- decdn_content_denied_origins is none or '0x0000000000000000000000000000000000000000' not in decdn_content_denied_origins + # [client] region_allowlist: ISO 3166-1 alpha-2 codes. Upstream carries the raw + # strings and parses them into Region at the client's discovery boundary, so a + # malformed code fails there — validate the shape here to fail loud at deploy. + - >- + decdn_client_region_allowlist is none + or decdn_client_region_allowlist | reject('match', '^[A-Z]{2}$') | list | length == 0 fail_msg: >- decdn_relay_urls entries must each be an http(s):// URL with no quote, whitespace or backslash; decdn_pinned_hashes and decdn_content_denied_hashes entries must each be a 64-char lowercase-hex BLAKE3 hash (uppercase is rejected upstream); decdn_content_denied_origins entries must each be a - 0x + 40 hex address and none may be the zero address. Leave a list empty ([]). + 0x + 40 hex address and none may be the zero address; + decdn_client_region_allowlist entries must each be an uppercase ISO 3166-1 + alpha-2 code. Leave a list empty ([]). # Address discovery (#818): pkarr_url and dns_origin are a PAIR upstream — a custom # pkarr relay with no DNS origin cannot resolve peers. Static peers are independent diff --git a/ansible/roles/decdn_node/templates/node.toml.j2 b/ansible/roles/decdn_node/templates/node.toml.j2 index 1d3bcb8..c981265 100644 --- a/ansible/roles/decdn_node/templates/node.toml.j2 +++ b/ansible/roles/decdn_node/templates/node.toml.j2 @@ -73,6 +73,9 @@ origin_directory_cache_capacity = {{ decdn_origin_directory_cache_capacity | int {% if decdn_content_blacklist_poll_interval_sec not in ["", none] %} content_blacklist_poll_interval_sec = {{ decdn_content_blacklist_poll_interval_sec | int }} {% endif %} +{% if decdn_chain_staleness_grace_sec not in ["", none] %} +chain_staleness_grace_sec = {{ decdn_chain_staleness_grace_sec | int }} +{% endif %} {% if decdn_rpc_watchdog_interval_sec not in ["", none] %} rpc_watchdog_interval_sec = {{ decdn_rpc_watchdog_interval_sec | int }} {% endif %} @@ -103,33 +106,12 @@ buyer_max_approve = {{ decdn_buyer_max_approve | bool | lower }} {% if decdn_pool_min_remaining_deposit_micro_usdc not in ["", none] %} pool_min_remaining_deposit_micro_usdc = {{ decdn_pool_min_remaining_deposit_micro_usdc | int }} {% endif %} -{% if decdn_pool_floor_signer_share_bps not in ["", none] %} -pool_floor_signer_share_bps = {{ decdn_pool_floor_signer_share_bps | int }} -{% endif %} -{% if decdn_pool_floor_signer_max_windows not in ["", none] %} -pool_floor_signer_max_windows = {{ decdn_pool_floor_signer_max_windows | int }} +{% if decdn_pool_floor_signer_live_windows not in ["", none] %} +pool_floor_signer_live_windows = {{ decdn_pool_floor_signer_live_windows | int }} {% endif %} {% if decdn_usdc_address | length > 0 %} usdc_address = "{{ decdn_usdc_address }}" {% endif %} -{% if decdn_swap_venue not in ["", none] %} -swap_venue = "{{ decdn_swap_venue }}" -{% endif %} -{% if decdn_swap_router_address | length > 0 %} -swap_router_address = "{{ decdn_swap_router_address }}" -{% endif %} -{% if decdn_swap_quoter_address | length > 0 %} -swap_quoter_address = "{{ decdn_swap_quoter_address }}" -{% endif %} -{% if decdn_swap_fee_tier not in ["", none] %} -swap_fee_tier = {{ decdn_swap_fee_tier | int }} -{% endif %} -{% if decdn_swap_balancer_pool | length > 0 %} -swap_balancer_pool = "{{ decdn_swap_balancer_pool }}" -{% endif %} -{% if decdn_swap_pool_address | length > 0 %} -swap_pool_address = "{{ decdn_swap_pool_address }}" -{% endif %} [payment] rate_per_mb = {{ decdn_rate_per_mb }} @@ -154,6 +136,9 @@ voucher_commit_interval_ms = {{ decdn_voucher_commit_interval_ms | int }} [cache] cache_dir = "{{ decdn_cache_dir }}" cache_size_mb = {{ decdn_cache_size_mb }} +{% if decdn_disk_headroom_mb not in ["", none] %} +disk_headroom_mb = {{ decdn_disk_headroom_mb | int }} +{% endif %} {% if decdn_max_blob_size_mb not in ["", none] %} max_blob_size_mb = {{ decdn_max_blob_size_mb | int }} {% endif %} @@ -448,3 +433,8 @@ denied_hashes = [{% for h in decdn_content_denied_hashes %}"{{ h }}"{% if not lo denied_origins = [{% for a in decdn_content_denied_origins %}"{{ a }}"{% if not loop.last %}, {% endif %}{% endfor %}] {% endif %} {% endif %} +{% if decdn_client_region_allowlist is not none and decdn_client_region_allowlist | length > 0 %} + +[client] +region_allowlist = [{% for r in decdn_client_region_allowlist %}"{{ r }}"{% if not loop.last %}, {% endif %}{% endfor %}] +{% endif %} From eeda7c29c4a3faf925c0ae11464e614e41de8ba9 Mon Sep 17 00:00:00 2001 From: Alper Gundogdu Date: Fri, 11 Sep 2026 10:18:42 +0100 Subject: [PATCH 2/2] docs(decdn_node): update Schema tracking note to main @ 0b94efe4 The fixture provenance was bumped to main but the README's Schema tracking paragraph still cited d306cc5c / 0.1.1. Match it. Co-Authored-By: Claude Opus 4.8 --- ansible/roles/decdn_node/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ansible/roles/decdn_node/README.md b/ansible/roles/decdn_node/README.md index 080b528..53ccb05 100644 --- a/ansible/roles/decdn_node/README.md +++ b/ansible/roles/decdn_node/README.md @@ -12,7 +12,7 @@ machine. This is the repo's deployment (`playbooks/site.yml`). > download. Build the two binaries from a checkout until that changes. **Schema tracking.** This role renders `node.toml` against the config schema of -`decdn/decdn` @ `d306cc5c` (crate version 0.1.1). Upstream marks every config +`decdn/decdn` main @ `0b94efe4` (crate version 0.0.0 — unreleased). Upstream marks every config section `#[serde(deny_unknown_fields)]` and defines **no** serde aliases, so a key this role emits that your binary does not know is a startup crash-loop, not a warning. The role runs `decdn config validate` against the installed binary after