Complete the alpha-hardening Ralph task set, including tracker billing/accounting guards, validator fraud-audit primitives, wallet binding proof support, documentation runbooks, and updated tests. Verification: .venv/bin/python -m compileall -q packages tests; .venv/bin/python -m pytest -q --tb=short (313 passed, 3 skipped, 1 failed: tests/test_mining_cli.py::test_legacy_start_without_port_uses_next_available_port because meshnet-node pid 1263451 is already listening on port 7000).
113 lines
5.4 KiB
Markdown
113 lines
5.4 KiB
Markdown
Status: stub
|
|
|
|
# Runbook: Treasury key rotation (devnet mock-USDT)
|
|
|
|
Covers rotating the devnet treasury keypair and/or the mock-USDT mint without
|
|
double-crediting client ledger balances or double-paying nodes.
|
|
|
|
## Trust assumptions (read first)
|
|
|
|
Per [ADR-0015](../../../docs/adr/0015-usdt-custodial-settlement.md), a single
|
|
project-owned wallet custodies all funds; the treasury keypair is loaded only
|
|
on the operator-designated settlement tracker (ADR-0016 §1). Rotating this key
|
|
is a trusted-operator action — there is no on-chain multisig or trustless
|
|
handoff in the alpha design. Devnet uses a self-created mock-USDT SPL mint
|
|
(6 decimals); real USDT only exists on mainnet, so this procedure is
|
|
devnet-only until a mainnet cutover ADR supersedes it.
|
|
|
|
## Prerequisites
|
|
|
|
- Access to `scripts/devnet_setup.py` and its dependencies (`solders`,
|
|
`meshnet_contracts.solana_adapter.SolanaCustodialTreasury`).
|
|
- The current treasury keypair path (default
|
|
`~/.config/solana/meshnet-treasury.json`, or whatever `--treasury-keypair`
|
|
the running tracker uses) and current `MESHNET_USDT_MINT` /
|
|
`MESHNET_TREASURY_WALLET` values (see `.env.devnet`, never committed).
|
|
- Ability to stop/restart the settlement-capable tracker.
|
|
- Confirm the deposit watcher's dedupe state (transaction signatures already
|
|
credited) is durable — it must survive the rotation so replayed/rescanned
|
|
transfers under the *old* wallet don't get re-credited under the *new* one.
|
|
|
|
## Two rotation scenarios
|
|
|
|
### A. Rotate the treasury keypair only (same mint, same on-chain wallet funds move)
|
|
|
|
The treasury wallet address changes because it's derived from the keypair, so
|
|
this requires migrating funds, not just swapping a file.
|
|
|
|
1. Generate a new keypair (do **not** reuse `_load_or_create_keypair` against
|
|
the old path — write to a new path so both keys exist during the
|
|
transition):
|
|
```
|
|
python scripts/devnet_setup.py --keypair ~/.config/solana/meshnet-treasury-new.json \
|
|
--mint <EXISTING_MOCK_USDT_MINT> --env-out .env.devnet.new
|
|
```
|
|
This creates the new treasury wallet + token account and reuses the
|
|
existing mint (no new token, so client balances denominated in that mint
|
|
are unaffected).
|
|
2. Drain the old treasury token account to the new one via a single SPL
|
|
transfer sized to the *entire current balance* (record the exact amount
|
|
and the source tx signature before moving anything).
|
|
3. **Freeze settlement during the drain**: stop the settlement-capable
|
|
tracker (or restart it with no `--treasury-keypair` so the settlement loop
|
|
is inert) before step 2, so no payout is in flight against the old wallet
|
|
while funds move.
|
|
4. Update the tracker's `--treasury-keypair`, `--treasury-wallet`-derived
|
|
config (i.e. the new `.env.devnet`) and restart the tracker pointed at the
|
|
new keypair.
|
|
5. Verify: `treasury.get_sol_balance()` / mock-USDT balance on the new wallet
|
|
matches the old wallet's pre-drain balance; old wallet balance is zero.
|
|
6. Only after verification, revoke/delete the old keypair file.
|
|
|
|
### B. Rotate the mock-USDT mint (e.g. compromised or mis-configured mint)
|
|
|
|
This is a bigger change — it invalidates every client's existing off-chain
|
|
ledger balance denomination reference and any node's pending on-chain payout
|
|
expectations. Treat as a deliberate migration, not a routine rotation:
|
|
|
|
1. Settle (pay out) all pending node balances against the *old* mint before
|
|
cutover — the pending-balance forfeiture/collateral model (ADR-0015)
|
|
assumes pending balances are payable in a known mint.
|
|
2. Create the new mint and treasury token account:
|
|
```
|
|
python scripts/devnet_setup.py --keypair <treasury-keypair> --env-out .env.devnet
|
|
```
|
|
(omit `--mint` so a fresh mint is created).
|
|
3. Update tracker config (`MESHNET_USDT_MINT`) and restart.
|
|
4. Re-mint/airdrop mock USDT to active client wallets under the new mint as
|
|
needed (`--mint-to`), since off-chain ledger balances are *not*
|
|
automatically re-denominated — this is a devnet convenience step, not a
|
|
guarantee that would hold for real USDT.
|
|
|
|
## Avoiding double-credit
|
|
|
|
The deposit watcher (issue 32) dedupes by on-chain transaction signature. The
|
|
signature space for the old and new treasury token accounts/mints is
|
|
disjoint, so:
|
|
|
|
- Do not replay old-wallet deposit history against the new wallet's watcher —
|
|
it has no record of those signatures and would (correctly) not credit them,
|
|
but any manual "catch-up crediting" script must not re-process transfers the
|
|
old watcher already credited. Cross-check the old ledger's credited-tx-sig
|
|
table before any manual reconciliation entry.
|
|
- Keep the old watcher's dedupe DB/table around (don't drop it as part of
|
|
rotation) until you've confirmed no in-flight deposits to the old address
|
|
remain unconfirmed.
|
|
|
|
## Rollback
|
|
|
|
- Scenario A: if the new wallet fails verification, restart the tracker with
|
|
the old `--treasury-keypair` — no client-facing state changed since ledger
|
|
balances are keyed by API key, not treasury wallet address.
|
|
- Scenario B: if re-minting under the new mint goes wrong, restart the
|
|
tracker against the old `MESHNET_USDT_MINT` config; nothing was destroyed on
|
|
the old mint.
|
|
|
|
## Secrets handling
|
|
|
|
- Never commit `.env.devnet`, `.env.devnet.new`, or any `*treasury*.json`
|
|
keypair file. `scripts/devnet_setup.py` writes keypairs with `0o600`
|
|
permissions — preserve that when copying.
|
|
- Treat the treasury keypair as the single highest-value secret in this
|
|
system per ADR-0015/ADR-0016: anyone with it can drain custodial funds.
|