|
| 1 | +# Resolver committee governance: onboarding and offboarding |
| 2 | + |
| 3 | +This is the human, off-chain half of running a resolver committee. See |
| 4 | +[DEPLOYMENT.md](DEPLOYMENT.md#rotating-the-resolver-committee) for the on-chain |
| 5 | +mechanics (`update_resolvers`, `propose_rotation`, `vote_rotation`) and |
| 6 | +[ROTATION_DESIGN.md](ROTATION_DESIGN.md) for why self-rotation works the way it |
| 7 | +does. Neither of those documents says who should hold a resolver seat, how that |
| 8 | +person secures their signing key, what they're expected to do when a dispute |
| 9 | +lands, or what actually happens the day a resolver needs to be replaced. This |
| 10 | +document is that operational layer, for anyone running a real (non-toy) |
| 11 | +deployment. |
| 12 | + |
| 13 | +None of this is enforced by the contract. `initialize`, `update_resolvers`, and |
| 14 | +`propose_rotation` accept any distinct addresses; nothing on-chain checks who |
| 15 | +controls them, how the key was generated, or whether the person behind it agreed |
| 16 | +to anything. The guarantees below come entirely from the deployment operator |
| 17 | +choosing to follow this process and documenting that they have. |
| 18 | + |
| 19 | +## Onboarding a new resolver |
| 20 | + |
| 21 | +### Selection criteria |
| 22 | + |
| 23 | +[V1_MAINNET_PARAMETERS.md](V1_MAINNET_PARAMETERS.md#part-3-resolver-committee-size-and-composition) |
| 24 | +already establishes the two properties that matter most for anyone on the |
| 25 | +committee: reachability within `challenge_window_secs`, and independence from |
| 26 | +the other resolvers (no shared employer, custody provider, or coordination |
| 27 | +channel that turns several addresses into one correlated point of failure). |
| 28 | +Onboarding is where those properties get checked before a seat is handed out, |
| 29 | +not after: |
| 30 | + |
| 31 | +- **Domain competence.** A resolver has to be able to actually judge the |
| 32 | + claims this specific deployment adjudicates. What that means varies by |
| 33 | + deployment: for a freelance-escrow use case (see `demos/freelance-escrow`) |
| 34 | + it might mean familiarity with the kind of work being disputed; for an |
| 35 | + oracle-style deployment it might mean the ability to verify a claimed |
| 36 | + real-world outcome from public sources. Generic trustworthiness isn't a |
| 37 | + substitute for being able to tell a good assertion from a bad one in the |
| 38 | + deployment's actual domain. |
| 39 | +- **Demonstrated reachability.** Ask for evidence, not a promise: a track |
| 40 | + record of availability on a timescale compatible with `challenge_window_secs` |
| 41 | + for this deployment. Someone who is highly qualified but unreachable within |
| 42 | + the window is worse for the committee than someone less qualified who |
| 43 | + reliably responds. |
| 44 | +- **Independence from the existing committee and from likely parties.** Screen |
| 45 | + for shared employer, shared custody provider, and shared off-chain |
| 46 | + coordination (the same group chat, the same on-call rotation) with current |
| 47 | + resolvers, per the composition guidance already linked above. Also screen |
| 48 | + for relationships to parties likely to assert or dispute on this deployment; |
| 49 | + see the conflict-of-interest section below for why this matters beyond what |
| 50 | + the contract itself checks. |
| 51 | +- **No conflicting role in the deployment.** A resolver shouldn't also be an |
| 52 | + operator with an economic stake in outcomes (e.g. the deployer taking a cut |
| 53 | + of disputed bonds) beyond the ordinary `finalize_reward_bps` mechanism, which |
| 54 | + applies uniformly and isn't resolver-specific. |
| 55 | + |
| 56 | +### Key generation and custody |
| 57 | + |
| 58 | +The resolver's signing key is what `resolve`, `propose_rotation`, and |
| 59 | +`vote_rotation` authenticate against, so its custody model should match what |
| 60 | +the resolver seat is actually worth to an attacker: the value of the disputes |
| 61 | +it's likely to swing, not the deployment's total value locked. |
| 62 | + |
| 63 | +- **Plain single key (`stellar keys generate`).** What the canonical testnet |
| 64 | + deployment uses today, and reasonable for a low-value or clearly-labeled |
| 65 | + testnet/staging deployment where the committee itself is a stopgap (see |
| 66 | + [DEPLOYMENT.md](DEPLOYMENT.md#canonical-testnet-deployment)'s own caveat |
| 67 | + about `resolver1`/`resolver2`/`resolver3`). Fastest to set up, weakest |
| 68 | + guarantee: a single lost or leaked key is a fully compromised resolver seat |
| 69 | + with no recourse except rotating them out. |
| 70 | +- **Hardware wallet.** Recommended default once a deployment has any real |
| 71 | + value at stake. The resolver holds one address's worth of signing power on |
| 72 | + a device that never exposes the private key to a networked machine. |
| 73 | + Meaningfully raises the bar against remote compromise without adding |
| 74 | + operational complexity for the resolver's day-to-day `resolve` calls, and |
| 75 | + doesn't require coordinating with anyone else to vote. |
| 76 | +- **Multisig behind the resolver's address.** Appropriate when a resolver |
| 77 | + seat represents an organization rather than an individual (e.g. a curated |
| 78 | + public list, or the "broad, visible representation" case |
| 79 | + [V1_MAINNET_PARAMETERS.md](V1_MAINNET_PARAMETERS.md#part-3-resolver-committee-size-and-composition) |
| 80 | + describes for 7+-member committees), or when a single resolver's vote |
| 81 | + carries enough weight that no one person should be able to cast it |
| 82 | + unilaterally. Adds coordination overhead to every single vote, which cuts |
| 83 | + against reachability within the challenge window; weigh that cost against |
| 84 | + the deployment's actual value at stake before choosing this over a hardware |
| 85 | + wallet. |
| 86 | + |
| 87 | +Whichever model is chosen, the resolver (not the deployment operator) should |
| 88 | +control the key material. The operator's role is validating that a reasonable |
| 89 | +custody model is in place before proposing the address for the committee, not |
| 90 | +holding the key on the resolver's behalf. |
| 91 | + |
| 92 | +### Commitments a resolver makes |
| 93 | + |
| 94 | +Before an address is proposed for `initialize`, `update_resolvers`, or |
| 95 | +`propose_rotation`, the person behind it should explicitly agree to: |
| 96 | + |
| 97 | +- **A response-time commitment compatible with `challenge_window_secs`.** |
| 98 | + State it in concrete terms (e.g. "will vote on an open dispute within N |
| 99 | + hours of being notified") rather than "will be reachable," so both sides can |
| 100 | + later judge objectively whether it was kept. This is the off-chain |
| 101 | + counterpart to the reachability criterion above, made auditable. |
| 102 | +- **Conflict-of-interest disclosure, both at onboarding and ongoing.** `resolve` |
| 103 | + rejects a vote where the resolver is also the assertion's `asserter` or |
| 104 | + `disputer` (`SelfVote`, added in PR #203, closing #165). That check is necessarily |
| 105 | + narrow. It only catches the resolver being a direct party to the specific |
| 106 | + dispute in front of them. It cannot catch a resolver with an undisclosed |
| 107 | + financial or personal relationship to one of the parties, since the |
| 108 | + contract has no way to know about relationships that don't show up as an |
| 109 | + on-chain address match. A resolver should disclose any such relationship to |
| 110 | + the rest of the committee (and recuse informally by voting neither way) the |
| 111 | + moment they become aware a specific dispute implicates it. This disclosure |
| 112 | + is not enforced by the contract and depends entirely on the resolver's own |
| 113 | + good faith. |
| 114 | +- **Notice before going dark.** A resolver who knows they'll be unreachable |
| 115 | + for an extended period (travel, planned unavailability) should say so ahead |
| 116 | + of time, so the committee can decide whether to route around it or start a |
| 117 | + replacement conversation before a dispute actually stalls. |
| 118 | + |
| 119 | +None of these commitments are enforceable by the contract; they're operating |
| 120 | +norms the deployment should document and hold resolvers to. Keeping a record |
| 121 | +of who agreed to what (and when) is what makes a later removal-for-cause |
| 122 | +defensible instead of an admin's unilateral judgment call. |
| 123 | + |
| 124 | +## Offboarding and replacement |
| 125 | + |
| 126 | +### What triggers a replacement |
| 127 | + |
| 128 | +- **Inactivity.** A resolver who repeatedly misses their response-time |
| 129 | + commitment, or who has gone dark without notice. Note the distinction from |
| 130 | + the contract's own liveness fallback: `reclaim_stalled_dispute` (when a |
| 131 | + deployment has `set_stall_timeout` configured) unwinds a single stalled |
| 132 | + dispute by returning both bonds with no winner — it resolves that one case, |
| 133 | + it does not remove the unresponsive resolver from the committee. Persistent |
| 134 | + inactivity is a reason to replace the resolver, not just a reason to reclaim |
| 135 | + the dispute they stalled. |
| 136 | +- **Compromised key.** Any signal that a resolver's signing key may have |
| 137 | + leaked or been used without their authorization. Treat this as urgent: see |
| 138 | + the admin-override guidance below. |
| 139 | +- **Conflict of interest.** A disclosed (or discovered) relationship to a |
| 140 | + party that makes continued service on the committee inappropriate, whether |
| 141 | + or not any specific vote was actually affected. |
| 142 | +- **Resignation.** A resolver who no longer wants or is able to serve. |
| 143 | + |
| 144 | +### Who initiates it |
| 145 | + |
| 146 | +Either the affected resolver themselves (resignation, disclosed conflict, |
| 147 | +planned unavailability) or another committee member who observes a problem |
| 148 | +(inactivity, a suspected compromise, an undisclosed conflict they've learned |
| 149 | +of). The deployment operator (the admin key holder) can also initiate a |
| 150 | +replacement, but for anything short of a compromised key or a stalled |
| 151 | +committee, initiating through the committee's own self-rotation path is |
| 152 | +preferable; see the choice of path below. |
| 153 | + |
| 154 | +### Choosing self-rotation vs. the admin override |
| 155 | + |
| 156 | +Both paths write the same committee; the choice is about who should be making |
| 157 | +the decision and how urgently it needs to happen, not a technical constraint |
| 158 | +(both are documented in full in |
| 159 | +[DEPLOYMENT.md](DEPLOYMENT.md#rotating-the-resolver-committee)): |
| 160 | + |
| 161 | +- **Use self-rotation (`propose_rotation` / `vote_rotation`) for the routine |
| 162 | + cases**: resignation, a disclosed conflict of interest, planned |
| 163 | + unavailability, or inactivity the rest of the committee can still act on |
| 164 | + without the outgoing resolver's cooperation. This keeps the decision inside |
| 165 | + the committee, requires no admin involvement, and is the expected path for |
| 166 | + anything that isn't an emergency. A resolver proposes their own removal (or |
| 167 | + another resolver proposes it, naming the affected seat), and the rest vote. |
| 168 | +- **Use the admin override (`update_resolvers`) only for the emergency |
| 169 | + cases**: a compromised key that needs to be cut off immediately, or a |
| 170 | + committee that has lost the ability to reach majority at all (more than |
| 171 | + `(n-1)/2` resolvers simultaneously unreachable, uncooperative, or otherwise |
| 172 | + unable to vote). This is exactly the deadlock scenario |
| 173 | + `docs/src/ROTATION_DESIGN.md` calls out: a compromised or deadlocked |
| 174 | + committee cannot be expected to self-heal by vote, which is why the admin |
| 175 | + path exists at all and stays pause-exempt. Using it for a routine, |
| 176 | + uncontested resignation is a bad default even though it's technically |
| 177 | + capable of doing the same thing — it reintroduces the single-admin-key |
| 178 | + trust path that self-rotation exists to avoid, for a case that didn't need |
| 179 | + it. |
| 180 | + |
| 181 | +A real deployment needs to decide, in advance, who holds the admin key that |
| 182 | +can exercise this override and under what conditions they're expected to use |
| 183 | +it (per |
| 184 | +[V1_MAINNET_PARAMETERS.md](V1_MAINNET_PARAMETERS.md#part-3-resolver-committee-size-and-composition)'s |
| 185 | +own framing) — not discover the answer for the first time during an incident. |
| 186 | + |
| 187 | +### Practical notes on executing a replacement |
| 188 | + |
| 189 | +- A rotation only ever affects disputes opened after it executes; the |
| 190 | + outgoing resolver keeps their vote on any dispute already snapshotted |
| 191 | + against the old committee (see `update_resolvers` in |
| 192 | + [CONTRACT.md](CONTRACT.md) and `docs/src/ROTATION_DESIGN.md`'s section on |
| 193 | + the per-dispute snapshot). If a resolver is being removed for a compromised |
| 194 | + key or an active conflict, check whether they're a snapshotted voter on any |
| 195 | + currently `Disputed` assertion; removing them from the live committee does |
| 196 | + not retroactively remove their vote there. |
| 197 | +- Confirm the incoming resolver's key and custody model (per the onboarding |
| 198 | + section above) before the rotation executes, not after — once |
| 199 | + `vote_rotation` reaches majority, the swap is immediate. |
| 200 | +- Document the reason for every replacement (which of the four triggers |
| 201 | + above applied) alongside the on-chain `RotationExecuted` / |
| 202 | + `ResolversUpdated` event. The event proves *that* the committee changed; |
| 203 | + it doesn't record *why*, and that reasoning is what makes the deployment's |
| 204 | + dispute history auditable later. |
0 commit comments