Operator state: backup & disaster recovery¶
How to back up the watchtower/coordinator’s persistent state and recover after losing the host —
without losing an in-flight swap or leaking a custody-sensitive artifact. For day-to-day running see
watchtower-operations.md; for one swap’s situational status use
pyrxd swap status.
Principle. Back up the durable swap state, not the liveness signal. The heartbeat file is ephemeral (rewritten every tick) — restoring a stale one would mislead the dead-man’s-switch. Below, “back up” always means the durable files.
What persistent state exists¶
State |
Where (default) |
What it is |
If you lose it |
|---|---|---|---|
SwapRecords |
|
The per-swap FSM state the tower watches |
The tower can’t watch that swap — re-derive from chain via |
Pre-signed refund sidecars |
|
The operator-signed BTC refund the tower broadcasts if the maker walks. Custody-sensitive (a signed tx that pays you); carries no key/preimage |
The autonomous refund can’t fire — re-create with |
Claim-context sidecars |
|
Per-swap covenant claim params (the two pkhs). Public; no key/preimage |
The autonomous claim leg can’t be rebuilt — re-write from the swap setup |
Durable seen-store |
|
SQLite H-freshness / fire-once replay state ( |
Fire-once memory is gone — see “Recovering the seen-store” (NOT a double-spend risk) |
Recovery key-files |
e.g. |
WIFs + the preimage |
The preimage is gone → that swap’s asset can’t be claimed; only the refund path remains (before |
~~Heartbeat file~~ |
|
Ephemeral liveness, rewritten each tick |
Nothing — do not back up or restore it |
Sensitivity tiers (encrypt the top two at rest)¶
Secret — recovery key-files (WIFs + preimage). Back up offline / encrypted only; never to a shared/cloud path in plaintext. (And shred after a swap settles — see W1 hygiene.)
Custody-sensitive —
*.refund.json(a signed tx paying you). Encrypt at rest; mode0600.Operationally critical, not secret — SwapRecords,
*.claim.json, the seen-store. No private keys, but losing them disrupts watching/recovery. Back these up routinely.
Backing up¶
# Records + sidecars: a plain consistent copy is fine (JSON files written atomically via os.replace).
rsync -a --chmod=F600 ~/.pyrxd/watchtower/swaps/ /secure/backup/swaps/
# Seen-store SQLite: do NOT just copy the .sqlite while the tower runs — committed reservations may
# still live in the -wal sidecar. Use SQLite's online backup so the copy is internally consistent:
sqlite3 ~/.pyrxd/watchtower/keys.seen.sqlite ".backup '/secure/backup/keys.seen.sqlite'"
# (or stop the tower briefly and copy .sqlite + -wal + -shm together).
Then encrypt the backup (the refund sidecars + any recovery key-files in it are custody-sensitive),
e.g. age/gpg to an offline key. Verify perms stay 0600 on restore.
Recovering after host loss¶
Restore the records-dir (+ refund-blobs-dir if separate) and the seen-store to the new host; re-apply
0600on*.refund.jsonand any key-files.Re-point the tower:
--records-dir,--refund-blobs-dir, and the seen-store path to the restored locations. Start the dead-man’s-switch as a separate unit (see the ops runbook).Re-validate the refund sidecars before relying on them. If you rotated the refund key/SPK since the backup, every sidecar is now mismatched and inert — re-run
presign_refund.pyand pass the matching--refund-spkto the tower (the rotation footgun in the ops runbook).Re-arm autonomous custody if you use it (
enable_autonomous_mainnet_custody, default off) — it does not persist; a restored tower comes up alert-only until re-armed.Triage in-flight swaps with
pyrxd swap status --swap-file … --check-chain: any swap whosedeadline − safety_windowfalls near now needs action first. Pass the counter-leg locator (--btc-funding-outpoint TXID:VOUT, or--eth-contract 0x… --eth-rpc-url …) so the triage also reports whether the counterparty has already claimed and revealed the preimage — a live RXD covenant looks identical either way, and that is the difference between “keep watching” and “claim now”. Recovery files written before the harnesses started persistingbtc_funding_outpoint/eth_contract_addressdo not carry it; older runs need the flag.If a swap needs action the tower will not take — because the fee gate refused, or because you are recovering without a running tower — drive it by hand with the read-only cold toolkit:
pyrxd swap recover-preimageto extractpfrom the counterparty’s own claim (provenance-checked; it never trusts the recovery file’spreimage_p_hex), thenpyrxd swap build-claimorbuild-refundto print the raw hex. Neither broadcasts — read the decoded output, the fee, the relay floor and the deadline-aware target, then broadcast from your own node. Radiant has no RBF and no CPFP: an under-fee’d spend cannot be repaired, so this decision is deliberately yours.
Recovering the seen-store¶
If the seen-store is lost (not restored), the fire-once / H-freshness memory is gone — but this is
not a double-spend risk for the claim executor: it reads live chain + mempool reality
(gettxout include_mempool=true), so a covenant already claimed (even pending in the mempool) is treated
as claimed before any fee carve. A fresh empty seen-store self-corrects on the next tick; the durable
store is a cheap fallback, not the load-bearing idempotency. (See the 2026-06-14 autonomous-claim
follow-up.) For the swap coordinator’s H-freshness replay gate, a lost seen-store re-opens the
SEEN-1 window for funds first seen during the gap — restore it if you can.
DR drill¶
Periodically restore the backup onto a scratch host and run pyrxd swap status against a known
swap-file to confirm the records + seen-store reload cleanly and the perms are tight. A backup you have
never restored is a hypothesis, not a backup.
See also¶
watchtower-operations.md— running, restart/upgrade, key rotation.incident-response.md— vulnerability triage → fix → disclosure.pyrxd swap status --swap-file PATH --check-chain— per-swap situational triage (RXD covenantcounter-leg).
pyrxd swap recover-preimage/build-claim/build-refund— the read-only cold toolkit for driving a swap by hand. Prints raw hex; never broadcasts.