034-constitutional-committee-management
Constitutional Committee Management
Context
The constitutional committee is modelled in a particularly interesting fashion in the Haskell Cardano node and has given us a lot of headaches to make it fit properly within our various abstraction layers.
In Haskell, the committee is captured by:
-
An optional
Committeetype which comprises:- a
Mapfrom cold credential to its validity period - a voting threshold as a rational
- See the upstream definition of
Committee.
- a
-
A
CommitteeStatewhich translates as aMapfrom cold credential to either:- an authorization (i.e. delegation to a hot credential)
- a resignation (with an optional anchor).
- See the upstream definitions of
CommitteeAuthorizationandCommitteeState.
The two maps are managed somewhat independently and “synchronized” every epoch. Among the notable behaviors:
-
A candidate committee member (i.e. added in an active but not yet ratified proposal) is allowed to register a hot credential in the
CommitteeState, but is not part of theCommittee. -
Similarly, a candidate committee member can also resign, prior to being elected (yes, yes, …).
-
In any case, a member that has previously resigned cannot authorize a new hot credential, and cannot resign again (might it be with a different rationale) in the same epoch.
- This is enforced in the Conway governance certificate rule:
ConwayCommitteeHasPreviouslyResigned. - There are subtle scenarios associated with this rule, which are detailed below.
- Voting conditions are handled separately and are entirely bound to the hot
credential. To be a valid voter, the hot credential must come from an
authorization (i.e. entry in
CommitteeState) of a member from theCommittee
- This means that an expired member can technically vote beyond its mandate, but a resigned member cannot (there is no stake credential left).
- In protocol version 10, not-yet-elected members with a registered hot credential could also cast votes.
- Whether a vote is valid is then deferred to the epoch boundary ratification. Votes from members that are expired, not elected or that have resigned are discarded.
-
The same hot credential can be associated with more than one cold credential.
-
The members of the
Committeeand the value of their validity period can only change at an epoch boundary, as a result of a corresponding governance action being enacted. -
A motion of no confidence sets the
CommitteetoNone, effectively removing the entire map of elected committee members.- See the
NoConfidencecase in the enactment rule:ensCommitteeL .~ SNothing.
- See the
-
At the beginning of every epoch, after ratification/enactment, the
CommitteeStateis restricted to only cold credentials present in theCommittee. This effectively prunes any dangling hot credential delegation or resignations for non-elected members.- This happens through
updateCommitteeStateand its definition as aMap.intersectionwith the elected committee:updateCommitteeState. - Consequently, a motion of no confidence will also empty every existing hot credential or resignation mapping as part of the same epoch transition.
- A candidate member that registers a hot credential and that isn’t elected at the next epoch boundary must eventually re-register the hot credential since it will be removed at the epoch boundary.
- Similarly, a candidate member that resigns in an epoch can actually re-register a hot credential in the next epoch (or resign again) if not elected. The resignation is not preserved.
- An expired member and its corresponding hot credential or resignation
bindings remain in the
CommitteeStateas long as the member is not explicitly removed (via a governance action) from theCommittee.
- This happens through
Decision
Observations
From these various points, we can derive a few interesting observations which have motivated our design:
-
CC Members are only added or removed at an epoch boundary, via governance actions (update committee or motion of no confidence).
-
Blocks can only ever update the
CommitteeState: the hot credential and/or the resignation. -
Blocks can never remove or add members, nor change their validity period.
Our State
We therefore represent the two Haskell maps as:
- A single key/value map where keys are cold credentials and values are:
- an optional epoch number which serves as a validity period
- an optional
CommitteeMemberStatuswhich can be eitherResignedorDelegated StakeCredential.
We store the committee threshold entirely separately.
In practice, this means that:
- A freshly elected member has a non-null epoch mandate; that epoch may or may not be in the future.
- A member may have a resignation or a delegation, irrespective of whether they have a mandate.
- This means that a member present in a proposal who would register a hot credential (or resignation) would correspond to a DB entry with no epoch.
- At epoch boundary, we can remove all entries that have no associated epoch (this is our “intersection”).
Bind<L = CommitteeMemberStatus, R = Epoch, V = Empty>
This fits particularly well with our notion of Bind where:
- a
left-bind is associated with a hot credential or resignation. - a
right-bind is associated with an epoch. - a bind’s
valueis always empty (blocks never ‘register’ a cc-member).
The left binds can only be produced by blocks, as the result of processing
certificates.
Conversely, right binds are only produced by epoch transitions, which live
in the overlay.
The semantics of Bind.then apply nicely:
pub fn then(&mut self, newer: Self) {
if newer.value.is_some() {
*self = newer;
} else {
if !matches!(newer.left, Resettable::Unchanged) {
self.left = newer.left;
}
if !matches!(newer.right, Resettable::Unchanged) {
self.right = newer.right;
}
}
}
- Neither
selfnornewercan have a value, so only theelsebranch applies - A more recent left bind other than
Unchangedreplaces an older left bind - A more recent right bind other than
Unchangedreplaces an older right bind
Existence<Bind<CommitteeMemberStatus, Epoch, Empty>>
Similarly, an Existence of such a bind becomes handy to track updates across
the volatile. Each layer (current, overlay, draining) in the volatile can
resolve a member’s Existence:
- The current and draining series can only ever yield
Existswith non-empty left binds orUnknown. - The overlay can yield either
Existswith right binds,Gone(removed members or no-confidence) orUnknown(no related governance action).
And they compose nicely from the most recent to least recent using Existence::fold:
-
A
Goneis terminal, once encountered, it supersedes any older state. -
An
Unknownstate defers to the next source; so a missing member in the current series will fall back to the overlay, which will in turn fall back to the draining sequence and then to the stable store. -
A recent
Existswill always apply on top of previousExistsstates, so that newer left or right binds overwrite older ones. The resulting bind contains the most recent left and right binds in the sequence, unless superseded by aGonestate.
Consequences
Edge cases
-
We require constitutional committee information in two cases:
- when a related CC member certificate is found in the transaction; in which case the member can be resolved by cold credential
- when a CC voter is found in a voting procedure
However, we do not have an index of hot credential to member(s). We must therefore scan the entire CC member column to determine which ones match the hot credential. This was deemed a reasonable approach due to the tiny size of the CC. That tiny size is not guaranteed by the protocol, but rather by social consensus, and reaching a size at which this becomes a practical problem would require multiple governance actions, leaving plenty of time to react.
- The same asymmetry exists upstream: hot credentials are derived by scanning
committee state rather than via a unique hot-to-cold index; see
authorizedHotCommitteeCredentialsandqueryCommitteeMembersState.
-
An elected member can exist solely in the volatile state and not be in the stable store. This is true for every newly elected member, which is only present in the overlay until the end of the stabilization window. Hence, the state of current CC members cannot be reconstructed from the stable store alone.
-
It is possible for a member to be removed via a governance action, and yet still be a valid cold credential for delegation; this happens in the case where two constitutional committee actions are (correctly) chained so that the first removes a member that the next one re-adds. The overlay must not recklessly mark CC members as “Gone” if they appear in another proposal.