Skip to content

Commit ead5329

Browse files
authored
docs: ETCM-12356 selection pallet and inherents documentation (#1055)
1 parent 3f19b64 commit ead5329

File tree

4 files changed

+277
-16
lines changed

4 files changed

+277
-16
lines changed

toolkit/committee-selection/authority-selection-inherents/src/lib.rs

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,41 @@
1-
//! This crate provides inherents for authority selection.
1+
//! # Partner Chain Committee Selection
2+
//!
3+
//! Inherent data provider and selection logic for Partner Chain committee selection.
4+
//!
5+
//! ## Overview
6+
//!
7+
//! This crate provides an IDP and all types necessary for a Partner Chain to select
8+
//! block producer committees using data sourced from Cardano smart contracts.
9+
//!
10+
//! ## Usage
11+
//!
12+
//! ### Prerequisites
13+
//!
14+
//! This crate is intended to work with `pallet_session_validator_management`. See
15+
//! the pallet's documentation for instructions how to add it to you runtime. Your
16+
//! pallet should be configured with [CommitteeMember] as its `CommitteeMember`,
17+
//! using the `CrossChainPublic` and `SessionKeys` defined described in the pallet's
18+
//! documentation.
19+
//!
20+
//! Additionally [AriadneInherentDataProvider] needs access to a data source
21+
//! implementing [AuthoritySelectionDataSource]. A Db-Sync-based implementation is
22+
//! provided by the `partner_chains_db_sync_data_sources` crate.
23+
//!
24+
//! ### Adding to the node
25+
//!
26+
//! #### Implementing runtime API
27+
//!
28+
//! Implement the [SessionValidatorManagementApi] for your runtime. Each API method has
29+
//! a corresponding method in the pallet that should be used for that purpose. Refer to
30+
//! the demo runtime for an example.
31+
//!
32+
//! #### Add the inherent data provider
33+
//!
34+
//! Wire the [AriadneInherentDataProvider] into your inherent data provider stack. The same
35+
//! constructor [AriadneInherentDataProvider::new] should be used for both proposing and
36+
//! validating blocks. Refer to the demo node implementation for an example of how to wire
37+
//! it correctly into a node.
38+
//!
239
#![cfg_attr(not(feature = "std"), no_std)]
340
#![deny(missing_docs)]
441

toolkit/committee-selection/pallet/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ frame-benchmarking = { workspace = true, optional = true }
3434
[dev-dependencies]
3535
sp-io = { workspace = true }
3636
pallet-balances = { workspace = true }
37+
sp-runtime = { workspace = true, default-features = true }
3738

3839
[features]
3940
default = ["std"]

toolkit/committee-selection/pallet/src/lib.rs

Lines changed: 234 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,245 @@
1-
//! Pallet for setting the Partner Chain validators using inherent data
1+
//! Pallet for setting the Partner Chain validators using inherent data
22
//!
3-
//! *Important*: It is recommended that when `pallet_session` is wired into the runtime, its
4-
//! extrinsics are hidden, using `exclude_parts` like so:
5-
//! ```rust,ignore
3+
//! # Purpose of the pallet
4+
//!
5+
//! This pallet provides a mechanism to rotate Partner Chain's block producing committees
6+
//! based on candidate registrations and chain configuration sourced from Cardano. It works
7+
//! by integrating with `pallet_session` as a [SessionManager] and [ShouldEndSession] to provide it
8+
//! with committee information and to rotate sessions. In addition to managing the sessions,
9+
//! the pallet automatically registers session keys of all active block producers, alleviating
10+
//! the need for manual key registration, and ensures that all necessary chain-local accounts
11+
//! exist.
12+
//!
13+
//! # Committee selection overview
14+
//!
15+
//! Committees are selected for sessions corresponding roughly to Partner Chain epochs, whose
16+
//! duration is configurable for each Partner Chain. Due to the way session rotation works in
17+
//! `pallet_session`, these sessions are delayed by *2 blocks* relative to their respective
18+
//! epoch.
19+
//!
20+
//! Committees are selected based on the following inputs sourced from Cardano:
21+
//! - `Registered candidates`:
22+
//! Cardano SPOs who have registered themselves as willing to participate as block producers.
23+
//! These candidates need to control an ADA stake pool to be eligible for selection to a
24+
//! committee, and their chance at securing a seat is proportional to their pool's size.
25+
//! This candidate group corresponds to a typical "trustless" Proof of Stake block producers.
26+
//! - `Permissioned candidates`:
27+
//! A list of trusted block producers that do not need to register themselves or control any
28+
//! ADA stake on Cardano to be eligible for a Partner Chain committee.
29+
//! This candidate group serves a special role as trusted block producers during initial phase
30+
//! of a Partner Chain's lifetime (when there may not be enough registered candidates to ensure
31+
//! proper security and decentralization of the network), and are intended to be phased out as
32+
//! the number of trustless participants grows.
33+
//! - `D-Parameter`:
34+
//! A pair of two values `R` and `P`, controlling the number of committee seats alloted for
35+
//! registered and permissioned candidates respectively, which means that a committee has R+P
36+
//! seats overall. This parameter gives the Partner Chain the ability to bootstrap itself using
37+
//! an initial pool of permissioned candidates running trusted nodes, and then gradually shift
38+
//! to registered (trustless) candidates when proper decentralization is achieved
39+
//! - `randomness seed`:
40+
//! All randomness when selecting the committee is seeded from data sourced from Cardano so that
41+
//! it is tamper-proof and agreed upon by all nodes.
42+
//!
43+
//! The permissioned candidate list and the D-Parameter are controlled by the Partner Chain's
44+
//! governance authority and are crucial in ensuring the chain's security in initial phases of
45+
//! its existence
46+
//!
47+
//! # Observability parameters
48+
//!
49+
//! All input data used when selecting a committee of a Partner Chain is sourced from Cardano.
50+
//! To correctly identify it, each node needs access to the current values of:
51+
//! - `the registration validator address`, at which all registration UTXOs are located
52+
//! - `the D-Parameter minting policy`, whose tokens mark the UTXO containing D-Parameter value
53+
//! - `the permissioned candidate minting policy`, whose tokens mark the UTXO containing the
54+
//! permissioned candidate list
55+
//!
56+
//! These values are stored in the pallet storage, ensuring that they're available for all nodes
57+
//! to use and agreed upon through the consensus mechanism, and can be updated using a governance
58+
//! level extrinsic [set_main_chain_scripts].
59+
//!
60+
//! # Usage
61+
//!
62+
//! ## Prerequisites
63+
//!
64+
//! This pallet's operation requires the appropriate inherent data provider and data source
65+
//! be present in the node. As this pallet is crucial for the operation of the chain itself,
66+
//! these must be present before at the chain start or before the pallet is migrated to, to
67+
//! avoid down time. See documentation of `sp_session_validator_management` for information
68+
//! on how to add the IDP to your node. A Db-Sync-based data source implementation is provided
69+
//! by the `partner_chains_db_sync_data_sources` crate.
70+
//!
71+
//! Aside from the node components, the pallet requires the Partner Chain smart contracts to
72+
//! have been initialized on Cardano and that at least one candidate - either a registered or
73+
//! permissioned one - exists. See `docs/user-guides/governance/governance.md` and
74+
//! `docs/user-guides/chain-builder.md` for more information about governance and how to set
75+
//! up the Partner Chain on Cardano.
76+
//!
77+
//! ## Adding into the runtime
78+
//!
79+
//! ### Defining key types
80+
//!
81+
//! As with a stock Substrate chain, a Partner Chain needs to define its session keys. What
82+
//! these keys are depends on the consensus mechanisms used by the chain. For a Partner Chain
83+
//! using Aura as its consensus with a Grandpa finality gadget, the session keys can be defined
84+
//! as following:
85+
//!
86+
//! ```rust, ignore
87+
//! sp_runtime::impl_opaque_keys! {
88+
//! #[derive(MaxEncodedLen, PartialOrd, Ord)]
89+
//! pub struct SessionKeys {
90+
//! pub aura: Aura
91+
//! pub grandpa: Grandpa,
92+
//! }
93+
//! }
94+
//! ```
95+
//!
96+
//! In addition to the session keys, the runtime needs to define an ECDSA key type to represent
97+
//! the `cross-chain key`:
98+
//! ```rust
99+
//! pub mod cross_chain_app {
100+
//! use sp_runtime::KeyTypeId;
101+
//! use sp_runtime::app_crypto::{ app_crypto, ecdsa };
102+
//! pub const CROSS_CHAIN: KeyTypeId = KeyTypeId(*b"crch");
103+
//! app_crypto!(ecdsa, CROSS_CHAIN);
104+
//! }
105+
//! pub type CrossChainPublic = cross_chain_app::Public;
106+
//! ```
107+
//!
108+
//! This key serves as the identity of a Partner Chain user across all chains in the ecosystem.
109+
//!
110+
//! ### Adding the pallet
111+
//!
112+
//! The pallet should be added to the runtime _before_ `pallet_session`, but after the consensus
113+
//! pallets used by the chain:
114+
//!
115+
//! ```rust, ignore
6116
//! construct_runtime!(
7117
//! pub struct Runtime {
8118
//! System: frame_system,
119+
//! Timestamp: pallet_timestamp,
120+
//! Aura: pallet_aura,
121+
//! Grandpa: pallet_grandpa,
122+
//! Sidechain: pallet_sidechain,
9123
//! SessionCommitteeManagement: pallet_session_validator_management,
10-
//! // ..other pallets
11124
//! Session: pallet_session exclude_parts { Call },
125+
//! // ... other pallets
126+
//! }
127+
//! );
128+
//! ```
129+
//!
130+
//! *Important*:
131+
//! It is recommended that when `pallet_session` is wired into the runtime, its extrinsics are
132+
//! hidden, using `exclude_parts` like in the example above. This ensures that chain users can't
133+
//! manually register their keys in the pallet and so the registrations done on Cardano remain
134+
//! the sole source of truth about key ownership. Proper operation in presence of manually set
135+
//! user keys is not guaranteed by the toolkit and its behavior is left unspecified.
136+
//!
137+
//! ### Configuring the pallet
138+
//!
139+
//! Configuring the pallet is straightforward and mostly consists of passing to it types already
140+
//! defined by other crates and in previous steps:
141+
//!
142+
//! ```rust, ignore
143+
//! impl pallet_session_validator_management::Config for Runtime {
144+
//! type MaxValidators = MaxValidators;
145+
//! type AuthorityId = CrossChainPublic;
146+
//! type AuthorityKeys = SessionKeys;
147+
//! type AuthoritySelectionInputs = authority_selection_inherents::AuthoritySelectionInputs;
148+
//! type ScEpochNumber = sidechain_domain::ScEpochNumber;
149+
//! type WeightInfo = pallet_session_validator_management::weights::SubstrateWeight<Runtime>;
150+
//! type CommitteeMember = authority_selection_inherents::CommitteeMember<CrossChainPublic, SessionKeys>;
151+
//! type MainChainScriptsOrigin = EnsureRoot<Self::AccountId>;
152+
//!
153+
//! fn select_authorities(
154+
//! input: AuthoritySelectionInputs,
155+
//! sidechain_epoch: ScEpochNumber,
156+
//! ) -> Option<BoundedVec<Self::CommitteeMember, Self::MaxValidators>> {
157+
//! authority_selection_inherents::select_authorities::<CrossChainPublic, SessionKeys, MaxValidators>(
158+
//! Sidechain::genesis_utxo(),
159+
//! input,
160+
//! sidechain_epoch,
161+
//! )
162+
//! }
163+
//!
164+
//! fn current_epoch_number() -> ScEpochNumber {
165+
//! Sidechain::current_epoch_number()
166+
//! }
167+
//! }
168+
//! ```
169+
//!
170+
//! One value that needs to be decided upon by the chain builder is `MaxValidators` which dictates
171+
//! the maximum size of a committee. This value should be higher than the P + R of the D-Parameter
172+
//! used and should be adjusted accordingly before any D-Parameter changes that would exceed the
173+
//! previous value. In case a committee selected is bigger than `MaxValidators`, it will be truncated,
174+
//! potentially leading to a skewed seat allocation and threatening the security of the consensus.
175+
//!
176+
//! ## Genesis configuration
177+
//!
178+
//! Genesis config can be created programmatically:
179+
//!
180+
//! ```rust,ignore
181+
//! GenesisConfig {
182+
//! initial_authorities: vec![
183+
//! CommitteeMember::permissioned(cross_chain_pubkey_1, session_keys_1),
184+
//! ],
185+
//! main_chain_scripts: MainChainScripts::read_from_env()?,
186+
//! }
187+
//! ```
188+
//!
189+
//! However, it is more typical for production chains to define their specs using Json. In that case
190+
//! an example configuration could look like this:
191+
//!
192+
//! ```json
193+
//! {
194+
//! "initialAuthorities": [
195+
//! {
196+
//! "Permissioned": {
197+
//! "id": "KW39r9CJjAVzmkf9zQ4YDb2hqfAVGdRqn53eRqyruqpxAP5YL",
198+
//! "keys": {
199+
//! "aura": "5GrwvaEF5zXb26Fz9rcQpDWS57CtERHpNehXCPcNoHGKutQY",
200+
//! "grandpa": "5FA9nQDVg267DEd8m1ZypXLBnvN7SFxYwV7ndqSYGiN9TTpu"
201+
//! }
202+
//! }
12203
//! }
13-
//! };
204+
//! ],
205+
//! "mainChainScripts": {
206+
//! "committee_candidate_address": "addr_test1wrp8p2c5h7encl55gv26d5fpz9r99jxjcm0rxgny3993dxs2xy42p",
207+
//! "d_parameter_policy_id": "0x434dc797fd036b0b654c005551ec08f39d25fa7f0eecdf4b170d46cf",
208+
//! "permissioned_candidates_policy_id": "0xe1ce5d1b8b3e93a7493ecc11556790f915aabbc44a56b0b5145770b2"
209+
//! }
210+
//! }
14211
//! ```
15-
//! This ensures that chain users can't manually register their keys in the pallet and so the
16-
//! registrations done on Cardano remain the sole source of truth about key ownership.
212+
//!
213+
//! *Important*:
214+
//! Notice, that since the pallet's operation is necessary for block production, all main chain script
215+
//! values and at least one initial authority (block producer) must be provided by the genesis config.
216+
//!
217+
//!
218+
//! ## Updating pallet configuration
219+
//!
220+
//! ### MaxValidators
221+
//!
222+
//! The maximum number of committee seats. As this value is not typically expected to change, it is
223+
//! configured as part of the pallet's [Config]. This means that it can only be updated as part of a
224+
//! runtime upgrade. The chain builder should release a new runtime version with this value updated
225+
//! and the Partner Chain's governance mechanism should be used to apply it using [set_code].
226+
//!
227+
//! ### Main chain scripts
228+
//!
229+
//! The main chain scripts can change over time as the Partner Chain migrates to new versions of the
230+
//! Partner Chain smart contracts, either due to bug fixes or new features being released. This is
231+
//! necessary, because the script addresses are derived by hashing their Plutus code and are affected
232+
//! by any change made to it.
233+
//!
234+
//! The scripts are updated by invoking the [set_main_chain_scripts] extrinsic using the Partner Chain's
235+
//! governance mechanism.
236+
//!
237+
//! *Important*: Setting incorrect main chain script values will result in stalling block production
238+
//! indefinitely, requiring a network-wide roll-back. As such, main chain scripts update
239+
//! should be carried out with special care.
240+
//!
241+
//! [SessionManager]: pallet_session::SessionManager
242+
//! [set_code]: frame_system::Pallet::set_code
17243
18244
#![cfg_attr(not(feature = "std"), no_std)]
19245
#![allow(clippy::type_complexity)]
@@ -223,11 +449,6 @@ pub mod pallet {
223449
#[pallet::hooks]
224450
impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> {
225451
// Only reason for this hook is to set the genesis committee as the committee for first block's epoch.
226-
// If it wouldn't be set, the should_end_session() function would return true at the 2nd block,
227-
// thus denying handover phase to genesis committee, which would break the chain. With this hook,
228-
// should_end_session() returns true at 1st block and changes committee to the same one, thus allowing
229-
// handover phase to happen. After having proper chain initialization procedure this probably won't be needed anymore.
230-
// Note: If chain is started during handover phase, it will wait until new epoch to produce the first block.
231452
fn on_initialize(block_nr: BlockNumberFor<T>) -> Weight {
232453
if block_nr.is_one() {
233454
CurrentCommittee::<T>::mutate(|committee| {
@@ -270,7 +491,6 @@ pub mod pallet {
270491
}
271492
}
272493

273-
// TODO make this call run by every full node, so it can be relied upon for ensuring that the block is correct
274494
fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> {
275495
let (validators_param, for_epoch_number_param, call_selection_inputs_hash) = match call
276496
{

toolkit/committee-selection/primitives/src/lib.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
//! Primitives for `committee-selection`.
1+
//! # Primitives for Partner Chain committee selection.
2+
//!
3+
//! This crate implements shared types and traits used to implement Partner Chain committee rotation.
4+
25
#![cfg_attr(not(feature = "std"), no_std)]
36
#![deny(missing_docs)]
47

0 commit comments

Comments
 (0)