diff --git a/ansible/inventory/host_vars/decdn-node-1.yml.example b/ansible/inventory/host_vars/decdn-node-1.yml.example index 5af6254..85daf93 100644 --- a/ansible/inventory/host_vars/decdn-node-1.yml.example +++ b/ansible/inventory/host_vars/decdn-node-1.yml.example @@ -19,6 +19,10 @@ decdn_payment_channel_address: "0xREPLACE_PaymentChannel_FROM_DEPLOYMENT" decdn_capacity_bond_address: "0xREPLACE_CapacityBond_FROM_DEPLOYMENT" decdn_slash_judge_address: "0xREPLACE_SlashJudge_FROM_DEPLOYMENT" +# --- Slash appeals (optional; ADR 028) ---------------------------------------- +# decdn_slash_appeal_address: "0xREPLACE_SlashAppeal_FROM_DEPLOYMENT" # for `decdn appeal slash` +# decdn_slash_judge_from_block: 0 # SlashJudge deploy block — set to bound per-restart RPC rescan + # --- Node identity / locale --------------------------------------------------- decdn_region: "US" # ISO 3166-1 alpha-2 of the node's physical location # decdn_relay_url: "" # optional iroh relay for NAT traversal diff --git a/ansible/roles/decdn_node/README.md b/ansible/roles/decdn_node/README.md index 11b5fa7..6b86bac 100644 --- a/ansible/roles/decdn_node/README.md +++ b/ansible/roles/decdn_node/README.md @@ -50,7 +50,12 @@ Per `decdn/adr/019-node-onboarding.md`, a node only serves paid traffic after `decdn_payment_channel_address`, `decdn_capacity_bond_address`, `decdn_slash_judge_address` (all `0x`+40-hex; SlashJudge non-zero), `decdn_region` (ISO 3166-1 alpha-2). Contract addresses/chain-id are protocol -facts — source them from the deployment / an ADR, never guess. See +facts — source them from the deployment / an ADR, never guess. + +Optional (omitted from `node.toml` unless set): `decdn_slash_appeal_address` +(SlashAppeal contract, source from the deployment / ADR 028 — only needed to file +appeals with `decdn appeal slash`) and `decdn_slash_judge_from_block` (SlashJudge +deploy block; bounds the slash-detection watcher's per-restart chain rescan). See `roles/decdn_node/defaults/main.yml` for the full knob list and defaults. ## Network @@ -77,6 +82,11 @@ systemctl status decdn-node journalctl -u decdn-node -e decdn node health # admin RPC (127.0.0.1:9191) decdn node peers + +# If this node is slashed (surfaced via the admin RPC — admin_v1_slashes), file an +# appeal within the ADR 028 window. Needs decdn_slash_appeal_address set in node.toml +# (else pass --slash-appeal-address / DECDN_SLASH_APPEAL_ADDRESS): +decdn appeal slash ``` Upgrades: bump `decdn_node_version` (+ `decdn_node_sha256`) and re-deploy — the diff --git a/ansible/roles/decdn_node/defaults/main.yml b/ansible/roles/decdn_node/defaults/main.yml index 7200e9d..26f7377 100644 --- a/ansible/roles/decdn_node/defaults/main.yml +++ b/ansible/roles/decdn_node/defaults/main.yml @@ -18,6 +18,13 @@ decdn_rpc_url: "" # SENSITIVE; may embed an API key decdn_payment_channel_address: "" decdn_capacity_bond_address: "" decdn_slash_judge_address: "" +# Optional (ADR 028). SlashAppeal contract — only needed to file appeals with +# `decdn appeal slash`; the daemon ignores it. Empty => key omitted from node.toml. +decdn_slash_appeal_address: "" +# Optional. SlashJudge deploy block: the slash-detection watcher rescans from +# here on EVERY daemon start, so 0 (the upstream default) re-scans the full chain +# each restart — RPC-heavy on an established L2. Set to bound restart cost. +decdn_slash_judge_from_block: 0 decdn_region: "" # ISO 3166-1 alpha-2 decdn_chain_id: 421614 # Arbitrum Sepolia (matches decdn-node's --chain-id default) diff --git a/ansible/roles/decdn_node/tasks/main.yml b/ansible/roles/decdn_node/tasks/main.yml index 0359cdc..a8dd300 100644 --- a/ansible/roles/decdn_node/tasks/main.yml +++ b/ansible/roles/decdn_node/tasks/main.yml @@ -32,6 +32,38 @@ vars: decdn_zero_address: "0x0000000000000000000000000000000000000000" +# slash_appeal_address is optional (only `decdn appeal slash` uses it — the daemon +# ignores the key), so it is not in the hard assert above. But if an operator sets +# it, a half-filled/zero value would silently render a bad node.toml, so validate +# it the same fail-loud way as the required addresses when present. +- name: Validate the optional SlashAppeal address when set + ansible.builtin.assert: + that: + - decdn_slash_appeal_address is match('^0x[0-9a-fA-F]{40}$') + - decdn_slash_appeal_address != decdn_zero_address + fail_msg: >- + decdn_slash_appeal_address is set but is not a valid contract address + (0x + 40 hex, not the zero address). Leave it empty to omit it, or set the + deployed SlashAppeal address (source it from the deployment / ADR 028). + vars: + decdn_zero_address: "0x0000000000000000000000000000000000000000" + when: decdn_slash_appeal_address | length > 0 + +# slash_judge_from_block feeds a `| int > 0` gate in node.toml.j2, and Jinja's int +# filter silently coerces any unparseable value to 0 — which drops the key and +# reverts the daemon to a full-chain rescan on every restart, discarding the +# operator's input with no trace in the rendered config. Assert integer shape so a +# typo fails loud at deploy time instead. Runs unconditionally: the default 0 +# passes, so a malformed value can never slip through the coercion silently. +- name: Validate the SlashJudge scan-floor block is a non-negative integer + ansible.builtin.assert: + that: + - decdn_slash_judge_from_block | string is match('^[0-9]+$') + fail_msg: >- + decdn_slash_judge_from_block must be a non-negative integer (the SlashJudge + deploy block). Got "{{ decdn_slash_judge_from_block }}". Leave it 0 to scan + from genesis, or set the deploy block to bound per-restart rescan cost. + # --- User & directories ------------------------------------------------------- - name: Create decdn system group ansible.builtin.group: diff --git a/ansible/roles/decdn_node/templates/node.toml.j2 b/ansible/roles/decdn_node/templates/node.toml.j2 index 488d3cc..a69b5f3 100644 --- a/ansible/roles/decdn_node/templates/node.toml.j2 +++ b/ansible/roles/decdn_node/templates/node.toml.j2 @@ -20,6 +20,12 @@ eth_keystore = "{{ decdn_keystore_file }}" payment_channel_address = "{{ decdn_payment_channel_address }}" capacity_bond_address = "{{ decdn_capacity_bond_address }}" slash_judge_address = "{{ decdn_slash_judge_address }}" +{% if decdn_slash_appeal_address | length > 0 %} +slash_appeal_address = "{{ decdn_slash_appeal_address }}" +{% endif %} +{% if decdn_slash_judge_from_block | int > 0 %} +slash_judge_from_block = {{ decdn_slash_judge_from_block | int }} +{% endif %} [payment] rate_per_mb = {{ decdn_rate_per_mb }}