recover-client.md
6.46 Kb · 157 lines
1# RecoverClient
2
3`core.RecoverClient` is the governance escape hatch that revives a client that
4has become unusable — either **Frozen** (valid misbehaviour was submitted via
5`UpdateClient`) or **Expired** (no valid header was submitted within the
6`TrustingPeriod`) — by copying state from a healthy **substitute** client that
7tracks the same counterparty chain.
8
9```gno
10core.RecoverClient(cross, subjectClientID, substituteClientID string)
11```
12
13Only the admin can call it (see `admin.gno`). In the long run this is expected
14to be driven by a govDAO proposal callback (tracked in issue #36).
15
16## End-to-end flow
17
18### 1. A client becomes unusable
19
20- **Frozen**: a relayer submitted valid misbehaviour via `UpdateClient` (two
21 conflicting signed headers for the same chain). The client's `FrozenHeight`
22 becomes non-zero and `Status()` returns `Frozen`.
23- **Expired**: no valid header was submitted within `TrustingPeriod`, so
24 `Status()` returns `Expired` because the latest consensus state's timestamp
25 is too old.
26
27From this point `SendPacket`, `RecvPacket`, `Acknowledgement`, `Timeout` and
28`UpdateClient` all panic for this client. Any in-flight user packets are
29stuck, and any inbound packets cannot be acknowledged. Channels using this
30client are frozen on this side until the client is recovered.
31
32### 2. Off-chain coordination
33
34Stakeholders agree to recover rather than migrate to a brand-new client.
35Recovery is preferable because it preserves the client ID, packet
36commitments / receipts / acknowledgements, counterparty registration and
37channel state — users don't need to migrate anything.
38
39### 3. Create a substitute client
40
41A relayer calls `core.CreateClient` with a fresh, **Active** client targeting
42the *same counterparty chain*. The substitute must satisfy
43`isMatchingClientState` with the subject, i.e. these fields must match:
44
45- `TrustLevel`
46- `UnbondingPeriod`
47- `MaxClockDrift`
48- `ProofSpecs`
49- `UpgradePath`
50
51The following are allowed to differ and are **adopted from the substitute** by
52the subject during recovery:
53
54- `ChainID` (typically the same, but the code supports a change — for example
55 a genesis-restart on a new chain ID tracking the same state). `ChainID` and
56 `LatestHeight` are always adopted together, so their revision numbers stay
57 aligned: `ClientState.ValidateBasic` requires
58 `LatestHeight.RevisionNumber == ParseChainID(ChainID)`, and since both sides
59 of that equality come from the substitute (which passed `ValidateBasic` at
60 `CreateClient`), the invariant is preserved on the subject post-recovery.
61- `LatestHeight`
62- `TrustingPeriod` — this is the parameter-tweaking knob: if the original
63 `TrustingPeriod` was set too aggressively (and partly caused the expiry),
64 governance can choose a larger value on the substitute and that new value is
65 copied into the subject. Same mechanism as ibc-go.
66- `FrozenHeight` (always reset to zero)
67
68### 4. (Optional) Fast-forward the substitute
69
70Relayers call `UpdateClient(substituteID, header)` until the substitute's
71`LatestHeight` is at the desired recovery height. The substitute must be
72`Active` at the moment recovery executes.
73
74### 5. Governance proposal
75
76A proposal asks to run:
77
78```gno
79core.RecoverClient(cross, subjectID, substituteID)
80```
81
82Currently gated by `ensureAdminCaller()`; once govDAO integration lands the
83proposal executor becomes the authorized caller.
84
85### 6. `RecoverClient` executes
86
87`r/aib/ibc/core/client.gno`:
88
891. `ensureAdminCaller()`.
902. Subject and substitute IDs must differ; both must resolve; `typ` must match.
913. Subject status ∈ {`Frozen`, `Expired`}; substitute status must be `Active`.
924. Delegates to `subject.lightClient.RecoverClient(substitute.lightClient)`.
93
94`p/aib/ibc/lightclient/tendermint/tendermint.gno`:
95
961. Type-assert substitute to `*TMLightClient`.
972. `isMatchingClientState` check.
983. Fetch `substitute.GetConsensusState(substitute.LatestHeight)`.
994. Copy into subject: `ChainID`, `LatestHeight`, `TrustingPeriod`; reset
100 `FrozenHeight`.
1015. Store the substitute's consensus state at the substitute's latest height in
102 the subject.
103
104### 7. Post-recovery state
105
106- Subject's `Status() == Active`. `UpdateClient`, packet verification, etc.
107 resume.
108- **Packet commitments, receipts, acknowledgements, `sendSeq`,
109 `counterpartyClientID`, `counterpartyMerklePrefix` are untouched** — that is
110 the point: channels keep working with their existing identifiers and
111 in-flight state.
112- Pre-recovery consensus states remain in the subject's tree but are below the
113 new `LatestHeight` and are not used to verify new packets.
114- The substitute client is **not** deleted and remains `Active`. It can be
115 reused for a future recovery or left idle.
116- `recover_client` event is emitted.
117
118### 8. Counterparty side (symmetric)
119
120If the counterparty chain's client tracking this chain is also Frozen/Expired
121(common when misbehaviour or a long halt affects both sides), the counterparty
122runs its own governance-level recovery. Packet relaying cannot resume on that
123path until both sides are `Active`.
124
125### 9. Relayer resumes
126
127Once both sides are `Active`, relayers submit headers via `UpdateClient` and
128the normal packet lifecycle resumes. No re-`RegisterCounterparty`, no new
129channel.
130
131## Changing parameters during recovery
132
133Because the substitute's `TrustingPeriod` and `ChainID` are adopted by the
134subject, creating the substitute is also the opportunity to adjust those
135parameters through the same governance action:
136
137- **Lengthening `TrustingPeriod`** to reduce the risk of future expiry — for
138 example after learning that the counterparty's block production is slower
139 than originally assumed.
140- **Adopting a new `ChainID`** after a counterparty genesis restart that kept
141 the same consensus state tree — the subject starts verifying headers signed
142 under the new chain ID without being migrated to a new client ID.
143
144Other parameters (`TrustLevel`, `UnbondingPeriod`, `MaxClockDrift`,
145`ProofSpecs`, `UpgradePath`) **cannot** be changed by recovery — the match
146check rejects the substitute. Changing those requires `UpgradeClient` (or a
147fresh client migration).
148
149## Caveats
150
151- The substitute's `LatestHeight` is not required to be greater than the
152 subject's. Same as ibc-go — nothing enforces a "forward" recovery, though in
153 practice the substitute is always ahead.
154- Only the substitute's consensus state **at its `LatestHeight`** is copied
155 into the subject. Earlier substitute consensus states are not migrated.
156- Recovery does not reset packet sequences or clear commitments — those are
157 packet-layer concerns and stay intact.