Skip to content

Commit 1e19b83

Browse files
authored
docs: document resolver committee onboarding and offboarding process
1 parent bcd951a commit 1e19b83

3 files changed

Lines changed: 213 additions & 1 deletion

File tree

docs/src/DEPLOYMENT.md

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ can be changed after `initialize`:
1818
| `token` | Any SEP-41 token your users already hold. No swap step exists, so picking a token nobody has is a dead deployment. |
1919
| `bond_amount` | Size from the spam/griefing model in [BOND_SIZING.md](BOND_SIZING.md): start with the larger of the assertion-spam and bad-faith-dispute floors (`R_case / tolerated spam per challenge window`), add any target attacker-loss margin, check finalize reward economics, then keep the result within the affordability cap for the smallest assertion value you want to support. Also capped at `MAX_BOND_AMOUNT`, a contract-enforced ceiling well above any realistic bond size. It exists so the bond can never overflow `finalize`'s reward-multiply arithmetic (the binding constraint) or the token balance held across a dispute. |
2020
| `challenge_window_secs` | Long enough that people who'd actually catch a bad assertion have a realistic chance to see it and act. Short windows finalize faster but catch less. See [V1_MAINNET_PARAMETERS.md](V1_MAINNET_PARAMETERS.md) for sizing guidance by monitoring tier. |
21-
| `resolvers` | Odd-length, non-zero, distinct, and at most 21 addresses. `initialize` rejects duplicates with `DuplicateResolvers`. Pick people who'll actually be reachable to vote within a reasonable time of a dispute; a slow resolver committee stalls every disputed assertion until it acts. See [V1_MAINNET_PARAMETERS.md](V1_MAINNET_PARAMETERS.md) for size and composition trade-offs. |
21+
| `resolvers` | Odd-length, non-zero, distinct, and at most 21 addresses. `initialize` rejects duplicates with `DuplicateResolvers`. Pick people who'll actually be reachable to vote within a reasonable time of a dispute; a slow resolver committee stalls every disputed assertion until it acts. See [V1_MAINNET_PARAMETERS.md](V1_MAINNET_PARAMETERS.md) for size and composition trade-offs, and [RESOLVER_GOVERNANCE.md](RESOLVER_GOVERNANCE.md) for the onboarding process behind picking these addresses in the first place. |
2222
| `finalize_reward_bps` | Basis points (0–1000) of the bond paid to whoever calls `finalize`. `caller` must authorize the call unconditionally, even at 0. 0 means no reward: the full bond returns to the asserter. A non-zero value creates an economic incentive for prompt finalization at the cost of a small bond haircut the asserter accepts when posting. 100 bps (1 %) is a reasonable starting point; 1000 bps (10 %) is the maximum enforced by the contract. |
2323

2424
## Canonical testnet deployment
@@ -101,6 +101,13 @@ switch.
101101

102102
### Rotating the resolver committee
103103

104+
This section covers the on-chain mechanics only: who is authorized to call what,
105+
and what each call does. For the off-chain side, who actually becomes a resolver,
106+
how their key is generated and secured, what they commit to, and which of the two
107+
paths below to use for a given real-world reason (resignation, inactivity, a
108+
compromised key, a conflict of interest), see
109+
[RESOLVER_GOVERNANCE.md](RESOLVER_GOVERNANCE.md).
110+
104111
There are two paths. `update_resolvers` is the admin emergency override; it works
105112
whether paused or not, so a compromised committee can be replaced without waiting to
106113
unpause:

docs/src/RESOLVER_GOVERNANCE.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
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.

docs/src/SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
- [Deployment & Operations](DEPLOYMENT.md)
1212
- [Bond Sizing Analysis](BOND_SIZING.md)
1313
- [V1 Mainnet Parameter Selection](V1_MAINNET_PARAMETERS.md)
14+
- [Resolver Committee Governance](RESOLVER_GOVERNANCE.md)
1415
- [Deployment & Operations: Protocol v2](DEPLOYMENT_V2.md)
1516
- [V2 Bond Sizing and Window Analysis](V2_BOND_SIZING.md)
1617
- [Integration Guide](INTEGRATION.md)

0 commit comments

Comments
 (0)