diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index 4dec90b33c..f9e40a40ac 100644 --- a/polkadot/node/core/runtime-api/src/lib.rs +++ b/polkadot/node/core/runtime-api/src/lib.rs @@ -134,7 +134,7 @@ fn make_runtime_api_request( Request::CandidatePendingAvailability(para, sender) => query!(candidate_pending_availability(para), sender), Request::CandidateEvents(sender) => query!(candidate_events(), sender), - Request::ValidatorDiscovery(ids, sender) => query!(validator_discovery(ids), sender), + Request::SessionInfo(index, sender) => query!(session_info(index), sender), Request::DmqContents(id, sender) => query!(dmq_contents(id), sender), Request::InboundHrmpChannelsContents(id, sender) => query!(inbound_hrmp_channels_contents(id), sender), } @@ -201,8 +201,8 @@ mod tests { use polkadot_primitives::v1::{ ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, PersistedValidationData, Id as ParaId, OccupiedCoreAssumption, ValidationData, SessionIndex, ValidationCode, - CommittedCandidateReceipt, CandidateEvent, AuthorityDiscoveryId, InboundDownwardMessage, - BlockNumber, InboundHrmpMessage, + CommittedCandidateReceipt, CandidateEvent, InboundDownwardMessage, + BlockNumber, InboundHrmpMessage, SessionInfo, }; use polkadot_node_subsystem_test_helpers as test_helpers; use sp_core::testing::TaskExecutor; @@ -216,6 +216,7 @@ mod tests { availability_cores: Vec, validation_data: HashMap, session_index_for_child: SessionIndex, + session_info: HashMap, validation_code: HashMap, historical_validation_code: HashMap>, validation_outputs_results: HashMap, @@ -289,6 +290,10 @@ mod tests { self.session_index_for_child.clone() } + fn session_info(&self, index: SessionIndex) -> Option { + self.session_info.get(&index).cloned() + } + fn validation_code( &self, para: ParaId, @@ -321,10 +326,6 @@ mod tests { self.candidate_events.clone() } - fn validator_discovery(ids: Vec) -> Vec> { - vec![None; ids.len()] - } - fn dmq_contents( &self, recipient: ParaId, @@ -569,6 +570,33 @@ mod tests { futures::executor::block_on(future::join(subsystem_task, test_task)); } + #[test] + fn requests_session_info() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + let mut runtime_api = MockRuntimeApi::default(); + let session_index = 1; + runtime_api.session_info.insert(session_index, Default::default()); + let runtime_api = Arc::new(runtime_api); + + let relay_parent = [1; 32].into(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None)); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle.send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request(relay_parent, Request::SessionInfo(session_index, tx)) + }).await; + + assert_eq!(rx.await.unwrap().unwrap(), Some(Default::default())); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); + } + #[test] fn requests_validation_code() { let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); diff --git a/polkadot/node/network/collator-protocol/src/collator_side.rs b/polkadot/node/network/collator-protocol/src/collator_side.rs index b0a61c66a9..1017b55c28 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side.rs @@ -738,6 +738,7 @@ mod tests { use polkadot_primitives::v1::{ BlockData, CandidateDescriptor, CollatorPair, ScheduledCore, ValidatorIndex, GroupRotationInfo, AuthorityDiscoveryId, + SessionIndex, SessionInfo, }; use polkadot_subsystem::{ActiveLeavesUpdate, messages::{RuntimeApiMessage, RuntimeApiRequest}}; use polkadot_node_subsystem_util::TimeoutExt; @@ -776,6 +777,7 @@ mod tests { relay_parent: Hash, availability_core: CoreState, our_collator_pair: CollatorPair, + session_index: SessionIndex, } fn validator_pubkeys(val_ids: &[Sr25519Keyring]) -> Vec { @@ -832,6 +834,7 @@ mod tests { relay_parent, availability_core, our_collator_pair, + session_index: 1, } } } @@ -841,6 +844,10 @@ mod tests { &self.validator_groups.0[0] } + fn current_session_index(&self) -> SessionIndex { + self.session_index + } + fn current_group_validator_peer_ids(&self) -> Vec { self.current_group_validator_indices().iter().map(|i| self.validator_peer_id[*i as usize].clone()).collect() } @@ -870,20 +877,6 @@ mod tests { .collect() } - fn next_group_validator_ids(&self) -> Vec { - self.next_group_validator_indices() - .iter() - .map(|i| self.validator_public[*i as usize].clone()) - .collect() - } - - /// Returns the unique count of validators in the current and next group. - fn current_and_next_group_unique_validator_count(&self) -> usize { - let mut indices = self.next_group_validator_indices().iter().collect::>(); - indices.extend(self.current_group_validator_indices()); - indices.len() - } - /// Generate a new relay parent and inform the subsystem about the new view. /// /// If `merge_views == true` it means the subsystem will be informed that we working on the old `relay_parent` @@ -1090,20 +1083,33 @@ mod tests { overseer_recv(virtual_overseer).await, AllMessages::RuntimeApi(RuntimeApiMessage::Request( relay_parent, - RuntimeApiRequest::ValidatorDiscovery(validators, tx), + RuntimeApiRequest::SessionIndexForChild(tx), )) => { assert_eq!(relay_parent, test_state.relay_parent); - assert_eq!(validators.len(), test_state.current_and_next_group_unique_validator_count()); + tx.send(Ok(test_state.current_session_index())).unwrap(); + } + ); - let current_validators = test_state.current_group_validator_ids(); - let next_validators = test_state.next_group_validator_ids(); + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(index, tx), + )) => { + assert_eq!(relay_parent, test_state.relay_parent); + assert_eq!(index, test_state.current_session_index()); - assert!(validators.iter().all(|v| current_validators.contains(&v) || next_validators.contains(&v))); + let validators = test_state.current_group_validator_ids(); + let current_discovery_keys = test_state.current_group_validator_authority_ids(); + let next_discovery_keys = test_state.next_group_validator_authority_ids(); - let current_validators = test_state.current_group_validator_authority_ids(); - let next_validators = test_state.next_group_validator_authority_ids(); + let discovery_keys = [¤t_discovery_keys[..], &next_discovery_keys[..]].concat(); - tx.send(Ok(current_validators.into_iter().chain(next_validators).map(Some).collect())).unwrap(); + tx.send(Ok(Some(SessionInfo { + validators, + discovery_keys, + ..Default::default() + }))).unwrap(); } ); diff --git a/polkadot/node/network/collator-protocol/src/lib.rs b/polkadot/node/network/collator-protocol/src/lib.rs index 3b58d9b96b..ba1147fef9 100644 --- a/polkadot/node/network/collator-protocol/src/lib.rs +++ b/polkadot/node/network/collator-protocol/src/lib.rs @@ -60,16 +60,6 @@ enum Error { Prometheus(#[from] prometheus::PrometheusError), } -impl From for Error { - fn from(me: util::validator_discovery::Error) -> Self { - match me { - util::validator_discovery::Error::Subsystem(s) => Error::Subsystem(s), - util::validator_discovery::Error::RuntimeApi(ra) => Error::RuntimeApi(ra), - util::validator_discovery::Error::Oneshot(c) => Error::Oneshot(c), - } - } -} - type Result = std::result::Result; /// What side of the collator protocol is being engaged diff --git a/polkadot/node/network/pov-distribution/src/error.rs b/polkadot/node/network/pov-distribution/src/error.rs index 625ea4c7fd..754c1e56c5 100644 --- a/polkadot/node/network/pov-distribution/src/error.rs +++ b/polkadot/node/network/pov-distribution/src/error.rs @@ -27,8 +27,6 @@ pub enum Error { #[error(transparent)] Runtime(#[from] polkadot_subsystem::errors::RuntimeApiError), #[error(transparent)] - ValidatorDiscovery(#[from] polkadot_node_subsystem_util::validator_discovery::Error), - #[error(transparent)] Util(#[from] polkadot_node_subsystem_util::Error), } diff --git a/polkadot/node/network/pov-distribution/src/tests.rs b/polkadot/node/network/pov-distribution/src/tests.rs index 7c7859b869..a6358bedfd 100644 --- a/polkadot/node/network/pov-distribution/src/tests.rs +++ b/polkadot/node/network/pov-distribution/src/tests.rs @@ -11,7 +11,7 @@ use sp_keyring::Sr25519Keyring; use polkadot_primitives::v1::{ AuthorityDiscoveryId, BlockData, CoreState, GroupRotationInfo, Id as ParaId, - ScheduledCore, ValidatorIndex, + ScheduledCore, ValidatorIndex, SessionIndex, SessionInfo, }; use polkadot_subsystem::messages::{RuntimeApiMessage, RuntimeApiRequest}; use polkadot_node_subsystem_test_helpers as test_helpers; @@ -37,8 +37,10 @@ fn validator_authority_id(val_ids: &[Sr25519Keyring]) -> Vec; + struct TestHarness { - virtual_overseer: test_helpers::TestSubsystemContextHandle, + virtual_overseer: VirtualOverseer, } fn test_harness>( @@ -75,7 +77,7 @@ fn test_harness>( const TIMEOUT: Duration = Duration::from_millis(100); async fn overseer_send( - overseer: &mut test_helpers::TestSubsystemContextHandle, + overseer: &mut VirtualOverseer, msg: PoVDistributionMessage, ) { trace!("Sending message:\n{:?}", &msg); @@ -87,7 +89,7 @@ async fn overseer_send( } async fn overseer_recv( - overseer: &mut test_helpers::TestSubsystemContextHandle, + overseer: &mut VirtualOverseer, ) -> AllMessages { let msg = overseer_recv_with_timeout(overseer, TIMEOUT) .await @@ -99,7 +101,7 @@ async fn overseer_recv( } async fn overseer_recv_with_timeout( - overseer: &mut test_helpers::TestSubsystemContextHandle, + overseer: &mut VirtualOverseer, timeout: Duration, ) -> Option { trace!("Waiting for message..."); @@ -110,7 +112,7 @@ async fn overseer_recv_with_timeout( } async fn overseer_signal( - overseer: &mut test_helpers::TestSubsystemContextHandle, + overseer: &mut VirtualOverseer, signal: OverseerSignal, ) { overseer @@ -130,6 +132,7 @@ struct TestState { validator_groups: (Vec>, GroupRotationInfo), relay_parent: Hash, availability_cores: Vec, + session_index: SessionIndex, } impl Default for TestState { @@ -184,10 +187,56 @@ impl Default for TestState { validator_groups, relay_parent, availability_cores, + session_index: 1, } } } +async fn test_validator_discovery( + virtual_overseer: &mut VirtualOverseer, + expected_relay_parent: Hash, + session_index: SessionIndex, + validator_ids: &[ValidatorId], + discovery_ids: &[AuthorityDiscoveryId], + validator_group: &[ValidatorIndex], +) { + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionIndexForChild(tx), + )) => { + assert_eq!(relay_parent, expected_relay_parent); + tx.send(Ok(session_index)).unwrap(); + } + ); + + assert_matches!( + overseer_recv(virtual_overseer).await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + relay_parent, + RuntimeApiRequest::SessionInfo(index, tx), + )) => { + assert_eq!(relay_parent, expected_relay_parent); + assert_eq!(index, session_index); + + let validators = validator_group.iter() + .map(|idx| validator_ids[*idx as usize].clone()) + .collect(); + + let discovery_keys = validator_group.iter() + .map(|idx| discovery_ids[*idx as usize].clone()) + .collect(); + + tx.send(Ok(Some(SessionInfo { + validators, + discovery_keys, + ..Default::default() + }))).unwrap(); + } + ); +} + #[test] fn ask_validators_for_povs() { let test_state = TestState::default(); @@ -271,25 +320,14 @@ fn ask_validators_for_povs() { } ); - // obtain the validator_id to authority_id mapping - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::ValidatorDiscovery(validators, tx), - )) => { - assert_eq!(relay_parent, current); - assert_eq!(validators.len(), 3); - assert!(validators.iter().all(|v| test_state.validator_public.contains(&v))); - - let result = vec![ - Some(test_state.validator_authority_id[2].clone()), - Some(test_state.validator_authority_id[0].clone()), - Some(test_state.validator_authority_id[4].clone()), - ]; - tx.send(Ok(result)).unwrap(); - } - ); + test_validator_discovery( + &mut virtual_overseer, + current, + test_state.session_index, + &test_state.validator_public, + &test_state.validator_authority_id, + &test_state.validator_groups.0[0], + ).await; // We now should connect to our validator group. assert_matches!( @@ -448,24 +486,14 @@ fn ask_validators_for_povs() { ); // obtain the validator_id to authority_id mapping - assert_matches!( - overseer_recv(&mut virtual_overseer).await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::ValidatorDiscovery(validators, tx), - )) => { - assert_eq!(relay_parent, next_leaf); - assert_eq!(validators.len(), 3); - assert!(validators.iter().all(|v| test_state.validator_public.contains(&v))); - - let result = vec![ - Some(test_state.validator_authority_id[2].clone()), - Some(test_state.validator_authority_id[0].clone()), - Some(test_state.validator_authority_id[4].clone()), - ]; - tx.send(Ok(result)).unwrap(); - } - ); + test_validator_discovery( + &mut virtual_overseer, + next_leaf, + test_state.session_index, + &test_state.validator_public, + &test_state.validator_authority_id, + &test_state.validator_groups.0[0], + ).await; // We now should connect to our validator group. assert_matches!( @@ -716,7 +744,7 @@ fn we_inform_peers_with_same_view_we_are_awaiting() { RuntimeApiRequest::ValidatorGroups(tx) )) => { assert_eq!(relay_parent, hash_a); - tx.send(Ok(validator_groups)).unwrap(); + tx.send(Ok(validator_groups.clone())).unwrap(); } ); @@ -731,25 +759,14 @@ fn we_inform_peers_with_same_view_we_are_awaiting() { } ); - assert_matches!( - handle.recv().await, - AllMessages::RuntimeApi(RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::ValidatorDiscovery(validators_res, tx), - )) => { - assert_eq!(relay_parent, hash_a); - assert_eq!(validators_res.len(), 3); - assert!(validators_res.iter().all(|v| validators.contains(&v))); - - let result = vec![ - Some(validator_authority_id[2].clone()), - Some(validator_authority_id[0].clone()), - Some(validator_authority_id[4].clone()), - ]; - - tx.send(Ok(result)).unwrap(); - } - ); + test_validator_discovery( + &mut handle, + hash_a, + 1, + &validators, + &validator_authority_id, + &validator_groups.0[0], + ).await; assert_matches!( handle.recv().await, diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs index 750939a57a..e8794e090d 100644 --- a/polkadot/node/subsystem-util/src/lib.rs +++ b/polkadot/node/subsystem-util/src/lib.rs @@ -37,6 +37,7 @@ use polkadot_primitives::v1::{ CandidateEvent, CommittedCandidateReceipt, CoreState, EncodeAs, PersistedValidationData, GroupRotationInfo, Hash, Id as ParaId, ValidationData, OccupiedCoreAssumption, SessionIndex, Signed, SigningContext, ValidationCode, ValidatorId, ValidatorIndex, + SessionInfo, }; use sp_core::{ traits::SpawnNamed, @@ -193,6 +194,7 @@ specialize_requests! { fn request_validation_code(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option; ValidationCode; fn request_candidate_pending_availability(para_id: ParaId) -> Option; CandidatePendingAvailability; fn request_candidate_events() -> Vec; CandidateEvents; + fn request_session_info(index: SessionIndex) -> Option; SessionInfo; } /// Request some data from the `RuntimeApi` via a SubsystemContext. @@ -274,6 +276,7 @@ specialize_requests_ctx! { fn request_validation_code_ctx(para_id: ParaId, assumption: OccupiedCoreAssumption) -> Option; ValidationCode; fn request_candidate_pending_availability_ctx(para_id: ParaId) -> Option; CandidatePendingAvailability; fn request_candidate_events_ctx() -> Vec; CandidateEvents; + fn request_session_info_ctx(index: SessionIndex) -> Option; SessionInfo; } /// From the given set of validators, find the first key we can sign with, if any. diff --git a/polkadot/node/subsystem-util/src/validator_discovery.rs b/polkadot/node/subsystem-util/src/validator_discovery.rs index 357041d96b..762b800253 100644 --- a/polkadot/node/subsystem-util/src/validator_discovery.rs +++ b/polkadot/node/subsystem-util/src/validator_discovery.rs @@ -20,34 +20,20 @@ use std::collections::HashMap; use std::pin::Pin; use futures::{ - channel::{mpsc, oneshot}, + channel::mpsc, task::{Poll, self}, stream, }; use streamunordered::{StreamUnordered, StreamYield}; -use thiserror::Error; use polkadot_node_subsystem::{ - errors::RuntimeApiError, SubsystemError, - messages::{AllMessages, RuntimeApiMessage, RuntimeApiRequest, NetworkBridgeMessage}, + errors::RuntimeApiError, + messages::{AllMessages, NetworkBridgeMessage}, SubsystemContext, }; -use polkadot_primitives::v1::{Hash, ValidatorId, AuthorityDiscoveryId}; +use polkadot_primitives::v1::{Hash, ValidatorId, AuthorityDiscoveryId, SessionIndex}; use sc_network::PeerId; - -/// Error when making a request to connect to validators. -#[derive(Debug, Error)] -pub enum Error { - /// Attempted to send or receive on a oneshot channel which had been canceled - #[error(transparent)] - Oneshot(#[from] oneshot::Canceled), - /// A subsystem error. - #[error(transparent)] - Subsystem(#[from] SubsystemError), - /// An error in the Runtime API. - #[error(transparent)] - RuntimeApi(#[from] RuntimeApiError), -} +use crate::Error; /// Utility function to make it easier to connect to validators. pub async fn connect_to_validators( @@ -55,17 +41,42 @@ pub async fn connect_to_validators( relay_parent: Hash, validators: Vec, ) -> Result { - // ValidatorId -> AuthorityDiscoveryId - let (tx, rx) = oneshot::channel(); + let current_index = crate::request_session_index_for_child_ctx(relay_parent, ctx).await?.await??; + connect_to_past_session_validators(ctx, relay_parent, validators, current_index).await +} - ctx.send_message(AllMessages::RuntimeApi( - RuntimeApiMessage::Request( - relay_parent, - RuntimeApiRequest::ValidatorDiscovery(validators.clone(), tx), - ) - )).await; +/// Utility function to make it easier to connect to validators in the past sessions. +pub async fn connect_to_past_session_validators( + ctx: &mut Context, + relay_parent: Hash, + validators: Vec, + session_index: SessionIndex, +) -> Result { + let session_info = crate::request_session_info_ctx( + relay_parent, + session_index, + ctx, + ).await?.await??; + + let (session_validators, discovery_keys) = match session_info { + Some(info) => (info.validators, info.discovery_keys), + None => return Err(RuntimeApiError::from( + format!("No SessionInfo found for the index {}", session_index) + ).into()), + }; + + let id_to_index = session_validators.iter() + .zip(0usize..) + .collect::>(); + + // We assume the same ordering in authorities as in validators so we can do an index search + let maybe_authorities: Vec<_> = validators.iter() + .map(|id| { + let validator_index = id_to_index.get(&id); + validator_index.and_then(|i| discovery_keys.get(*i).cloned()) + }) + .collect(); - let maybe_authorities = rx.await??; let authorities: Vec<_> = maybe_authorities.iter() .cloned() .filter_map(|id| id) diff --git a/polkadot/node/subsystem/src/messages.rs b/polkadot/node/subsystem/src/messages.rs index 87d2981b54..979364b13c 100644 --- a/polkadot/node/subsystem/src/messages.rs +++ b/polkadot/node/subsystem/src/messages.rs @@ -31,7 +31,7 @@ use polkadot_node_primitives::{ CollationGenerationConfig, MisbehaviorReport, SignedFullStatement, ValidationResult, }; use polkadot_primitives::v1::{ - AuthorityDiscoveryId, AvailableData, BackedCandidate, BlockNumber, + AuthorityDiscoveryId, AvailableData, BackedCandidate, BlockNumber, SessionInfo, Header as BlockHeader, CandidateDescriptor, CandidateEvent, CandidateReceipt, CollatorId, CommittedCandidateReceipt, CoreState, ErasureChunk, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption, @@ -434,14 +434,8 @@ pub enum RuntimeApiRequest { /// Get all events concerning candidates (backing, inclusion, time-out) in the parent of /// the block in whose state this request is executed. CandidateEvents(RuntimeApiSender>), - /// Get the `AuthorityDiscoveryId`s corresponding to the given `ValidatorId`s. - /// Currently this request is limited to validators in the current session. - /// - /// Returns `None` for validators not found in the current session. - ValidatorDiscovery( - Vec, - RuntimeApiSender>>, - ), + /// Get the session info for the given session, if stored. + SessionInfo(SessionIndex, RuntimeApiSender>), /// Get all the pending inbound messages in the downward message queue for a para. DmqContents( ParaId, diff --git a/polkadot/primitives/src/v1.rs b/polkadot/primitives/src/v1.rs index 492be95ba3..9644c65ffc 100644 --- a/polkadot/primitives/src/v1.rs +++ b/polkadot/primitives/src/v1.rs @@ -25,6 +25,7 @@ use primitives::RuntimeDebug; use runtime_primitives::traits::AppVerify; use inherents::InherentIdentifier; use sp_arithmetic::traits::{BaseArithmetic, Saturating, Zero}; +use application_crypto::KeyTypeId; pub use runtime_primitives::traits::{BlakeTwo256, Hash as HashT}; @@ -57,6 +58,34 @@ pub use sp_authority_discovery::AuthorityId as AuthorityDiscoveryId; /// Unique identifier for the Inclusion Inherent pub const INCLUSION_INHERENT_IDENTIFIER: InherentIdentifier = *b"inclusn0"; + +/// The key type ID for a parachain approval voting key. +pub const APPROVAL_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"aprv"); + +mod approval_app { + use application_crypto::{app_crypto, sr25519}; + app_crypto!(sr25519, super::APPROVAL_KEY_TYPE_ID); +} + +/// The public key of a keypair used by a validator for approval voting +/// on included parachain candidates. +pub type ApprovalId = approval_app::Public; + +/// The key type ID for parachain assignment key. +pub const ASSIGNMENT_KEY_TYPE_ID: KeyTypeId = KeyTypeId(*b"asgn"); + +// The public key of a keypair used by a validator for determining assignments +/// to approve included parachain candidates. +mod assigment_app { + use application_crypto::{app_crypto, sr25519}; + app_crypto!(sr25519, super::ASSIGNMENT_KEY_TYPE_ID); +} + +/// The public key of a keypair used by a validator for determining assignments +/// to approve included parachain candidates. +pub type AssignmentId = assigment_app::Public; + + /// Get a collator signature payload on a relay-parent, block-data combo. pub fn collator_signature_payload>( relay_parent: &H, @@ -671,6 +700,35 @@ pub enum CandidateEvent { CandidateTimedOut(CandidateReceipt, HeadData), } +/// Information about validator sets of a session. +#[derive(Clone, Encode, Decode, RuntimeDebug)] +#[cfg_attr(feature = "std", derive(PartialEq, Default))] +pub struct SessionInfo { + /// Validators in canonical ordering. + pub validators: Vec, + /// Validators' authority discovery keys for the session in canonical ordering. + pub discovery_keys: Vec, + /// The assignment and approval keys for validators. + pub approval_keys: Vec<(ApprovalId, AssignmentId)>, + /// Validators in shuffled ordering - these are the validator groups as produced + /// by the `Scheduler` module for the session and are typically referred to by + /// `GroupIndex`. + pub validator_groups: Vec>, + /// The number of availability cores used by the protocol during this session. + pub n_cores: u32, + /// The zeroth delay tranche width. + pub zeroth_delay_tranche_width: u32, + /// The number of samples we do of relay_vrf_modulo. + pub relay_vrf_modulo_samples: u32, + /// The number of delay tranches in total. + pub n_delay_tranches: u32, + /// How many slots (BABE / SASSAFRAS) must pass before an assignment is considered a + /// no-show. + pub no_show_slots: u32, + /// The number of validators needed to approve a block. + pub needed_approvals: u32, +} + sp_api::decl_runtime_apis! { /// The API for querying the state of parachains on-chain. pub trait ParachainHost { @@ -710,6 +768,9 @@ sp_api::decl_runtime_apis! { /// This can be used to instantiate a `SigningContext`. fn session_index_for_child() -> SessionIndex; + /// Get the session info for the given session, if stored. + fn session_info(index: SessionIndex) -> Option; + /// Fetch the validation code used by a para, making the given `OccupiedCoreAssumption`. /// /// Returns `None` if either the para is not registered or the assumption is `Freed` @@ -735,13 +796,6 @@ sp_api::decl_runtime_apis! { #[skip_initialize_block] fn candidate_events() -> Vec>; - /// Get the `AuthorityDiscoveryId`s corresponding to the given `ValidatorId`s. - /// Currently this request is limited to validators in the current session. - /// - /// We assume that every validator runs authority discovery, - /// which would allow us to establish point-to-point connection to given validators. - fn validator_discovery(validators: Vec) -> Vec>; - /// Get all the pending inbound messages in the downward message queue for a para. fn dmq_contents( recipient: Id, diff --git a/polkadot/roadmap/implementers-guide/src/runtime/initializer.md b/polkadot/roadmap/implementers-guide/src/runtime/initializer.md index fd7324b219..361fab38c8 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/initializer.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/initializer.md @@ -23,7 +23,7 @@ The other parachains modules are initialized in this order: 1. Paras 1. Scheduler 1. Inclusion -1. Validity +1. SessionInfo 1. DMP 1. UMP 1. HRMP diff --git a/polkadot/roadmap/implementers-guide/src/runtime/session_info.md b/polkadot/roadmap/implementers-guide/src/runtime/session_info.md index 697e79fab5..ac2ad926dd 100644 --- a/polkadot/roadmap/implementers-guide/src/runtime/session_info.md +++ b/polkadot/roadmap/implementers-guide/src/runtime/session_info.md @@ -30,11 +30,11 @@ struct SessionInfo { // no-show. no_show_slots: u32, /// The number of validators needed to approve a block. - needed_approvals: u32, + needed_approvals: u32, } ``` -Storage Layout: +Storage Layout: ```rust /// The earliest session for which previous session info is stored. @@ -45,11 +45,10 @@ Sessions: map SessionIndex => Option, ## Session Change -1. Update the `CurrentSessionIndex`. 1. Update `EarliestStoredSession` based on `config.dispute_period` and remove all entries from `Sessions` from the previous value up to the new value. 1. Create a new entry in `Sessions` with information about the current session. ## Routines * `earliest_stored_session() -> SessionIndex`: Yields the earliest session for which we have information stored. -* `session_info(session: SessionIndex) -> Option`: Yields the session info for the given session, if stored. \ No newline at end of file +* `session_info(session: SessionIndex) -> Option`: Yields the session info for the given session, if stored. diff --git a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md index daebbab34a..942d597533 100644 --- a/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md +++ b/polkadot/roadmap/implementers-guide/src/types/overseer-protocol.md @@ -424,14 +424,8 @@ enum RuntimeApiRequest { Validators(ResponseChannel>), /// Get the validator groups and rotation info. ValidatorGroups(ResponseChannel<(Vec>, GroupRotationInfo)>), - /// Get the session index for children of the block. This can be used to construct a signing - /// context. - SessionIndex(ResponseChannel), - /// Get the validation code for a specific para, using the given occupied core assumption. - ValidationCode(ParaId, OccupiedCoreAssumption, ResponseChannel>), - /// Fetch the historical validation code used by a para for candidates executed in - /// the context of a given block height in the current chain. - HistoricalValidationCode(ParaId, BlockNumber, ResponseChannel>), + /// Get information about all availability cores. + AvailabilityCores(ResponseChannel>), /// with the given occupied core assumption. PersistedValidationData( ParaId, @@ -450,12 +444,25 @@ enum RuntimeApiRequest { CandidateCommitments, RuntimeApiSender, ), - /// Get information about all availability cores. - AvailabilityCores(ResponseChannel>), + /// Get the session index for children of the block. This can be used to construct a signing + /// context. + SessionIndexForChild(ResponseChannel), + /// Get the validation code for a specific para, using the given occupied core assumption. + ValidationCode(ParaId, OccupiedCoreAssumption, ResponseChannel>), + /// Fetch the historical validation code used by a para for candidates executed in + /// the context of a given block height in the current chain. + HistoricalValidationCode(ParaId, BlockNumber, ResponseChannel>), /// Get a committed candidate receipt for all candidates pending availability. CandidatePendingAvailability(ParaId, ResponseChannel>), /// Get all events concerning candidates in the last block. CandidateEvents(ResponseChannel>), + /// Get the session info for the given session, if stored. + SessionInfo(SessionIndex, ResponseChannel>), + /// Get all the pending inbound messages in the downward message queue for a para. + DmqContents(ParaId, ResponseChannel>>), + /// Get the contents of all channels addressed to the given recipient. Channels that have no + /// messages in them are also included. + InboundHrmpChannelsContents(ParaId, ResponseChannel>>>), } enum RuntimeApiMessage { diff --git a/polkadot/roadmap/implementers-guide/src/types/runtime.md b/polkadot/roadmap/implementers-guide/src/types/runtime.md index 70c7237e1c..7900a6edab 100644 --- a/polkadot/roadmap/implementers-guide/src/types/runtime.md +++ b/polkadot/roadmap/implementers-guide/src/types/runtime.md @@ -12,6 +12,9 @@ struct HostConfiguration { pub validation_upgrade_frequency: BlockNumber, /// The delay, in blocks, before a validation upgrade is applied. pub validation_upgrade_delay: BlockNumber, + /// The acceptance period, in blocks. This is the amount of blocks after availability that validators + /// and fishermen have to perform secondary checks or issue reports. + pub acceptance_period: BlockNumber, /// The maximum validation code size, in bytes. pub max_code_size: u32, /// The maximum head-data size, in bytes. @@ -37,6 +40,8 @@ struct HostConfiguration { /// submitting an approval vote before a validator is considered a no-show. /// Must be at least 1. pub no_show_slots: u32, + /// The number of delay tranches in total. + pub n_delay_tranches: u32, /// The width of the zeroth delay tranche for approval assignments. This many delay tranches /// beyond 0 are all consolidated to form a wide 0 tranche. pub zeroth_delay_tranche_width: u32, diff --git a/polkadot/runtime/common/src/paras_registrar.rs b/polkadot/runtime/common/src/paras_registrar.rs index 474402d61f..8427bc3f4a 100644 --- a/polkadot/runtime/common/src/paras_registrar.rs +++ b/polkadot/runtime/common/src/paras_registrar.rs @@ -262,14 +262,14 @@ mod tests { }, testing::{UintAuthorityId, TestXt}, Perbill, curve::PiecewiseLinear, }; use primitives::v1::{ - Balance, BlockNumber, Header, Signature, + Balance, BlockNumber, Header, Signature, AuthorityDiscoveryId, }; use frame_support::{ traits::{Randomness, OnInitialize, OnFinalize}, impl_outer_origin, impl_outer_dispatch, assert_ok, parameter_types, }; use keyring::Sr25519Keyring; - use runtime_parachains::{initializer, configuration, inclusion, scheduler, dmp, ump, hrmp}; + use runtime_parachains::{initializer, configuration, inclusion, session_info, scheduler, dmp, ump, hrmp}; use pallet_session::OneSessionHandler; impl_outer_origin! { @@ -477,6 +477,14 @@ mod tests { type Event = (); } + impl session_info::AuthorityDiscoveryTrait for Test { + fn authorities() -> Vec { + Vec::new() + } + } + + impl session_info::Trait for Test { } + pub struct TestRandomness; impl Randomness for TestRandomness { diff --git a/polkadot/runtime/kusama/src/lib.rs b/polkadot/runtime/kusama/src/lib.rs index c30bf59a6a..0aa1a4cadb 100644 --- a/polkadot/runtime/kusama/src/lib.rs +++ b/polkadot/runtime/kusama/src/lib.rs @@ -29,7 +29,7 @@ use primitives::v1::{ AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash, Id, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, Signature, ValidationCode, ValidationData, ValidatorId, ValidatorIndex, - InboundDownwardMessage, InboundHrmpMessage, + InboundDownwardMessage, InboundHrmpMessage, SessionInfo, }; use runtime_common::{ claims, SlowAdjustingFeeUpdate, CurrencyToVote, @@ -1092,6 +1092,10 @@ sp_api::impl_runtime_apis! { 0 } + fn session_info(_: SessionIndex) -> Option { + None + } + fn validation_code(_: Id, _: OccupiedCoreAssumption) -> Option { None } @@ -1108,10 +1112,6 @@ sp_api::impl_runtime_apis! { Vec::new() } - fn validator_discovery(_: Vec) -> Vec> { - Vec::new() - } - fn dmq_contents( _recipient: Id, ) -> Vec> { diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index 98cb403c9a..96ab3dd88a 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -19,9 +19,10 @@ //! Configuration can change only at session boundaries and is buffered until then. use sp_std::prelude::*; -use primitives::v1::{Balance, ValidatorId}; +use primitives::v1::{Balance, ValidatorId, SessionIndex}; use frame_support::{ decl_storage, decl_module, decl_error, + ensure, dispatch::DispatchResult, weights::{DispatchClass, Weight}, }; @@ -60,6 +61,21 @@ pub struct HostConfiguration { pub thread_availability_period: BlockNumber, /// The amount of blocks ahead to schedule parachains and parathreads. pub scheduling_lookahead: u32, + /// The amount of sessions to keep for disputes. + pub dispute_period: SessionIndex, + /// The amount of consensus slots that must pass between submitting an assignment and + /// submitting an approval vote before a validator is considered a no-show. + /// Must be at least 1. + pub no_show_slots: u32, + /// The number of delay tranches in total. + pub n_delay_tranches: u32, + /// The width of the zeroth delay tranche for approval assignments. This many delay tranches + /// beyond 0 are all consolidated to form a wide 0 tranche. + pub zeroth_delay_tranche_width: u32, + /// The number of validators needed to approve a block. + pub needed_approvals: u32, + /// The number of samples to do of the RelayVRFModulo approval assignment criterion. + pub relay_vrf_modulo_samples: u32, /// Total number of individual messages allowed in the parachain -> relay-chain message queue. pub max_upward_queue_count: u32, /// Total size of messages allowed in the parachain -> relay-chain message queue before which @@ -255,6 +271,68 @@ decl_module! { Ok(()) } + /// Set the dispute period, in number of sessions to keep for disputes. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_dispute_period(origin, new: SessionIndex) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.dispute_period, new) != new + }); + Ok(()) + } + + /// Set the no show slots, in number of number of consensus slots. + /// Must be at least 1. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_no_show_slots(origin, new: u32) -> DispatchResult { + ensure_root(origin)?; + ensure!(new >= 1, "no_show_slots must be at least 1"); + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.no_show_slots, new) != new + }); + Ok(()) + } + + /// Set the total number of delay tranches. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_n_delay_tranches(origin, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.n_delay_tranches, new) != new + }); + Ok(()) + } + + /// Set the zeroth delay tranche width. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_zeroth_delay_tranche_width(origin, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.zeroth_delay_tranche_width, new) != new + }); + Ok(()) + } + + /// Set the number of validators needed to approve a block. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_needed_approvals(origin, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.needed_approvals, new) != new + }); + Ok(()) + } + + /// Set the number of samples to do of the RelayVRFModulo approval assignment criterion. + #[weight = (1_000, DispatchClass::Operational)] + pub fn set_relay_vrf_modulo_samples(origin, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::update_config_member(|config| { + sp_std::mem::replace(&mut config.relay_vrf_modulo_samples, new) != new + }); + Ok(()) + } + /// Sets the maximum items that can present in a upward dispatch queue at once. #[weight = (1_000, DispatchClass::Operational)] pub fn set_max_upward_queue_count(origin, new: u32) -> DispatchResult { @@ -504,6 +582,12 @@ mod tests { chain_availability_period: 10, thread_availability_period: 8, scheduling_lookahead: 3, + dispute_period: 239, + no_show_slots: 240, + n_delay_tranches: 241, + zeroth_delay_tranche_width: 242, + needed_approvals: 242, + relay_vrf_modulo_samples: 243, max_upward_queue_count: 1337, max_upward_queue_size: 228, max_downward_message_size: 2048, @@ -561,6 +645,24 @@ mod tests { Configuration::set_scheduling_lookahead( Origin::root(), new_config.scheduling_lookahead, ).unwrap(); + Configuration::set_dispute_period( + Origin::root(), new_config.dispute_period, + ).unwrap(); + Configuration::set_no_show_slots( + Origin::root(), new_config.no_show_slots, + ).unwrap(); + Configuration::set_n_delay_tranches( + Origin::root(), new_config.n_delay_tranches, + ).unwrap(); + Configuration::set_zeroth_delay_tranche_width( + Origin::root(), new_config.zeroth_delay_tranche_width, + ).unwrap(); + Configuration::set_needed_approvals( + Origin::root(), new_config.needed_approvals, + ).unwrap(); + Configuration::set_relay_vrf_modulo_samples( + Origin::root(), new_config.relay_vrf_modulo_samples, + ).unwrap(); Configuration::set_max_upward_queue_count( Origin::root(), new_config.max_upward_queue_count, ).unwrap(); diff --git a/polkadot/runtime/parachains/src/initializer.rs b/polkadot/runtime/parachains/src/initializer.rs index ea9348d79f..7ab41e4633 100644 --- a/polkadot/runtime/parachains/src/initializer.rs +++ b/polkadot/runtime/parachains/src/initializer.rs @@ -29,7 +29,7 @@ use sp_runtime::traits::One; use parity_scale_codec::{Encode, Decode}; use crate::{ configuration::{self, HostConfiguration}, - paras, scheduler, inclusion, dmp, ump, hrmp, + paras, scheduler, inclusion, session_info, dmp, ump, hrmp, }; /// Information about a session change that has just occurred. @@ -63,6 +63,7 @@ pub trait Trait: + paras::Trait + scheduler::Trait + inclusion::Trait + + session_info::Trait + dmp::Trait + ump::Trait + hrmp::Trait @@ -123,6 +124,7 @@ decl_module! { // - Paras // - Scheduler // - Inclusion + // - SessionInfo // - Validity // - DMP // - UMP @@ -131,6 +133,7 @@ decl_module! { paras::Module::::initializer_initialize(now) + scheduler::Module::::initializer_initialize(now) + inclusion::Module::::initializer_initialize(now) + + session_info::Module::::initializer_initialize(now) + dmp::Module::::initializer_initialize(now) + ump::Module::::initializer_initialize(now) + hrmp::Module::::initializer_initialize(now); @@ -146,6 +149,7 @@ decl_module! { hrmp::Module::::initializer_finalize(); ump::Module::::initializer_finalize(); dmp::Module::::initializer_finalize(); + session_info::Module::::initializer_finalize(); inclusion::Module::::initializer_finalize(); scheduler::Module::::initializer_finalize(); paras::Module::::initializer_finalize(); @@ -189,6 +193,7 @@ impl Module { paras::Module::::initializer_on_new_session(¬ification); scheduler::Module::::initializer_on_new_session(¬ification); inclusion::Module::::initializer_on_new_session(¬ification); + session_info::Module::::initializer_on_new_session(¬ification); dmp::Module::::initializer_on_new_session(¬ification); ump::Module::::initializer_on_new_session(¬ification); hrmp::Module::::initializer_on_new_session(¬ification); diff --git a/polkadot/runtime/parachains/src/lib.rs b/polkadot/runtime/parachains/src/lib.rs index 3691b41c36..15c41dce17 100644 --- a/polkadot/runtime/parachains/src/lib.rs +++ b/polkadot/runtime/parachains/src/lib.rs @@ -28,6 +28,7 @@ pub mod inclusion_inherent; pub mod initializer; pub mod paras; pub mod scheduler; +pub mod session_info; pub mod validity; pub mod origin; pub mod dmp; diff --git a/polkadot/runtime/parachains/src/mock.rs b/polkadot/runtime/parachains/src/mock.rs index edb84e2a12..702dac71eb 100644 --- a/polkadot/runtime/parachains/src/mock.rs +++ b/polkadot/runtime/parachains/src/mock.rs @@ -24,7 +24,7 @@ use sp_runtime::{ BlakeTwo256, IdentityLookup, }, }; -use primitives::v1::{BlockNumber, Header}; +use primitives::v1::{AuthorityDiscoveryId, BlockNumber, Header}; use frame_support::{ impl_outer_origin, impl_outer_dispatch, impl_outer_event, parameter_types, weights::Weight, traits::Randomness as RandomnessT, @@ -124,6 +124,14 @@ impl crate::inclusion::Trait for Test { type Event = TestEvent; } +impl crate::session_info::Trait for Test { } + +impl crate::session_info::AuthorityDiscoveryTrait for Test { + fn authorities() -> Vec { + Vec::new() + } +} + pub type System = frame_system::Module; /// Mocked initializer. @@ -150,6 +158,9 @@ pub type Scheduler = crate::scheduler::Module; /// Mocked inclusion module. pub type Inclusion = crate::inclusion::Module; +/// Mocked session info module. +pub type SessionInfo = crate::session_info::Module; + /// Create a new set of test externalities. pub fn new_test_ext(state: GenesisConfig) -> TestExternalities { let mut t = state.system.build_storage::().unwrap(); diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs index 2f49f4af8c..abecbbaeb1 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v1.rs @@ -23,12 +23,12 @@ use primitives::v1::{ ValidatorId, ValidatorIndex, GroupRotationInfo, CoreState, ValidationData, Id as ParaId, OccupiedCoreAssumption, SessionIndex, ValidationCode, CommittedCandidateReceipt, ScheduledCore, OccupiedCore, CoreOccupied, CoreIndex, - GroupIndex, CandidateEvent, PersistedValidationData, AuthorityDiscoveryId, + GroupIndex, CandidateEvent, PersistedValidationData, SessionInfo, InboundDownwardMessage, InboundHrmpMessage, }; use sp_runtime::traits::Zero; use frame_support::debug; -use crate::{initializer, inclusion, scheduler, configuration, paras, dmp, hrmp}; +use crate::{initializer, inclusion, scheduler, configuration, paras, session_info, dmp, hrmp}; /// Implementation for the `validators` function of the runtime API. pub fn validators() -> Vec { @@ -285,28 +285,9 @@ where .collect() } -/// Get the `AuthorityDiscoveryId`s corresponding to the given `ValidatorId`s. -/// Currently this request is limited to validators in the current session. -/// -/// We assume that every validator runs authority discovery, -/// which would allow us to establish point-to-point connection to given validators. -// FIXME: handle previous sessions: -// https://github.com/paritytech/polkadot/issues/1461 -pub fn validator_discovery(validators: Vec) -> Vec> -where - T: initializer::Trait + pallet_authority_discovery::Trait, -{ - // FIXME: the mapping might be invalid if a session change happens in between the calls - // use SessionInfo from https://github.com/paritytech/polkadot/pull/1691 - let current_validators = >::validators(); - let authorities = >::authorities(); - // We assume the same ordering in authorities as in validators so we can do an index search - validators.iter().map(|id| { - // FIXME: linear search is slow O(n^2) - // use SessionInfo from https://github.com/paritytech/polkadot/pull/1691 - let validator_index = current_validators.iter().position(|v| v == id); - validator_index.and_then(|i| authorities.get(i).cloned()) - }).collect() +/// Get the session info for the given session, if stored. +pub fn session_info(index: SessionIndex) -> Option { + >::session_info(index) } /// Implementation for the `dmq_contents` function of the runtime API. diff --git a/polkadot/runtime/parachains/src/session_info.rs b/polkadot/runtime/parachains/src/session_info.rs new file mode 100644 index 0000000000..2a183c909f --- /dev/null +++ b/polkadot/runtime/parachains/src/session_info.rs @@ -0,0 +1,256 @@ +// Copyright 2020 Parity Technologies (UK) Ltd. +// This file is part of Polkadot. + +// Polkadot is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Polkadot is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Polkadot. If not, see . + +//! The session info module provides information about validator sets +//! from prior sessions needed for approvals and disputes. +//! +//! See https://w3f.github.io/parachain-implementers-guide/runtime/session_info.html. + +use primitives::v1::{AuthorityDiscoveryId, SessionIndex, SessionInfo}; +use frame_support::{ + decl_storage, decl_module, decl_error, + weights::Weight, +}; +use crate::{configuration, paras, scheduler}; +use sp_std::{cmp, vec::Vec}; + +pub trait Trait: + frame_system::Trait + + configuration::Trait + + paras::Trait + + scheduler::Trait + + AuthorityDiscoveryTrait +{ +} + +decl_storage! { + trait Store for Module as ParaSessionInfo { + /// The earliest session for which previous session info is stored. + EarliestStoredSession get(fn earliest_stored_session): SessionIndex; + /// Session information in a rolling window. + /// Should have an entry in range `EarliestStoredSession..=CurrentSessionIndex`. + /// Does not have any entries before the session index in the first session change notification. + Sessions get(fn session_info): map hasher(identity) SessionIndex => Option; + } +} + +decl_error! { + pub enum Error for Module { } +} + +decl_module! { + /// The session info module. + pub struct Module for enum Call where origin: ::Origin { + type Error = Error; + } +} + +/// An abstraction for the authority discovery pallet +/// to help with mock testing. +pub trait AuthorityDiscoveryTrait { + /// Retrieve authority identifiers of the current and next authority set. + fn authorities() -> Vec; +} + +impl AuthorityDiscoveryTrait for T { + fn authorities() -> Vec { + >::authorities() + } +} + +impl Module { + /// Handle an incoming session change. + pub(crate) fn initializer_on_new_session( + notification: &crate::initializer::SessionChangeNotification + ) { + let config = >::config(); + + let dispute_period = config.dispute_period; + let n_parachains = >::parachains().len() as u32; + + let validators = notification.validators.clone(); + let discovery_keys = ::authorities(); + // FIXME: once we store these keys: https://github.com/paritytech/polkadot/issues/1975 + let approval_keys = Default::default(); + let validator_groups = >::validator_groups(); + let n_cores = n_parachains + config.parathread_cores; + let zeroth_delay_tranche_width = config.zeroth_delay_tranche_width; + let relay_vrf_modulo_samples = config.relay_vrf_modulo_samples; + let n_delay_tranches = config.n_delay_tranches; + let no_show_slots = config.no_show_slots; + let needed_approvals = config.needed_approvals; + + let new_session_index = notification.session_index; + let old_earliest_stored_session = EarliestStoredSession::get(); + let dispute_period = cmp::max(1, dispute_period); + let new_earliest_stored_session = new_session_index.checked_sub(dispute_period - 1).unwrap_or(0); + let new_earliest_stored_session = cmp::max(new_earliest_stored_session, old_earliest_stored_session); + // update `EarliestStoredSession` based on `config.dispute_period` + EarliestStoredSession::set(new_earliest_stored_session); + // remove all entries from `Sessions` from the previous value up to the new value + for idx in old_earliest_stored_session..new_earliest_stored_session { + Sessions::remove(&idx); + } + // create a new entry in `Sessions` with information about the current session + let new_session_info = SessionInfo { + validators, + discovery_keys, + approval_keys, + validator_groups, + n_cores, + zeroth_delay_tranche_width, + relay_vrf_modulo_samples, + n_delay_tranches, + no_show_slots, + needed_approvals, + }; + Sessions::insert(&new_session_index, &new_session_info); + } + + /// Called by the initializer to initialize the session info module. + pub(crate) fn initializer_initialize(_now: T::BlockNumber) -> Weight { + 0 + } + + /// Called by the initializer to finalize the session info module. + pub(crate) fn initializer_finalize() {} +} + + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{ + new_test_ext, Configuration, SessionInfo, System, GenesisConfig as MockGenesisConfig, + Origin, + }; + use crate::initializer::SessionChangeNotification; + use crate::configuration::HostConfiguration; + use frame_support::traits::{OnFinalize, OnInitialize}; + use primitives::v1::BlockNumber; + + fn run_to_block( + to: BlockNumber, + new_session: impl Fn(BlockNumber) -> Option>, + ) { + while System::block_number() < to { + let b = System::block_number(); + + SessionInfo::initializer_finalize(); + Configuration::initializer_finalize(); + + System::on_finalize(b); + + System::on_initialize(b + 1); + System::set_block_number(b + 1); + + if let Some(notification) = new_session(b + 1) { + Configuration::initializer_on_new_session(¬ification.validators, ¬ification.queued); + SessionInfo::initializer_on_new_session(¬ification); + } + + Configuration::initializer_initialize(b + 1); + SessionInfo::initializer_initialize(b + 1); + } + } + + fn default_config() -> HostConfiguration { + HostConfiguration { + parathread_cores: 1, + dispute_period: 2, + needed_approvals: 3, + ..Default::default() + } + } + + fn genesis_config() -> MockGenesisConfig { + MockGenesisConfig { + configuration: configuration::GenesisConfig { + config: default_config(), + ..Default::default() + }, + ..Default::default() + } + } + + fn session_changes(n: BlockNumber) -> Option> { + match n { + 100 => Some(SessionChangeNotification { + session_index: 10, + ..Default::default() + }), + 200 => Some(SessionChangeNotification { + session_index: 20, + ..Default::default() + }), + 300 => Some(SessionChangeNotification { + session_index: 30, + ..Default::default() + }), + 400 => Some(SessionChangeNotification { + session_index: 40, + ..Default::default() + }), + _ => None, + } + } + + fn new_session_every_block(n: BlockNumber) -> Option> { + Some(SessionChangeNotification{ + session_index: n, + ..Default::default() + }) + } + + #[test] + fn session_pruning_is_based_on_dispute_deriod() { + new_test_ext(genesis_config()).execute_with(|| { + run_to_block(100, session_changes); + assert_eq!(EarliestStoredSession::get(), 9); + + // changing dispute_period works + let dispute_period = 5; + Configuration::set_dispute_period(Origin::root(), dispute_period).unwrap(); + run_to_block(200, session_changes); + assert_eq!(EarliestStoredSession::get(), 20 - dispute_period + 1); + + // we don't have that many sessions stored + let new_dispute_period = 16; + Configuration::set_dispute_period(Origin::root(), new_dispute_period).unwrap(); + run_to_block(300, session_changes); + assert_eq!(EarliestStoredSession::get(), 20 - dispute_period + 1); + + // now we do + run_to_block(400, session_changes); + assert_eq!(EarliestStoredSession::get(), 40 - new_dispute_period + 1); + }) + } + + #[test] + fn session_info_is_based_on_config() { + new_test_ext(genesis_config()).execute_with(|| { + run_to_block(1, new_session_every_block); + let session = Sessions::get(&1).unwrap(); + assert_eq!(session.needed_approvals, 3); + + // change some param + Configuration::set_needed_approvals(Origin::root(), 42).unwrap(); + run_to_block(2, new_session_every_block); + let session = Sessions::get(&2).unwrap(); + assert_eq!(session.needed_approvals, 42); + }) + } +} diff --git a/polkadot/runtime/polkadot/src/lib.rs b/polkadot/runtime/polkadot/src/lib.rs index 8d9e9897a6..b12c98541e 100644 --- a/polkadot/runtime/polkadot/src/lib.rs +++ b/polkadot/runtime/polkadot/src/lib.rs @@ -37,7 +37,7 @@ use primitives::v1::{ AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash, Id, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, Signature, ValidationCode, ValidationData, ValidatorId, ValidatorIndex, - InboundDownwardMessage, InboundHrmpMessage, + InboundDownwardMessage, InboundHrmpMessage, SessionInfo, }; use sp_runtime::{ create_runtime_str, generic, impl_opaque_keys, ModuleId, ApplyExtrinsicResult, @@ -1086,6 +1086,10 @@ sp_api::impl_runtime_apis! { 0 } + fn session_info(_: SessionIndex) -> Option { + None + } + fn validation_code(_: Id, _: OccupiedCoreAssumption) -> Option { None } @@ -1102,10 +1106,6 @@ sp_api::impl_runtime_apis! { Vec::new() } - fn validator_discovery(_: Vec) -> Vec> { - Vec::new() - } - fn dmq_contents( _recipient: Id, ) -> Vec> { diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 07e3e0c433..2cc0a83398 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -28,7 +28,7 @@ use primitives::v1::{ AccountId, AccountIndex, Balance, BlockNumber, Hash, Nonce, Signature, Moment, GroupRotationInfo, CoreState, Id, ValidationData, ValidationCode, CandidateEvent, ValidatorId, ValidatorIndex, CommittedCandidateReceipt, OccupiedCoreAssumption, - PersistedValidationData, InboundDownwardMessage, InboundHrmpMessage, + PersistedValidationData, InboundDownwardMessage, InboundHrmpMessage, SessionInfo, }; use runtime_common::{ SlowAdjustingFeeUpdate, @@ -72,6 +72,7 @@ use runtime_parachains::configuration as parachains_configuration; use runtime_parachains::inclusion as parachains_inclusion; use runtime_parachains::inclusion_inherent as parachains_inclusion_inherent; use runtime_parachains::initializer as parachains_initializer; +use runtime_parachains::session_info as parachains_session_info; use runtime_parachains::paras as parachains_paras; use runtime_parachains::dmp as parachains_dmp; use runtime_parachains::ump as parachains_ump; @@ -536,6 +537,8 @@ impl parachains_paras::Trait for Runtime { type Origin = Origin; } +impl parachains_session_info::Trait for Runtime {} + impl parachains_ump::Trait for Runtime { type UmpSink = (); // TODO: #1873 To be handled by the XCM receiver. } @@ -688,8 +691,9 @@ sp_api::impl_runtime_apis! { } }) } - fn validator_discovery(validators: Vec) -> Vec> { - runtime_api_impl::validator_discovery::(validators) + + fn session_info(index: SessionIndex) -> Option { + runtime_api_impl::session_info::(index) } fn dmq_contents(recipient: Id) -> Vec> { diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index fffcd892e6..abfb734ccd 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -29,6 +29,7 @@ use polkadot_runtime_parachains::configuration as parachains_configuration; use polkadot_runtime_parachains::inclusion as parachains_inclusion; use polkadot_runtime_parachains::inclusion_inherent as parachains_inclusion_inherent; use polkadot_runtime_parachains::initializer as parachains_initializer; +use polkadot_runtime_parachains::session_info as parachains_session_info; use polkadot_runtime_parachains::paras as parachains_paras; use polkadot_runtime_parachains::dmp as parachains_dmp; use polkadot_runtime_parachains::ump as parachains_ump; @@ -40,7 +41,7 @@ use primitives::v1::{ AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash as HashT, Id as ParaId, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, Signature, ValidationCode, ValidationData, ValidatorId, ValidatorIndex, - InboundDownwardMessage, InboundHrmpMessage, + InboundDownwardMessage, InboundHrmpMessage, SessionInfo, }; use runtime_common::{ claims, SlowAdjustingFeeUpdate, paras_sudo_wrapper, @@ -457,6 +458,8 @@ impl parachains_initializer::Trait for Runtime { type Randomness = RandomnessCollectiveFlip; } +impl parachains_session_info::Trait for Runtime {} + impl parachains_paras::Trait for Runtime { type Origin = Origin; } @@ -678,8 +681,8 @@ sp_api::impl_runtime_apis! { runtime_impl::candidate_events::(|trait_event| trait_event.try_into().ok()) } - fn validator_discovery(validators: Vec) -> Vec> { - runtime_impl::validator_discovery::(validators) + fn session_info(index: SessionIndex) -> Option { + runtime_impl::session_info::(index) } fn dmq_contents( diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 08e0ab6a28..e8f55dead8 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -28,7 +28,7 @@ use primitives::v1::{ AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash, Id, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, Signature, ValidationCode, ValidationData, ValidatorId, ValidatorIndex, - InboundDownwardMessage, InboundHrmpMessage, + InboundDownwardMessage, InboundHrmpMessage, SessionInfo, }; use runtime_common::{ SlowAdjustingFeeUpdate, CurrencyToVote, @@ -840,6 +840,10 @@ sp_api::impl_runtime_apis! { 0 } + fn session_info(_: SessionIndex) -> Option { + None + } + fn validation_code(_: Id, _: OccupiedCoreAssumption) -> Option { None } @@ -852,10 +856,6 @@ sp_api::impl_runtime_apis! { Vec::new() } - fn validator_discovery(_: Vec) -> Vec> { - Vec::new() - } - fn dmq_contents( _recipient: Id, ) -> Vec> {