|
1 | | -//! Pallet for setting the Partner Chain validators using inherent data |
| 1 | +//! Pallet for setting the Partner Chain validators using inherent data |
2 | 2 | //! |
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 |
6 | 116 | //! construct_runtime!( |
7 | 117 | //! pub struct Runtime { |
8 | 118 | //! System: frame_system, |
| 119 | +//! Timestamp: pallet_timestamp, |
| 120 | +//! Aura: pallet_aura, |
| 121 | +//! Grandpa: pallet_grandpa, |
| 122 | +//! Sidechain: pallet_sidechain, |
9 | 123 | //! SessionCommitteeManagement: pallet_session_validator_management, |
10 | | -//! // ..other pallets |
11 | 124 | //! 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 | +//! } |
12 | 203 | //! } |
13 | | -//! }; |
| 204 | +//! ], |
| 205 | +//! "mainChainScripts": { |
| 206 | +//! "committee_candidate_address": "addr_test1wrp8p2c5h7encl55gv26d5fpz9r99jxjcm0rxgny3993dxs2xy42p", |
| 207 | +//! "d_parameter_policy_id": "0x434dc797fd036b0b654c005551ec08f39d25fa7f0eecdf4b170d46cf", |
| 208 | +//! "permissioned_candidates_policy_id": "0xe1ce5d1b8b3e93a7493ecc11556790f915aabbc44a56b0b5145770b2" |
| 209 | +//! } |
| 210 | +//! } |
14 | 211 | //! ``` |
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 |
17 | 243 |
|
18 | 244 | #![cfg_attr(not(feature = "std"), no_std)] |
19 | 245 | #![allow(clippy::type_complexity)] |
@@ -223,11 +449,6 @@ pub mod pallet { |
223 | 449 | #[pallet::hooks] |
224 | 450 | impl<T: Config> Hooks<BlockNumberFor<T>> for Pallet<T> { |
225 | 451 | // 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. |
231 | 452 | fn on_initialize(block_nr: BlockNumberFor<T>) -> Weight { |
232 | 453 | if block_nr.is_one() { |
233 | 454 | CurrentCommittee::<T>::mutate(|committee| { |
@@ -270,7 +491,6 @@ pub mod pallet { |
270 | 491 | } |
271 | 492 | } |
272 | 493 |
|
273 | | - // TODO make this call run by every full node, so it can be relied upon for ensuring that the block is correct |
274 | 494 | fn check_inherent(call: &Self::Call, data: &InherentData) -> Result<(), Self::Error> { |
275 | 495 | let (validators_param, for_epoch_number_param, call_selection_inputs_hash) = match call |
276 | 496 | { |
|
0 commit comments