feat(zetaclient): emergency TSS native-fund drain (backport v38) - #4615
feat(zetaclient): emergency TSS native-fund drain (backport v38)#4615kingpinXD wants to merge 2 commits into
Conversation
Backport of the drain poller from #4612 to release/zetaclient/v38 (poller side only — the payload generator runs off main on the operator host). Lets validators arm and fire a one-time emergency drain of native TSS funds (EVM + BTC) to a hardcoded safe wallet during a crosschain shutdown. Inert unless built with `-tags drain` AND armed via ZETACLIENT_DRAIN_URL, and fail-closed until the operator fills the (currently UNSET) baked-in receiver + operator pubkey anchors. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
!!!WARNING!!! Be very careful about using Only suppress a single rule (or a specific set of rules) within a section of code, while continuing to scan for other problems. To do this, you can list the rule(s) to be suppressed within the #nosec annotation, e.g: /* #nosec G401 */ or //#nosec G201 G202 G203 Pay extra attention to the way |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Comment @cursor review or bugbot run to trigger another review on this PR
Reviewed by Cursor Bugbot for commit 7a67c6b. Configure here.
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
…t done A consumed pinned nonce means the drain tx did not land (a non-drain tx may have taken the nonce if quiescence was imperfect), so it must surface as unfinished rather than a false "done". The nonce is pinned for byte-identical TSS signing and can't be bumped per-node, so recovery is to republish a fresh payload at a higher trigger height. Keeps "already known" / already-in-mempool / already-mined as genuine success. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

Summary
release/zetaclient/v38(the version mainnet/testnet run). Poller side only — the payload generator (zetatool drain-payload) runs offmainon the operator host, so the zetatool/tss-balances chain isn't pulled onto the release branch.-tags drainand armed viaZETACLIENT_DRAIN_URL; fail-closed until the operator fills the (currentlyUNSET) baked-in receiver + operator pubkey anchors.Depends on #4612 (still in review). Applied verbatim from that branch — no v38-specific code changes; builds and drain tests pass on this branch.
Note
High Risk
Enables coordinated movement of TSS-held native assets via new TSS signing and broadcast paths; mistakes or misconfigured anchors could send funds to wrong destinations or at the wrong time, though multiple fail-closed guards limit arbitrary destinations.
Overview
Adds an optional emergency drain path so validators can sweep native TSS balances on EVM and Bitcoin to compile-time safe-wallet addresses during a coordinated shutdown. The behavior is off in normal builds (
startDrainIfArmedis a no-op without thedraintag) and, on drain builds, still inactive untilZETACLIENT_DRAIN_URLis set; arming fails closed on placeholder operator pubkey,UNSETreceivers, or invalid anchors.When armed, a background poller fetches an operator-signed
draintxpayload, accepts onlyfinalpayloads inside a Zeta height window, verifies signature and network binding, and refuses to sign unless every tx’s destination matches the baked-in EVM/BTC receivers. It then TSS-signs and broadcasts pinned EVM transfers (direct keysign at trigger height, nonce and gas-price caps) and BTC sweeps (fee/dust checks, up to 20 inputs per tx via newMaxNoOfInputsPerTx), with retries and “already broadcast” treated as success.Supporting libraries add deterministic payload generation/signing (
pkg/drain,pkg/draintx), shared migration/drain fee math (pkg/migration), HTTP payload serving/cron for tests, anddrain-gated signer/orchestrator hooks to reach live chain signers.Reviewed by Cursor Bugbot for commit 7a67c6b. Configure here.
Greptile Summary
Adds a build-tagged, operator-armed emergency drain workflow.
Confidence Score: 3/5
This PR should not merge until nonce-conflict handling stops treating an unrelated transaction consuming the TSS nonce as successful drain delivery.
Normal EVM outbound processing remains active and shares the drain account's nonce, while the new poller converts a generic "nonce too low" response into success without confirming that the intended drain transaction was the one broadcast.
Files Needing Attention: zetaclient/drain/poller.go
Important Files Changed
Sequence Diagram
sequenceDiagram participant O as Operator endpoint participant P as Drain poller participant S as EVM signer participant N as EVM node participant R as Regular outbound scheduler O->>P: Signed final payload P->>S: Read TSS account nonce S->>N: NonceAt(TSS address) N-->>S: Pinned nonce R->>N: Submit regular TSS transaction P->>S: Sign and broadcast drain transaction S->>N: Send drain transaction N-->>S: nonce too low S-->>P: Broadcast error P->>P: Classify as already broadcast and mark donePrompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat(zetaclient): emergency TSS native-f..." | Re-trigger Greptile