From 19b44a9af127725a506508ef39e6965860a290ef Mon Sep 17 00:00:00 2001 From: Alin Dima Date: Thu, 31 Aug 2023 14:01:36 +0300 Subject: [PATCH] backing: move the min votes threshold to the runtime (#1200) * move min backing votes const to runtime also cache it per-session in the backing subsystem Signed-off-by: alindima * add runtime migration * introduce api versioning for min_backing votes also enable it for rococo/versi for testing * also add min_backing_votes runtime calls to statement-distribution this dependency has been recently introduced by async backing * remove explicit version runtime API call this is not needed, as the RuntimeAPISubsystem already takes care of versioning and will return NotSupported if the version is not right. * address review comments - parametrise backing votes runtime API with session index - remove RuntimeInfo usage in backing subsystem, as runtime API caches the min backing votes by session index anyway. - move the logic for adjusting the configured needed backing votes with the size of the backing group to a primitives helper. - move the legacy min backing votes value to a primitives helper. - mark JoinMultiple error as fatal, since the Canceled (non-multiple) counterpart is also fatal. - make backing subsystem handle fatal errors for new leaves update. - add HostConfiguration consistency check for zeroed backing votes threshold - add cumulus accompanying change * fix cumulus test compilation * fix tests * more small fixes * fix merge * bump runtime api version for westend and rollback version for rococo --------- Signed-off-by: alindima Co-authored-by: Javier Viola --- .../src/blockchain_rpc_client.rs | 8 + .../src/rpc_client.rs | 10 + .../emulated/common/src/lib.rs | 1 + polkadot/node/core/backing/src/error.rs | 1 + polkadot/node/core/backing/src/lib.rs | 55 +-- polkadot/node/core/backing/src/tests/mod.rs | 31 +- .../src/tests/prospective_parachains.rs | 30 +- polkadot/node/core/runtime-api/src/cache.rs | 15 + polkadot/node/core/runtime-api/src/lib.rs | 18 + polkadot/node/core/runtime-api/src/tests.rs | 4 + .../network/statement-distribution/Cargo.toml | 2 +- .../src/vstaging/grid.rs | 2 +- .../src/vstaging/groups.rs | 17 +- .../src/vstaging/mod.rs | 21 +- .../src/vstaging/tests/mod.rs | 18 +- polkadot/node/primitives/src/lib.rs | 7 - polkadot/node/subsystem-types/src/messages.rs | 5 + .../subsystem-types/src/runtime_client.rs | 16 + .../node/subsystem-util/src/runtime/error.rs | 4 +- .../node/subsystem-util/src/runtime/mod.rs | 45 ++- polkadot/primitives/src/lib.rs | 32 +- polkadot/primitives/src/runtime_api.rs | 7 + polkadot/primitives/src/v5/mod.rs | 12 + polkadot/runtime/kusama/src/lib.rs | 2 + .../runtime/parachains/src/configuration.rs | 29 +- .../parachains/src/configuration/migration.rs | 1 + .../src/configuration/migration/v8.rs | 104 +++++- .../src/configuration/migration/v9.rs | 321 ++++++++++++++++++ .../parachains/src/configuration/tests.rs | 6 + .../runtime/parachains/src/inclusion/mod.rs | 28 +- .../runtime/parachains/src/inclusion/tests.rs | 27 +- .../parachains/src/paras_inherent/tests.rs | 239 ++++++------- .../src/runtime_api_impl/vstaging.rs | 5 + polkadot/runtime/polkadot/src/lib.rs | 2 + polkadot/runtime/rococo/src/lib.rs | 1 + polkadot/runtime/westend/src/lib.rs | 10 +- polkadot/statement-table/src/generic.rs | 33 +- 37 files changed, 917 insertions(+), 252 deletions(-) create mode 100644 polkadot/runtime/parachains/src/configuration/migration/v9.rs diff --git a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs index 0a3d61e83e512..57e16bc4283ce 100644 --- a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs +++ b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs @@ -338,6 +338,14 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { .await?) } + async fn minimum_backing_votes( + &self, + at: Hash, + session_index: polkadot_primitives::SessionIndex, + ) -> Result { + Ok(self.rpc_client.parachain_host_minimum_backing_votes(at, session_index).await?) + } + async fn staging_async_backing_params(&self, at: Hash) -> Result { Ok(self.rpc_client.parachain_host_staging_async_backing_params(at).await?) } diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index 8d070d628206d..c1e92b249d776 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -588,6 +588,16 @@ impl RelayChainRpcClient { .await } + /// Get the minimum number of backing votes for a candidate. + pub async fn parachain_host_minimum_backing_votes( + &self, + at: RelayHash, + _session_index: SessionIndex, + ) -> Result { + self.call_remote_runtime_function("ParachainHost_minimum_backing_votes", at, None::<()>) + .await + } + #[allow(missing_docs)] pub async fn parachain_host_staging_async_backing_params( &self, diff --git a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs index 7734529169084..6201b0ef71941 100644 --- a/cumulus/parachains/integration-tests/emulated/common/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/common/src/lib.rs @@ -35,6 +35,7 @@ pub use impls::{RococoWococoMessageHandler, WococoRococoMessageHandler}; pub use parachains_common::{AccountId, Balance}; pub use paste; use polkadot_parachain::primitives::HrmpChannelId; +use polkadot_primitives::runtime_api::runtime_decl_for_parachain_host::ParachainHostV6; pub use polkadot_runtime_parachains::inclusion::{AggregateMessageOrigin, UmpQueueId}; pub use sp_core::{sr25519, storage::Storage, Get}; use sp_tracing; diff --git a/polkadot/node/core/backing/src/error.rs b/polkadot/node/core/backing/src/error.rs index d8f9e82d8f482..1b00a62510b7c 100644 --- a/polkadot/node/core/backing/src/error.rs +++ b/polkadot/node/core/backing/src/error.rs @@ -79,6 +79,7 @@ pub enum Error { RuntimeApiUnavailable(#[source] oneshot::Canceled), #[error("a channel was closed before receipt in try_join!")] + #[fatal] JoinMultiple(#[source] oneshot::Canceled), #[error("Obtaining erasure chunks failed")] diff --git a/polkadot/node/core/backing/src/lib.rs b/polkadot/node/core/backing/src/lib.rs index 58763e6d80cc1..a75571da76d60 100644 --- a/polkadot/node/core/backing/src/lib.rs +++ b/polkadot/node/core/backing/src/lib.rs @@ -80,8 +80,8 @@ use futures::{ use error::{Error, FatalResult}; use polkadot_node_primitives::{ - minimum_votes, AvailableData, InvalidCandidate, PoV, SignedFullStatementWithPVD, - StatementWithPVD, ValidationResult, + AvailableData, InvalidCandidate, PoV, SignedFullStatementWithPVD, StatementWithPVD, + ValidationResult, }; use polkadot_node_subsystem::{ messages::{ @@ -98,7 +98,9 @@ use polkadot_node_subsystem_util::{ backing_implicit_view::{FetchError as ImplicitViewFetchError, View as ImplicitView}, request_from_runtime, request_session_index_for_child, request_validator_groups, request_validators, - runtime::{prospective_parachains_mode, ProspectiveParachainsMode}, + runtime::{ + self, prospective_parachains_mode, request_min_backing_votes, ProspectiveParachainsMode, + }, Validator, }; use polkadot_primitives::{ @@ -219,6 +221,8 @@ struct PerRelayParentState { awaiting_validation: HashSet, /// Data needed for retrying in case of `ValidatedCandidateCommand::AttestNoPoV`. fallbacks: HashMap, + /// The minimum backing votes threshold. + minimum_backing_votes: u32, } struct PerCandidateState { @@ -400,8 +404,8 @@ impl TableContextTrait for TableContext { self.groups.get(group).map_or(false, |g| g.iter().any(|a| a == authority)) } - fn requisite_votes(&self, group: &ParaId) -> usize { - self.groups.get(group).map_or(usize::MAX, |g| minimum_votes(g.len())) + fn get_group_size(&self, group: &ParaId) -> Option { + self.groups.get(group).map(|g| g.len()) } } @@ -965,28 +969,25 @@ async fn construct_per_relay_parent_state( ($x: expr) => { match $x { Ok(x) => x, - Err(e) => { - gum::warn!( - target: LOG_TARGET, - err = ?e, - "Failed to fetch runtime API data for job", - ); + Err(err) => { + // Only bubble up fatal errors. + error::log_error(Err(Into::::into(err).into()))?; // We can't do candidate validation work if we don't have the // requisite runtime API data. But these errors should not take // down the node. - return Ok(None); - } + return Ok(None) + }, } - } + }; } let parent = relay_parent; - let (validators, groups, session_index, cores) = futures::try_join!( + let (session_index, validators, groups, cores) = futures::try_join!( + request_session_index_for_child(parent, ctx.sender()).await, request_validators(parent, ctx.sender()).await, request_validator_groups(parent, ctx.sender()).await, - request_session_index_for_child(parent, ctx.sender()).await, request_from_runtime(parent, ctx.sender(), |tx| { RuntimeApiRequest::AvailabilityCores(tx) },) @@ -994,10 +995,12 @@ async fn construct_per_relay_parent_state( ) .map_err(Error::JoinMultiple)?; + let session_index = try_runtime_api!(session_index); let validators: Vec<_> = try_runtime_api!(validators); let (validator_groups, group_rotation_info) = try_runtime_api!(groups); - let session_index = try_runtime_api!(session_index); let cores = try_runtime_api!(cores); + let minimum_backing_votes = + try_runtime_api!(request_min_backing_votes(parent, session_index, ctx.sender()).await); let signing_context = SigningContext { parent_hash: parent, session_index }; let validator = @@ -1061,6 +1064,7 @@ async fn construct_per_relay_parent_state( issued_statements: HashSet::new(), awaiting_validation: HashSet::new(), fallbacks: HashMap::new(), + minimum_backing_votes, })) } @@ -1563,10 +1567,13 @@ async fn post_import_statement_actions( rp_state: &mut PerRelayParentState, summary: Option<&TableSummary>, ) -> Result<(), Error> { - if let Some(attested) = summary - .as_ref() - .and_then(|s| rp_state.table.attested_candidate(&s.candidate, &rp_state.table_context)) - { + if let Some(attested) = summary.as_ref().and_then(|s| { + rp_state.table.attested_candidate( + &s.candidate, + &rp_state.table_context, + rp_state.minimum_backing_votes, + ) + }) { let candidate_hash = attested.candidate.hash(); // `HashSet::insert` returns true if the thing wasn't in there already. @@ -2009,7 +2016,11 @@ fn handle_get_backed_candidates_message( }; rp_state .table - .attested_candidate(&candidate_hash, &rp_state.table_context) + .attested_candidate( + &candidate_hash, + &rp_state.table_context, + rp_state.minimum_backing_votes, + ) .and_then(|attested| table_attested_to_backed(attested, &rp_state.table_context)) }) .collect(); diff --git a/polkadot/node/core/backing/src/tests/mod.rs b/polkadot/node/core/backing/src/tests/mod.rs index 054337669c077..06baebc98e672 100644 --- a/polkadot/node/core/backing/src/tests/mod.rs +++ b/polkadot/node/core/backing/src/tests/mod.rs @@ -34,7 +34,7 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ CandidateDescriptor, GroupRotationInfo, HeadData, PersistedValidationData, PvfExecTimeoutKind, - ScheduledCore, SessionIndex, + ScheduledCore, SessionIndex, LEGACY_MIN_BACKING_VOTES, }; use sp_application_crypto::AppCrypto; use sp_keyring::Sr25519Keyring; @@ -80,6 +80,7 @@ struct TestState { head_data: HashMap, signing_context: SigningContext, relay_parent: Hash, + minimum_backing_votes: u32, } impl TestState { @@ -150,6 +151,7 @@ impl Default for TestState { validation_data, signing_context, relay_parent, + minimum_backing_votes: LEGACY_MIN_BACKING_VOTES, } } } @@ -250,6 +252,16 @@ async fn test_startup(virtual_overseer: &mut VirtualOverseer, test_state: &TestS } ); + // Check that subsystem job issues a request for the session index for child. + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) + ) if parent == test_state.relay_parent => { + tx.send(Ok(test_state.signing_context.session_index)).unwrap(); + } + ); + // Check that subsystem job issues a request for a validator set. assert_matches!( virtual_overseer.recv().await, @@ -270,23 +282,24 @@ async fn test_startup(virtual_overseer: &mut VirtualOverseer, test_state: &TestS } ); - // Check that subsystem job issues a request for the session index for child. + // Check that subsystem job issues a request for the availability cores. assert_matches!( virtual_overseer.recv().await, AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) + RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx)) ) if parent == test_state.relay_parent => { - tx.send(Ok(test_state.signing_context.session_index)).unwrap(); + tx.send(Ok(test_state.availability_cores.clone())).unwrap(); } ); - // Check that subsystem job issues a request for the availability cores. + // Check if subsystem job issues a request for the minimum backing votes. assert_matches!( virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx)) - ) if parent == test_state.relay_parent => { - tx.send(Ok(test_state.availability_cores.clone())).unwrap(); + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::MinimumBackingVotes(session_index, tx), + )) if parent == test_state.relay_parent && session_index == test_state.signing_context.session_index => { + tx.send(Ok(test_state.minimum_backing_votes)).unwrap(); } ); } diff --git a/polkadot/node/core/backing/src/tests/prospective_parachains.rs b/polkadot/node/core/backing/src/tests/prospective_parachains.rs index 6c15539056636..7a30886736d70 100644 --- a/polkadot/node/core/backing/src/tests/prospective_parachains.rs +++ b/polkadot/node/core/backing/src/tests/prospective_parachains.rs @@ -138,13 +138,24 @@ async fn activate_leaf( } for (hash, number) in ancestry_iter.take(requested_len) { - // Check that subsystem job issues a request for a validator set. let msg = match next_overseer_message.take() { Some(msg) => msg, None => virtual_overseer.recv().await, }; + + // Check that subsystem job issues a request for the session index for child. assert_matches!( msg, + AllMessages::RuntimeApi( + RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) + ) if parent == hash => { + tx.send(Ok(test_state.signing_context.session_index)).unwrap(); + } + ); + + // Check that subsystem job issues a request for a validator set. + assert_matches!( + virtual_overseer.recv().await, AllMessages::RuntimeApi( RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx)) ) if parent == hash => { @@ -164,23 +175,24 @@ async fn activate_leaf( } ); - // Check that subsystem job issues a request for the session index for child. + // Check that subsystem job issues a request for the availability cores. assert_matches!( virtual_overseer.recv().await, AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) + RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx)) ) if parent == hash => { - tx.send(Ok(test_state.signing_context.session_index)).unwrap(); + tx.send(Ok(test_state.availability_cores.clone())).unwrap(); } ); - // Check that subsystem job issues a request for the availability cores. + // Check if subsystem job issues a request for the minimum backing votes. assert_matches!( virtual_overseer.recv().await, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::AvailabilityCores(tx)) - ) if parent == hash => { - tx.send(Ok(test_state.availability_cores.clone())).unwrap(); + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::MinimumBackingVotes(session_index, tx), + )) if parent == hash && session_index == test_state.signing_context.session_index => { + tx.send(Ok(test_state.minimum_backing_votes)).unwrap(); } ); } diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs index cd22f37ddee43..7f41d74e616c6 100644 --- a/polkadot/node/core/runtime-api/src/cache.rs +++ b/polkadot/node/core/runtime-api/src/cache.rs @@ -65,6 +65,7 @@ pub(crate) struct RequestResultCache { LruMap>, key_ownership_proof: LruMap<(Hash, ValidatorId), Option>, + minimum_backing_votes: LruMap, staging_para_backing_state: LruMap<(Hash, ParaId), Option>, staging_async_backing_params: LruMap, @@ -97,6 +98,7 @@ impl Default for RequestResultCache { disputes: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), unapplied_slashes: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), key_ownership_proof: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), + minimum_backing_votes: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), staging_para_backing_state: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), staging_async_backing_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), @@ -434,6 +436,18 @@ impl RequestResultCache { None } + pub(crate) fn minimum_backing_votes(&mut self, session_index: SessionIndex) -> Option { + self.minimum_backing_votes.get(&session_index).copied() + } + + pub(crate) fn cache_minimum_backing_votes( + &mut self, + session_index: SessionIndex, + minimum_backing_votes: u32, + ) { + self.minimum_backing_votes.insert(session_index, minimum_backing_votes); + } + pub(crate) fn staging_para_backing_state( &mut self, key: (Hash, ParaId), @@ -469,6 +483,7 @@ pub(crate) enum RequestResult { // The structure of each variant is (relay_parent, [params,]*, result) Authorities(Hash, Vec), Validators(Hash, Vec), + MinimumBackingVotes(Hash, SessionIndex, u32), ValidatorGroups(Hash, (Vec>, GroupRotationInfo)), AvailabilityCores(Hash, Vec), PersistedValidationData(Hash, ParaId, OccupiedCoreAssumption, Option), diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index 78531d41272b5..ec9bf10fa6e3a 100644 --- a/polkadot/node/core/runtime-api/src/lib.rs +++ b/polkadot/node/core/runtime-api/src/lib.rs @@ -101,6 +101,9 @@ where self.requests_cache.cache_authorities(relay_parent, authorities), Validators(relay_parent, validators) => self.requests_cache.cache_validators(relay_parent, validators), + MinimumBackingVotes(_, session_index, minimum_backing_votes) => self + .requests_cache + .cache_minimum_backing_votes(session_index, minimum_backing_votes), ValidatorGroups(relay_parent, groups) => self.requests_cache.cache_validator_groups(relay_parent, groups), AvailabilityCores(relay_parent, cores) => @@ -301,6 +304,15 @@ where Request::StagingAsyncBackingParams(sender) => query!(staging_async_backing_params(), sender) .map(|sender| Request::StagingAsyncBackingParams(sender)), + Request::MinimumBackingVotes(index, sender) => { + if let Some(value) = self.requests_cache.minimum_backing_votes(index) { + self.metrics.on_cached_request(); + let _ = sender.send(Ok(value)); + None + } else { + Some(Request::MinimumBackingVotes(index, sender)) + } + }, } } @@ -551,6 +563,12 @@ where ver = Request::SUBMIT_REPORT_DISPUTE_LOST_RUNTIME_REQUIREMENT, sender ), + Request::MinimumBackingVotes(index, sender) => query!( + MinimumBackingVotes, + minimum_backing_votes(index), + ver = Request::MINIMUM_BACKING_VOTES_RUNTIME_REQUIREMENT, + sender + ), Request::StagingParaBackingState(para, sender) => { query!( diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs index c3f8108312be1..bb7c296896117 100644 --- a/polkadot/node/core/runtime-api/src/tests.rs +++ b/polkadot/node/core/runtime-api/src/tests.rs @@ -264,6 +264,10 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient { ) -> Result, ApiError> { todo!("Not required for tests") } + + async fn minimum_backing_votes(&self, _: Hash, _: SessionIndex) -> Result { + todo!("Not required for tests") + } } #[test] diff --git a/polkadot/node/network/statement-distribution/Cargo.toml b/polkadot/node/network/statement-distribution/Cargo.toml index 4c835bc3a688c..5ff1ba9de02ba 100644 --- a/polkadot/node/network/statement-distribution/Cargo.toml +++ b/polkadot/node/network/statement-distribution/Cargo.toml @@ -16,6 +16,7 @@ sp-keystore = { path = "../../../../substrate/primitives/keystore" } polkadot-node-subsystem = { path = "../../subsystem" } polkadot-node-primitives = { path = "../../primitives" } polkadot-node-subsystem-util = { path = "../../subsystem-util" } +polkadot-node-subsystem-types = { path = "../../subsystem-types" } polkadot-node-network-protocol = { path = "../protocol" } arrayvec = "0.7.4" indexmap = "1.9.1" @@ -39,4 +40,3 @@ sc-network = { path = "../../../../substrate/client/network" } futures-timer = "3.0.2" polkadot-primitives-test-helpers = { path = "../../../primitives/test-helpers" } rand_chacha = "0.3" -polkadot-node-subsystem-types = { path = "../../subsystem-types" } diff --git a/polkadot/node/network/statement-distribution/src/vstaging/grid.rs b/polkadot/node/network/statement-distribution/src/vstaging/grid.rs index ec470a2eeb444..4fd77d0ced1ce 100644 --- a/polkadot/node/network/statement-distribution/src/vstaging/grid.rs +++ b/polkadot/node/network/statement-distribution/src/vstaging/grid.rs @@ -1074,7 +1074,7 @@ mod tests { fn dummy_groups(group_size: usize) -> Groups { let groups = vec![(0..(group_size as u32)).map(ValidatorIndex).collect()].into(); - Groups::new(groups) + Groups::new(groups, 2) } #[test] diff --git a/polkadot/node/network/statement-distribution/src/vstaging/groups.rs b/polkadot/node/network/statement-distribution/src/vstaging/groups.rs index f93a8e13fc10e..b2daa1c0ac7ca 100644 --- a/polkadot/node/network/statement-distribution/src/vstaging/groups.rs +++ b/polkadot/node/network/statement-distribution/src/vstaging/groups.rs @@ -16,8 +16,10 @@ //! A utility for tracking groups and their members within a session. -use polkadot_node_primitives::minimum_votes; -use polkadot_primitives::vstaging::{GroupIndex, IndexedVec, ValidatorIndex}; +use polkadot_primitives::{ + effective_minimum_backing_votes, + vstaging::{GroupIndex, IndexedVec, ValidatorIndex}, +}; use std::collections::HashMap; @@ -27,12 +29,16 @@ use std::collections::HashMap; pub struct Groups { groups: IndexedVec>, by_validator_index: HashMap, + backing_threshold: u32, } impl Groups { /// Create a new [`Groups`] tracker with the groups and discovery keys /// from the session. - pub fn new(groups: IndexedVec>) -> Self { + pub fn new( + groups: IndexedVec>, + backing_threshold: u32, + ) -> Self { let mut by_validator_index = HashMap::new(); for (i, group) in groups.iter().enumerate() { @@ -42,7 +48,7 @@ impl Groups { } } - Groups { groups, by_validator_index } + Groups { groups, by_validator_index, backing_threshold } } /// Access all the underlying groups. @@ -60,7 +66,8 @@ impl Groups { &self, group_index: GroupIndex, ) -> Option<(usize, usize)> { - self.get(group_index).map(|g| (g.len(), minimum_votes(g.len()))) + self.get(group_index) + .map(|g| (g.len(), effective_minimum_backing_votes(g.len(), self.backing_threshold))) } /// Get the group index for a validator by index. diff --git a/polkadot/node/network/statement-distribution/src/vstaging/mod.rs b/polkadot/node/network/statement-distribution/src/vstaging/mod.rs index 830a488308a93..4639720b3221a 100644 --- a/polkadot/node/network/statement-distribution/src/vstaging/mod.rs +++ b/polkadot/node/network/statement-distribution/src/vstaging/mod.rs @@ -41,8 +41,9 @@ use polkadot_node_subsystem::{ overseer, ActivatedLeaf, }; use polkadot_node_subsystem_util::{ - backing_implicit_view::View as ImplicitView, reputation::ReputationAggregator, - runtime::ProspectiveParachainsMode, + backing_implicit_view::View as ImplicitView, + reputation::ReputationAggregator, + runtime::{request_min_backing_votes, ProspectiveParachainsMode}, }; use polkadot_primitives::vstaging::{ AuthorityDiscoveryId, CandidateHash, CompactStatement, CoreIndex, CoreState, GroupIndex, @@ -163,8 +164,8 @@ struct PerSessionState { } impl PerSessionState { - fn new(session_info: SessionInfo, keystore: &KeystorePtr) -> Self { - let groups = Groups::new(session_info.validator_groups.clone()); + fn new(session_info: SessionInfo, keystore: &KeystorePtr, backing_threshold: u32) -> Self { + let groups = Groups::new(session_info.validator_groups.clone(), backing_threshold); let mut authority_lookup = HashMap::new(); for (i, ad) in session_info.discovery_keys.iter().cloned().enumerate() { authority_lookup.insert(ad, ValidatorIndex(i as _)); @@ -504,9 +505,13 @@ pub(crate) async fn handle_active_leaves_update( Some(s) => s, }; - state - .per_session - .insert(session_index, PerSessionState::new(session_info, &state.keystore)); + let minimum_backing_votes = + request_min_backing_votes(new_relay_parent, session_index, ctx.sender()).await?; + + state.per_session.insert( + session_index, + PerSessionState::new(session_info, &state.keystore, minimum_backing_votes), + ); } let per_session = state @@ -2502,7 +2507,7 @@ pub(crate) async fn dispatch_requests(ctx: &mut Context, state: &mut St Some(RequestProperties { unwanted_mask, backing_threshold: if require_backing { - Some(polkadot_node_primitives::minimum_votes(group.len())) + Some(per_session.groups.get_size_and_backing_threshold(group_index)?.1) } else { None }, diff --git a/polkadot/node/network/statement-distribution/src/vstaging/tests/mod.rs b/polkadot/node/network/statement-distribution/src/vstaging/tests/mod.rs index 88c8a42599d4e..c5a4d14d2c7a5 100644 --- a/polkadot/node/network/statement-distribution/src/vstaging/tests/mod.rs +++ b/polkadot/node/network/statement-distribution/src/vstaging/tests/mod.rs @@ -356,7 +356,7 @@ async fn activate_leaf( virtual_overseer: &mut VirtualOverseer, leaf: &TestLeaf, test_state: &TestState, - expect_session_info_request: bool, + is_new_session: bool, ) { let activated = ActivatedLeaf { hash: leaf.hash, @@ -371,14 +371,14 @@ async fn activate_leaf( )))) .await; - handle_leaf_activation(virtual_overseer, leaf, test_state, expect_session_info_request).await; + handle_leaf_activation(virtual_overseer, leaf, test_state, is_new_session).await; } async fn handle_leaf_activation( virtual_overseer: &mut VirtualOverseer, leaf: &TestLeaf, test_state: &TestState, - expect_session_info_request: bool, + is_new_session: bool, ) { let TestLeaf { number, hash, parent_hash, para_data, session, availability_cores } = leaf; @@ -447,7 +447,7 @@ async fn handle_leaf_activation( } ); - if expect_session_info_request { + if is_new_session { assert_matches!( virtual_overseer.recv().await, AllMessages::RuntimeApi( @@ -455,6 +455,16 @@ async fn handle_leaf_activation( tx.send(Ok(Some(test_state.session_info.clone()))).unwrap(); } ); + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::MinimumBackingVotes(session_index, tx), + )) if parent == *hash && session_index == *session => { + tx.send(Ok(2)).unwrap(); + } + ); } } diff --git a/polkadot/node/primitives/src/lib.rs b/polkadot/node/primitives/src/lib.rs index baab07a9ba284..b1e5639479609 100644 --- a/polkadot/node/primitives/src/lib.rs +++ b/polkadot/node/primitives/src/lib.rs @@ -649,10 +649,3 @@ pub fn maybe_compress_pov(pov: PoV) -> PoV { let pov = PoV { block_data: BlockData(raw) }; pov } - -/// How many votes we need to consider a candidate backed. -/// -/// WARNING: This has to be kept in sync with the runtime check in the inclusion module. -pub fn minimum_votes(n_validators: usize) -> usize { - std::cmp::min(2, n_validators) -} diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index 8adc39eed56db..6b078e08377eb 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -691,6 +691,8 @@ pub enum RuntimeApiRequest { slashing::OpaqueKeyOwnershipProof, RuntimeApiSender>, ), + /// Get the minimum required backing votes. + MinimumBackingVotes(SessionIndex, RuntimeApiSender), /// Get the backing state of the given para. /// This is a staging API that will not be available on production runtimes. @@ -719,6 +721,9 @@ impl RuntimeApiRequest { /// `SubmitReportDisputeLost` pub const SUBMIT_REPORT_DISPUTE_LOST_RUNTIME_REQUIREMENT: u32 = 5; + /// `MinimumBackingVotes` + pub const MINIMUM_BACKING_VOTES_RUNTIME_REQUIREMENT: u32 = 6; + /// Minimum version for backing state, required for async backing. /// /// 99 for now, should be adjusted to VSTAGING/actual runtime version once released. diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs index 312cc4eec6ce9..06aa351efb4bc 100644 --- a/polkadot/node/subsystem-types/src/runtime_client.rs +++ b/polkadot/node/subsystem-types/src/runtime_client.rs @@ -232,6 +232,14 @@ pub trait RuntimeApiSubsystemClient { session_index: SessionIndex, ) -> Result, ApiError>; + // === STAGING v6 === + /// Get the minimum number of backing votes. + async fn minimum_backing_votes( + &self, + at: Hash, + session_index: SessionIndex, + ) -> Result; + // === Asynchronous backing API === /// Returns candidate's acceptance limitations for asynchronous backing for a relay parent. @@ -473,6 +481,14 @@ where runtime_api.submit_report_dispute_lost(at, dispute_proof, key_ownership_proof) } + async fn minimum_backing_votes( + &self, + at: Hash, + _session_index: SessionIndex, + ) -> Result { + self.client.runtime_api().minimum_backing_votes(at) + } + async fn staging_para_backing_state( &self, at: Hash, diff --git a/polkadot/node/subsystem-util/src/runtime/error.rs b/polkadot/node/subsystem-util/src/runtime/error.rs index db3eacd685141..9c3d57b8ea6d3 100644 --- a/polkadot/node/subsystem-util/src/runtime/error.rs +++ b/polkadot/node/subsystem-util/src/runtime/error.rs @@ -33,7 +33,7 @@ pub enum Error { /// Some request to the runtime failed. /// For example if we prune a block we're requesting info about. #[error("Runtime API error {0}")] - RuntimeRequest(RuntimeApiError), + RuntimeRequest(#[from] RuntimeApiError), /// We tried fetching a session info which was not available. #[error("There was no session with the given index {0}")] @@ -43,7 +43,7 @@ pub enum Error { pub type Result = std::result::Result; /// Receive a response from a runtime request and convert errors. -pub(crate) async fn recv_runtime( +pub async fn recv_runtime( r: oneshot::Receiver>, ) -> Result { let result = r diff --git a/polkadot/node/subsystem-util/src/runtime/mod.rs b/polkadot/node/subsystem-util/src/runtime/mod.rs index a044a64e93a0d..fcd778b078408 100644 --- a/polkadot/node/subsystem-util/src/runtime/mod.rs +++ b/polkadot/node/subsystem-util/src/runtime/mod.rs @@ -24,27 +24,29 @@ use sp_core::crypto::ByteArray; use sp_keystore::{Keystore, KeystorePtr}; use polkadot_node_subsystem::{ - errors::RuntimeApiError, messages::RuntimeApiMessage, overseer, SubsystemSender, + errors::RuntimeApiError, + messages::{RuntimeApiMessage, RuntimeApiRequest}, + overseer, SubsystemSender, }; use polkadot_primitives::{ vstaging, CandidateEvent, CandidateHash, CoreState, EncodeAs, GroupIndex, GroupRotationInfo, Hash, IndexedVec, OccupiedCore, ScrapedOnChainVotes, SessionIndex, SessionInfo, Signed, SigningContext, UncheckedSigned, ValidationCode, ValidationCodeHash, ValidatorId, - ValidatorIndex, + ValidatorIndex, LEGACY_MIN_BACKING_VOTES, }; use crate::{ - request_availability_cores, request_candidate_events, request_key_ownership_proof, - request_on_chain_votes, request_session_index_for_child, request_session_info, - request_staging_async_backing_params, request_submit_report_dispute_lost, + request_availability_cores, request_candidate_events, request_from_runtime, + request_key_ownership_proof, request_on_chain_votes, request_session_index_for_child, + request_session_info, request_staging_async_backing_params, request_submit_report_dispute_lost, request_unapplied_slashes, request_validation_code_by_hash, request_validator_groups, }; /// Errors that can happen on runtime fetches. mod error; -use error::{recv_runtime, Result}; -pub use error::{Error, FatalError, JfyiError}; +use error::Result; +pub use error::{recv_runtime, Error, FatalError, JfyiError}; const LOG_TARGET: &'static str = "parachain::runtime-info"; @@ -451,3 +453,32 @@ where }) } } + +/// Request the min backing votes value. +/// Prior to runtime API version 6, just return a hardcoded constant. +pub async fn request_min_backing_votes( + parent: Hash, + session_index: SessionIndex, + sender: &mut impl overseer::SubsystemSender, +) -> Result { + let min_backing_votes_res = recv_runtime( + request_from_runtime(parent, sender, |tx| { + RuntimeApiRequest::MinimumBackingVotes(session_index, tx) + }) + .await, + ) + .await; + + if let Err(Error::RuntimeRequest(RuntimeApiError::NotSupported { .. })) = min_backing_votes_res + { + gum::trace!( + target: LOG_TARGET, + ?parent, + "Querying the backing threshold from the runtime is not supported by the current Runtime API", + ); + + Ok(LEGACY_MIN_BACKING_VOTES) + } else { + min_backing_votes_res + } +} diff --git a/polkadot/primitives/src/lib.rs b/polkadot/primitives/src/lib.rs index 3680cb857e66e..729908cc12ba0 100644 --- a/polkadot/primitives/src/lib.rs +++ b/polkadot/primitives/src/lib.rs @@ -34,19 +34,19 @@ pub mod runtime_api; // Current primitives not requiring versioning are exported here. // Primitives requiring versioning must not be exported and must be referred by an exact version. pub use v5::{ - byzantine_threshold, check_candidate_backing, collator_signature_payload, metric_definitions, - slashing, supermajority_threshold, well_known_keys, AbridgedHostConfiguration, - AbridgedHrmpChannel, AccountId, AccountIndex, AccountPublic, ApprovalVote, AssignmentId, - AuthorityDiscoveryId, AvailabilityBitfield, BackedCandidate, Balance, BlakeTwo256, Block, - BlockId, BlockNumber, CandidateCommitments, CandidateDescriptor, CandidateEvent, CandidateHash, - CandidateIndex, CandidateReceipt, CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, - CollatorId, CollatorSignature, CommittedCandidateReceipt, CompactStatement, ConsensusLog, - CoreIndex, CoreOccupied, CoreState, DisputeState, DisputeStatement, DisputeStatementSet, - DownwardMessage, EncodeAs, ExecutorParam, ExecutorParams, ExecutorParamsHash, - ExplicitDisputeStatement, GroupIndex, GroupRotationInfo, Hash, HashT, HeadData, Header, - HrmpChannelId, Id, InboundDownwardMessage, InboundHrmpMessage, IndexedVec, InherentData, - InvalidDisputeStatementKind, Moment, MultiDisputeStatementSet, Nonce, OccupiedCore, - OccupiedCoreAssumption, OutboundHrmpMessage, ParathreadClaim, ParathreadEntry, + byzantine_threshold, check_candidate_backing, collator_signature_payload, + effective_minimum_backing_votes, metric_definitions, slashing, supermajority_threshold, + well_known_keys, AbridgedHostConfiguration, AbridgedHrmpChannel, AccountId, AccountIndex, + AccountPublic, ApprovalVote, AssignmentId, AuthorityDiscoveryId, AvailabilityBitfield, + BackedCandidate, Balance, BlakeTwo256, Block, BlockId, BlockNumber, CandidateCommitments, + CandidateDescriptor, CandidateEvent, CandidateHash, CandidateIndex, CandidateReceipt, + CheckedDisputeStatementSet, CheckedMultiDisputeStatementSet, CollatorId, CollatorSignature, + CommittedCandidateReceipt, CompactStatement, ConsensusLog, CoreIndex, CoreOccupied, CoreState, + DisputeState, DisputeStatement, DisputeStatementSet, DownwardMessage, EncodeAs, ExecutorParam, + ExecutorParams, ExecutorParamsHash, ExplicitDisputeStatement, GroupIndex, GroupRotationInfo, + Hash, HashT, HeadData, Header, HrmpChannelId, Id, InboundDownwardMessage, InboundHrmpMessage, + IndexedVec, InherentData, InvalidDisputeStatementKind, Moment, MultiDisputeStatementSet, Nonce, + OccupiedCore, OccupiedCoreAssumption, OutboundHrmpMessage, ParathreadClaim, ParathreadEntry, PersistedValidationData, PvfCheckStatement, PvfExecTimeoutKind, PvfPrepTimeoutKind, RuntimeMetricLabel, RuntimeMetricLabelValue, RuntimeMetricLabelValues, RuntimeMetricLabels, RuntimeMetricOp, RuntimeMetricUpdate, ScheduledCore, ScrapedOnChainVotes, SessionIndex, @@ -55,9 +55,9 @@ pub use v5::{ UncheckedSignedAvailabilityBitfields, UncheckedSignedStatement, UpgradeGoAhead, UpgradeRestriction, UpwardMessage, ValidDisputeStatementKind, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, ValidatorSignature, ValidityAttestation, - ValidityError, ASSIGNMENT_KEY_TYPE_ID, LOWEST_PUBLIC_ID, MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, - MAX_POV_SIZE, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, PARACHAINS_INHERENT_IDENTIFIER, - PARACHAIN_KEY_TYPE_ID, + ValidityError, ASSIGNMENT_KEY_TYPE_ID, LEGACY_MIN_BACKING_VOTES, LOWEST_PUBLIC_ID, + MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, + PARACHAINS_INHERENT_IDENTIFIER, PARACHAIN_KEY_TYPE_ID, }; #[cfg(feature = "std")] diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs index 483256fe20f36..86aca6c9bc6f5 100644 --- a/polkadot/primitives/src/runtime_api.rs +++ b/polkadot/primitives/src/runtime_api.rs @@ -240,6 +240,13 @@ sp_api::decl_runtime_apis! { key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof, ) -> Option<()>; + /***** Staging *****/ + + /// Get the minimum number of backing votes for a parachain candidate. + /// This is a staging method! Do not use on production runtimes! + #[api_version(6)] + fn minimum_backing_votes() -> u32; + /***** Asynchronous backing *****/ /// Returns the state of parachain backing for a given para. diff --git a/polkadot/primitives/src/v5/mod.rs b/polkadot/primitives/src/v5/mod.rs index 825d733c5f549..a41bfcdef8414 100644 --- a/polkadot/primitives/src/v5/mod.rs +++ b/polkadot/primitives/src/v5/mod.rs @@ -390,6 +390,10 @@ pub const MAX_POV_SIZE: u32 = 5 * 1024 * 1024; /// Can be adjusted in configuration. pub const ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE: u32 = 10_000; +/// Backing votes threshold used from the host prior to runtime API version 6 and from the runtime +/// prior to v9 configuration migration. +pub const LEGACY_MIN_BACKING_VOTES: u32 = 2; + // The public key of a keypair used by a validator for determining assignments /// to approve included parachain candidates. mod assignment_app { @@ -1695,6 +1699,14 @@ pub const fn supermajority_threshold(n: usize) -> usize { n - byzantine_threshold(n) } +/// Adjust the configured needed backing votes with the size of the backing group. +pub fn effective_minimum_backing_votes( + group_len: usize, + configured_minimum_backing_votes: u32, +) -> usize { + sp_std::cmp::min(group_len, configured_minimum_backing_votes as usize) +} + /// Information about validator sets of a session. /// /// NOTE: `SessionInfo` is frozen. Do not include new fields, consider creating a separate runtime diff --git a/polkadot/runtime/kusama/src/lib.rs b/polkadot/runtime/kusama/src/lib.rs index ac80f88cfaa2a..4b5f03b38c6c6 100644 --- a/polkadot/runtime/kusama/src/lib.rs +++ b/polkadot/runtime/kusama/src/lib.rs @@ -1738,6 +1738,8 @@ pub mod migrations { // Upgrade SessionKeys to include BEEFY key UpgradeSessionKeys, + + parachains_configuration::migration::v9::MigrateToV9, ); } diff --git a/polkadot/runtime/parachains/src/configuration.rs b/polkadot/runtime/parachains/src/configuration.rs index fc24ac18ed5a5..77c15c5672f16 100644 --- a/polkadot/runtime/parachains/src/configuration.rs +++ b/polkadot/runtime/parachains/src/configuration.rs @@ -24,8 +24,8 @@ use frame_system::pallet_prelude::*; use parity_scale_codec::{Decode, Encode}; use polkadot_parachain::primitives::{MAX_HORIZONTAL_MESSAGE_NUM, MAX_UPWARD_MESSAGE_NUM}; use primitives::{ - vstaging::AsyncBackingParams, Balance, ExecutorParams, SessionIndex, MAX_CODE_SIZE, - MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, + vstaging::AsyncBackingParams, Balance, ExecutorParams, SessionIndex, LEGACY_MIN_BACKING_VOTES, + MAX_CODE_SIZE, MAX_HEAD_DATA_SIZE, MAX_POV_SIZE, ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, }; use sp_runtime::{traits::Zero, Perbill}; use sp_std::prelude::*; @@ -245,6 +245,9 @@ pub struct HostConfiguration { /// This value should be greater than /// [`paras_availability_period`](Self::paras_availability_period). pub minimum_validation_upgrade_delay: BlockNumber, + /// The minimum number of valid backing statements required to consider a parachain candidate + /// backable. + pub minimum_backing_votes: u32, } impl> Default for HostConfiguration { @@ -295,6 +298,7 @@ impl> Default for HostConfiguration { MaxHrmpOutboundChannelsExceeded, /// Maximum number of HRMP inbound channels exceeded. MaxHrmpInboundChannelsExceeded, + /// `minimum_backing_votes` is set to zero. + ZeroMinimumBackingVotes, } impl HostConfiguration @@ -411,6 +417,10 @@ where return Err(MaxHrmpInboundChannelsExceeded) } + if self.minimum_backing_votes.is_zero() { + return Err(ZeroMinimumBackingVotes) + } + Ok(()) } @@ -477,7 +487,8 @@ pub mod pallet { /// v5-v6: (remove UMP dispatch queue) /// v6-v7: /// v7-v8: - const STORAGE_VERSION: StorageVersion = StorageVersion::new(8); + /// v8-v9: + const STORAGE_VERSION: StorageVersion = StorageVersion::new(9); #[pallet::pallet] #[pallet::storage_version(STORAGE_VERSION)] @@ -1153,6 +1164,18 @@ pub mod pallet { config.on_demand_ttl = new; }) } + /// Set the minimum backing votes threshold. + #[pallet::call_index(52)] + #[pallet::weight(( + T::WeightInfo::set_config_with_u32(), + DispatchClass::Operational + ))] + pub fn set_minimum_backing_votes(origin: OriginFor, new: u32) -> DispatchResult { + ensure_root(origin)?; + Self::schedule_config_update(|config| { + config.minimum_backing_votes = new; + }) + } } #[pallet::hooks] diff --git a/polkadot/runtime/parachains/src/configuration/migration.rs b/polkadot/runtime/parachains/src/configuration/migration.rs index 4499b116462b8..26f8a85b496d5 100644 --- a/polkadot/runtime/parachains/src/configuration/migration.rs +++ b/polkadot/runtime/parachains/src/configuration/migration.rs @@ -19,3 +19,4 @@ pub mod v6; pub mod v7; pub mod v8; +pub mod v9; diff --git a/polkadot/runtime/parachains/src/configuration/migration/v8.rs b/polkadot/runtime/parachains/src/configuration/migration/v8.rs index 7f7cc1cdefcd9..5c5b34821835e 100644 --- a/polkadot/runtime/parachains/src/configuration/migration/v8.rs +++ b/polkadot/runtime/parachains/src/configuration/migration/v8.rs @@ -23,14 +23,114 @@ use frame_support::{ weights::Weight, }; use frame_system::pallet_prelude::BlockNumberFor; -use primitives::SessionIndex; +use primitives::{ + vstaging::AsyncBackingParams, Balance, ExecutorParams, SessionIndex, + ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, +}; use sp_runtime::Perbill; use sp_std::vec::Vec; use frame_support::traits::OnRuntimeUpgrade; use super::v7::V7HostConfiguration; -type V8HostConfiguration = configuration::HostConfiguration; +/// All configuration of the runtime with respect to paras. +#[derive(Clone, Encode, Decode, Debug)] +pub struct V8HostConfiguration { + pub max_code_size: u32, + pub max_head_data_size: u32, + pub max_upward_queue_count: u32, + pub max_upward_queue_size: u32, + pub max_upward_message_size: u32, + pub max_upward_message_num_per_candidate: u32, + pub hrmp_max_message_num_per_candidate: u32, + pub validation_upgrade_cooldown: BlockNumber, + pub validation_upgrade_delay: BlockNumber, + pub async_backing_params: AsyncBackingParams, + pub max_pov_size: u32, + pub max_downward_message_size: u32, + pub hrmp_max_parachain_outbound_channels: u32, + pub hrmp_sender_deposit: Balance, + pub hrmp_recipient_deposit: Balance, + pub hrmp_channel_max_capacity: u32, + pub hrmp_channel_max_total_size: u32, + pub hrmp_max_parachain_inbound_channels: u32, + pub hrmp_channel_max_message_size: u32, + pub executor_params: ExecutorParams, + pub code_retention_period: BlockNumber, + pub on_demand_cores: u32, + pub on_demand_retries: u32, + pub on_demand_queue_max_size: u32, + pub on_demand_target_queue_utilization: Perbill, + pub on_demand_fee_variability: Perbill, + pub on_demand_base_fee: Balance, + pub on_demand_ttl: BlockNumber, + pub group_rotation_frequency: BlockNumber, + pub paras_availability_period: BlockNumber, + pub scheduling_lookahead: u32, + pub max_validators_per_core: Option, + pub max_validators: Option, + pub dispute_period: SessionIndex, + pub dispute_post_conclusion_acceptance_period: BlockNumber, + pub no_show_slots: u32, + pub n_delay_tranches: u32, + pub zeroth_delay_tranche_width: u32, + pub needed_approvals: u32, + pub relay_vrf_modulo_samples: u32, + pub pvf_voting_ttl: SessionIndex, + pub minimum_validation_upgrade_delay: BlockNumber, +} + +impl> Default for V8HostConfiguration { + fn default() -> Self { + Self { + async_backing_params: AsyncBackingParams { + max_candidate_depth: 0, + allowed_ancestry_len: 0, + }, + group_rotation_frequency: 1u32.into(), + paras_availability_period: 1u32.into(), + no_show_slots: 1u32.into(), + validation_upgrade_cooldown: Default::default(), + validation_upgrade_delay: 2u32.into(), + code_retention_period: Default::default(), + max_code_size: Default::default(), + max_pov_size: Default::default(), + max_head_data_size: Default::default(), + on_demand_cores: Default::default(), + on_demand_retries: Default::default(), + scheduling_lookahead: 1, + max_validators_per_core: Default::default(), + max_validators: None, + dispute_period: 6, + dispute_post_conclusion_acceptance_period: 100.into(), + n_delay_tranches: Default::default(), + zeroth_delay_tranche_width: Default::default(), + needed_approvals: Default::default(), + relay_vrf_modulo_samples: Default::default(), + max_upward_queue_count: Default::default(), + max_upward_queue_size: Default::default(), + max_downward_message_size: Default::default(), + max_upward_message_size: Default::default(), + max_upward_message_num_per_candidate: Default::default(), + hrmp_sender_deposit: Default::default(), + hrmp_recipient_deposit: Default::default(), + hrmp_channel_max_capacity: Default::default(), + hrmp_channel_max_total_size: Default::default(), + hrmp_max_parachain_inbound_channels: Default::default(), + hrmp_channel_max_message_size: Default::default(), + hrmp_max_parachain_outbound_channels: Default::default(), + hrmp_max_message_num_per_candidate: Default::default(), + pvf_voting_ttl: 2u32.into(), + minimum_validation_upgrade_delay: 2.into(), + executor_params: Default::default(), + on_demand_queue_max_size: ON_DEMAND_DEFAULT_QUEUE_MAX_SIZE, + on_demand_base_fee: 10_000_000u128, + on_demand_fee_variability: Perbill::from_percent(3), + on_demand_target_queue_utilization: Perbill::from_percent(25), + on_demand_ttl: 5u32.into(), + } + } +} mod v7 { use super::*; diff --git a/polkadot/runtime/parachains/src/configuration/migration/v9.rs b/polkadot/runtime/parachains/src/configuration/migration/v9.rs new file mode 100644 index 0000000000000..64d71e628f4ea --- /dev/null +++ b/polkadot/runtime/parachains/src/configuration/migration/v9.rs @@ -0,0 +1,321 @@ +// Copyright (C) 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 . + +//! A module that is responsible for migration of storage. + +use crate::configuration::{self, Config, Pallet}; +use frame_support::{ + pallet_prelude::*, + traits::{Defensive, StorageVersion}, + weights::Weight, +}; +use frame_system::pallet_prelude::BlockNumberFor; +use primitives::{SessionIndex, LEGACY_MIN_BACKING_VOTES}; +use sp_runtime::Perbill; +use sp_std::vec::Vec; + +use frame_support::traits::OnRuntimeUpgrade; + +use super::v8::V8HostConfiguration; +type V9HostConfiguration = configuration::HostConfiguration; + +mod v8 { + use super::*; + + #[frame_support::storage_alias] + pub(crate) type ActiveConfig = + StorageValue, V8HostConfiguration>, OptionQuery>; + + #[frame_support::storage_alias] + pub(crate) type PendingConfigs = StorageValue< + Pallet, + Vec<(SessionIndex, V8HostConfiguration>)>, + OptionQuery, + >; +} + +mod v9 { + use super::*; + + #[frame_support::storage_alias] + pub(crate) type ActiveConfig = + StorageValue, V9HostConfiguration>, OptionQuery>; + + #[frame_support::storage_alias] + pub(crate) type PendingConfigs = StorageValue< + Pallet, + Vec<(SessionIndex, V9HostConfiguration>)>, + OptionQuery, + >; +} + +pub struct MigrateToV9(sp_std::marker::PhantomData); +impl OnRuntimeUpgrade for MigrateToV9 { + #[cfg(feature = "try-runtime")] + fn pre_upgrade() -> Result, sp_runtime::TryRuntimeError> { + log::trace!(target: crate::configuration::LOG_TARGET, "Running pre_upgrade() for HostConfiguration MigrateToV9"); + Ok(Vec::new()) + } + + fn on_runtime_upgrade() -> Weight { + log::info!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV9 started"); + if StorageVersion::get::>() == 8 { + let weight_consumed = migrate_to_v9::(); + + log::info!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV9 executed successfully"); + StorageVersion::new(9).put::>(); + + weight_consumed + } else { + log::warn!(target: configuration::LOG_TARGET, "HostConfiguration MigrateToV9 should be removed."); + T::DbWeight::get().reads(1) + } + } + + #[cfg(feature = "try-runtime")] + fn post_upgrade(_state: Vec) -> Result<(), sp_runtime::TryRuntimeError> { + log::trace!(target: crate::configuration::LOG_TARGET, "Running post_upgrade() for HostConfiguration MigrateToV9"); + ensure!( + StorageVersion::get::>() >= 9, + "Storage version should be >= 9 after the migration" + ); + + Ok(()) + } +} + +fn migrate_to_v9() -> Weight { + // Unusual formatting is justified: + // - make it easier to verify that fields assign what they supposed to assign. + // - this code is transient and will be removed after all migrations are done. + // - this code is important enough to optimize for legibility sacrificing consistency. + #[rustfmt::skip] + let translate = + |pre: V8HostConfiguration>| -> + V9HostConfiguration> + { + V9HostConfiguration { +max_code_size : pre.max_code_size, +max_head_data_size : pre.max_head_data_size, +max_upward_queue_count : pre.max_upward_queue_count, +max_upward_queue_size : pre.max_upward_queue_size, +max_upward_message_size : pre.max_upward_message_size, +max_upward_message_num_per_candidate : pre.max_upward_message_num_per_candidate, +hrmp_max_message_num_per_candidate : pre.hrmp_max_message_num_per_candidate, +validation_upgrade_cooldown : pre.validation_upgrade_cooldown, +validation_upgrade_delay : pre.validation_upgrade_delay, +max_pov_size : pre.max_pov_size, +max_downward_message_size : pre.max_downward_message_size, +hrmp_sender_deposit : pre.hrmp_sender_deposit, +hrmp_recipient_deposit : pre.hrmp_recipient_deposit, +hrmp_channel_max_capacity : pre.hrmp_channel_max_capacity, +hrmp_channel_max_total_size : pre.hrmp_channel_max_total_size, +hrmp_max_parachain_inbound_channels : pre.hrmp_max_parachain_inbound_channels, +hrmp_max_parachain_outbound_channels : pre.hrmp_max_parachain_outbound_channels, +hrmp_channel_max_message_size : pre.hrmp_channel_max_message_size, +code_retention_period : pre.code_retention_period, +on_demand_cores : pre.on_demand_cores, +on_demand_retries : pre.on_demand_retries, +group_rotation_frequency : pre.group_rotation_frequency, +paras_availability_period : pre.paras_availability_period, +scheduling_lookahead : pre.scheduling_lookahead, +max_validators_per_core : pre.max_validators_per_core, +max_validators : pre.max_validators, +dispute_period : pre.dispute_period, +dispute_post_conclusion_acceptance_period: pre.dispute_post_conclusion_acceptance_period, +no_show_slots : pre.no_show_slots, +n_delay_tranches : pre.n_delay_tranches, +zeroth_delay_tranche_width : pre.zeroth_delay_tranche_width, +needed_approvals : pre.needed_approvals, +relay_vrf_modulo_samples : pre.relay_vrf_modulo_samples, +pvf_voting_ttl : pre.pvf_voting_ttl, +minimum_validation_upgrade_delay : pre.minimum_validation_upgrade_delay, +async_backing_params : pre.async_backing_params, +executor_params : pre.executor_params, +on_demand_queue_max_size : 10_000u32, +on_demand_base_fee : 10_000_000u128, +on_demand_fee_variability : Perbill::from_percent(3), +on_demand_target_queue_utilization : Perbill::from_percent(25), +on_demand_ttl : 5u32.into(), +minimum_backing_votes : LEGACY_MIN_BACKING_VOTES + } + }; + + let v8 = v8::ActiveConfig::::get() + .defensive_proof("Could not decode old config") + .unwrap_or_default(); + let v9 = translate(v8); + v9::ActiveConfig::::set(Some(v9)); + + // Allowed to be empty. + let pending_v8 = v8::PendingConfigs::::get().unwrap_or_default(); + let mut pending_v9 = Vec::new(); + + for (session, v8) in pending_v8.into_iter() { + let v9 = translate(v8); + pending_v9.push((session, v9)); + } + v9::PendingConfigs::::set(Some(pending_v9.clone())); + + let num_configs = (pending_v9.len() + 1) as u64; + T::DbWeight::get().reads_writes(num_configs, num_configs) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::mock::{new_test_ext, Test}; + + #[test] + fn v9_deserialized_from_actual_data() { + // Example how to get new `raw_config`: + // We'll obtain the raw_config at a specified a block + // Steps: + // 1. Go to Polkadot.js -> Developer -> Chain state -> Storage: https://polkadot.js.org/apps/#/chainstate + // 2. Set these parameters: + // 2.1. selected state query: configuration; activeConfig(): + // PolkadotRuntimeParachainsConfigurationHostConfiguration + // 2.2. blockhash to query at: + // 0xf89d3ab5312c5f70d396dc59612f0aa65806c798346f9db4b35278baed2e0e53 (the hash of + // the block) + // 2.3. Note the value of encoded storage key -> + // 0x06de3d8a54d27e44a9d5ce189618f22db4b49d95320d9021994c850f25b8e385 for the + // referenced block. + // 2.4. You'll also need the decoded values to update the test. + // 3. Go to Polkadot.js -> Developer -> Chain state -> Raw storage + // 3.1 Enter the encoded storage key and you get the raw config. + + // This exceeds the maximal line width length, but that's fine, since this is not code and + // doesn't need to be read and also leaving it as one line allows to easily copy it. + let raw_config = + hex_literal::hex![" + 0000300000800000080000000000100000c8000005000000050000000200000002000000000000000000000000005000000010000400000000000000000000000000000000000000000000000000000000000000000000000800000000200000040000000000100000b004000000000000000000001027000080b2e60e80c3c901809698000000000000000000000000000500000014000000040000000100000001010000000006000000640000000200000019000000000000000300000002000000020000000500000002000000" + ]; + + let v9 = + V9HostConfiguration::::decode(&mut &raw_config[..]).unwrap(); + + // We check only a sample of the values here. If we missed any fields or messed up data + // types that would skew all the fields coming after. + assert_eq!(v9.max_code_size, 3_145_728); + assert_eq!(v9.validation_upgrade_cooldown, 2); + assert_eq!(v9.max_pov_size, 5_242_880); + assert_eq!(v9.hrmp_channel_max_message_size, 1_048_576); + assert_eq!(v9.n_delay_tranches, 25); + assert_eq!(v9.minimum_validation_upgrade_delay, 5); + assert_eq!(v9.group_rotation_frequency, 20); + assert_eq!(v9.on_demand_cores, 0); + assert_eq!(v9.on_demand_base_fee, 10_000_000); + assert_eq!(v9.minimum_backing_votes, LEGACY_MIN_BACKING_VOTES); + } + + #[test] + fn test_migrate_to_v9() { + // Host configuration has lots of fields. However, in this migration we only add one + // field. The most important part to check are a couple of the last fields. We also pick + // extra fields to check arbitrarily, e.g. depending on their position (i.e. the middle) and + // also their type. + // + // We specify only the picked fields and the rest should be provided by the `Default` + // implementation. That implementation is copied over between the two types and should work + // fine. + let v8 = V8HostConfiguration:: { + needed_approvals: 69, + paras_availability_period: 55, + hrmp_recipient_deposit: 1337, + max_pov_size: 1111, + minimum_validation_upgrade_delay: 20, + ..Default::default() + }; + + let mut pending_configs = Vec::new(); + pending_configs.push((100, v8.clone())); + pending_configs.push((300, v8.clone())); + + new_test_ext(Default::default()).execute_with(|| { + // Implant the v8 version in the state. + v8::ActiveConfig::::set(Some(v8)); + v8::PendingConfigs::::set(Some(pending_configs)); + + migrate_to_v9::(); + + let v9 = v9::ActiveConfig::::get().unwrap(); + let mut configs_to_check = v9::PendingConfigs::::get().unwrap(); + configs_to_check.push((0, v9.clone())); + + for (_, v8) in configs_to_check { + #[rustfmt::skip] + { + assert_eq!(v8.max_code_size , v9.max_code_size); + assert_eq!(v8.max_head_data_size , v9.max_head_data_size); + assert_eq!(v8.max_upward_queue_count , v9.max_upward_queue_count); + assert_eq!(v8.max_upward_queue_size , v9.max_upward_queue_size); + assert_eq!(v8.max_upward_message_size , v9.max_upward_message_size); + assert_eq!(v8.max_upward_message_num_per_candidate , v9.max_upward_message_num_per_candidate); + assert_eq!(v8.hrmp_max_message_num_per_candidate , v9.hrmp_max_message_num_per_candidate); + assert_eq!(v8.validation_upgrade_cooldown , v9.validation_upgrade_cooldown); + assert_eq!(v8.validation_upgrade_delay , v9.validation_upgrade_delay); + assert_eq!(v8.max_pov_size , v9.max_pov_size); + assert_eq!(v8.max_downward_message_size , v9.max_downward_message_size); + assert_eq!(v8.hrmp_max_parachain_outbound_channels , v9.hrmp_max_parachain_outbound_channels); + assert_eq!(v8.hrmp_sender_deposit , v9.hrmp_sender_deposit); + assert_eq!(v8.hrmp_recipient_deposit , v9.hrmp_recipient_deposit); + assert_eq!(v8.hrmp_channel_max_capacity , v9.hrmp_channel_max_capacity); + assert_eq!(v8.hrmp_channel_max_total_size , v9.hrmp_channel_max_total_size); + assert_eq!(v8.hrmp_max_parachain_inbound_channels , v9.hrmp_max_parachain_inbound_channels); + assert_eq!(v8.hrmp_channel_max_message_size , v9.hrmp_channel_max_message_size); + assert_eq!(v8.code_retention_period , v9.code_retention_period); + assert_eq!(v8.on_demand_cores , v9.on_demand_cores); + assert_eq!(v8.on_demand_retries , v9.on_demand_retries); + assert_eq!(v8.group_rotation_frequency , v9.group_rotation_frequency); + assert_eq!(v8.paras_availability_period , v9.paras_availability_period); + assert_eq!(v8.scheduling_lookahead , v9.scheduling_lookahead); + assert_eq!(v8.max_validators_per_core , v9.max_validators_per_core); + assert_eq!(v8.max_validators , v9.max_validators); + assert_eq!(v8.dispute_period , v9.dispute_period); + assert_eq!(v8.no_show_slots , v9.no_show_slots); + assert_eq!(v8.n_delay_tranches , v9.n_delay_tranches); + assert_eq!(v8.zeroth_delay_tranche_width , v9.zeroth_delay_tranche_width); + assert_eq!(v8.needed_approvals , v9.needed_approvals); + assert_eq!(v8.relay_vrf_modulo_samples , v9.relay_vrf_modulo_samples); + assert_eq!(v8.pvf_voting_ttl , v9.pvf_voting_ttl); + assert_eq!(v8.minimum_validation_upgrade_delay , v9.minimum_validation_upgrade_delay); + assert_eq!(v8.async_backing_params.allowed_ancestry_len, v9.async_backing_params.allowed_ancestry_len); + assert_eq!(v8.async_backing_params.max_candidate_depth , v9.async_backing_params.max_candidate_depth); + assert_eq!(v8.executor_params , v9.executor_params); + assert_eq!(v8.minimum_backing_votes , v9.minimum_backing_votes); + }; // ; makes this a statement. `rustfmt::skip` cannot be put on an expression. + } + }); + } + + // Test that migration doesn't panic in case there're no pending configurations upgrades in + // pallet's storage. + #[test] + fn test_migrate_to_v9_no_pending() { + let v8 = V8HostConfiguration::::default(); + + new_test_ext(Default::default()).execute_with(|| { + // Implant the v8 version in the state. + v8::ActiveConfig::::set(Some(v8)); + // Ensure there're no pending configs. + v8::PendingConfigs::::set(None); + + // Shouldn't fail. + migrate_to_v9::(); + }); + } +} diff --git a/polkadot/runtime/parachains/src/configuration/tests.rs b/polkadot/runtime/parachains/src/configuration/tests.rs index 43c03067a9a74..ea39628c95876 100644 --- a/polkadot/runtime/parachains/src/configuration/tests.rs +++ b/polkadot/runtime/parachains/src/configuration/tests.rs @@ -317,6 +317,7 @@ fn setting_pending_config_members() { on_demand_fee_variability: Perbill::from_percent(3), on_demand_target_queue_utilization: Perbill::from_percent(25), on_demand_ttl: 5u32, + minimum_backing_votes: 5, }; Configuration::set_validation_upgrade_cooldown( @@ -467,6 +468,11 @@ fn setting_pending_config_members() { .unwrap(); Configuration::set_pvf_voting_ttl(RuntimeOrigin::root(), new_config.pvf_voting_ttl) .unwrap(); + Configuration::set_minimum_backing_votes( + RuntimeOrigin::root(), + new_config.minimum_backing_votes, + ) + .unwrap(); assert_eq!(PendingConfigs::::get(), vec![(shared::SESSION_DELAY, new_config)],); }) diff --git a/polkadot/runtime/parachains/src/inclusion/mod.rs b/polkadot/runtime/parachains/src/inclusion/mod.rs index 0cca61d09964d..19e56be492c75 100644 --- a/polkadot/runtime/parachains/src/inclusion/mod.rs +++ b/polkadot/runtime/parachains/src/inclusion/mod.rs @@ -36,11 +36,11 @@ use frame_system::pallet_prelude::*; use pallet_message_queue::OnQueueChanged; use parity_scale_codec::{Decode, Encode}; use primitives::{ - supermajority_threshold, well_known_keys, AvailabilityBitfield, BackedCandidate, - CandidateCommitments, CandidateDescriptor, CandidateHash, CandidateReceipt, - CommittedCandidateReceipt, CoreIndex, GroupIndex, Hash, HeadData, Id as ParaId, - SignedAvailabilityBitfields, SigningContext, UpwardMessage, ValidatorId, ValidatorIndex, - ValidityAttestation, + effective_minimum_backing_votes, supermajority_threshold, well_known_keys, + AvailabilityBitfield, BackedCandidate, CandidateCommitments, CandidateDescriptor, + CandidateHash, CandidateReceipt, CommittedCandidateReceipt, CoreIndex, GroupIndex, Hash, + HeadData, Id as ParaId, SignedAvailabilityBitfields, SigningContext, UpwardMessage, + ValidatorId, ValidatorIndex, ValidityAttestation, }; use scale_info::TypeInfo; use sp_runtime::{traits::One, DispatchError, SaturatedConversion, Saturating}; @@ -199,17 +199,6 @@ impl Default for ProcessedCandidates { } } -/// Number of backing votes we need for a valid backing. -/// -/// WARNING: This check has to be kept in sync with the node side checks. -pub fn minimum_backing_votes(n_validators: usize) -> usize { - // For considerations on this value see: - // https://github.com/paritytech/polkadot/pull/1656#issuecomment-999734650 - // and - // https://github.com/paritytech/polkadot/issues/4386 - sp_std::cmp::min(n_validators, 2) -} - /// Reads the footprint of queues for a specific origin type. pub trait QueueFootprinter { type Origin; @@ -623,6 +612,7 @@ impl Pallet { return Ok(ProcessedCandidates::default()) } + let minimum_backing_votes = configuration::Pallet::::config().minimum_backing_votes; let validators = shared::Pallet::::active_validator_keys(); // Collect candidate receipts with backers. @@ -739,7 +729,11 @@ impl Pallet { match maybe_amount_validated { Ok(amount_validated) => ensure!( - amount_validated >= minimum_backing_votes(group_vals.len()), + amount_validated >= + effective_minimum_backing_votes( + group_vals.len(), + minimum_backing_votes + ), Error::::InsufficientBacking, ), Err(()) => { diff --git a/polkadot/runtime/parachains/src/inclusion/tests.rs b/polkadot/runtime/parachains/src/inclusion/tests.rs index a07413702771d..89fdc6eec6796 100644 --- a/polkadot/runtime/parachains/src/inclusion/tests.rs +++ b/polkadot/runtime/parachains/src/inclusion/tests.rs @@ -26,7 +26,10 @@ use crate::{ paras_inherent::DisputedBitfield, shared::AllowedRelayParentsTracker, }; -use primitives::{SignedAvailabilityBitfields, UncheckedSignedAvailabilityBitfields}; +use primitives::{ + effective_minimum_backing_votes, SignedAvailabilityBitfields, + UncheckedSignedAvailabilityBitfields, +}; use assert_matches::assert_matches; use frame_support::assert_noop; @@ -120,11 +123,14 @@ pub(crate) fn back_candidate( kind: BackingKind, ) -> BackedCandidate { let mut validator_indices = bitvec::bitvec![u8, BitOrderLsb0; 0; group.len()]; - let threshold = minimum_backing_votes(group.len()); + let threshold = effective_minimum_backing_votes( + group.len(), + configuration::Pallet::::config().minimum_backing_votes, + ); let signing = match kind { BackingKind::Unanimous => group.len(), - BackingKind::Threshold => threshold, + BackingKind::Threshold => threshold as usize, BackingKind::Lacking => threshold.saturating_sub(1), }; @@ -1615,7 +1621,10 @@ fn backing_works() { ); let backers = { - let num_backers = minimum_backing_votes(group_validators(GroupIndex(0)).unwrap().len()); + let num_backers = effective_minimum_backing_votes( + group_validators(GroupIndex(0)).unwrap().len(), + configuration::Pallet::::config().minimum_backing_votes, + ); backing_bitfield(&(0..num_backers).collect::>()) }; assert_eq!( @@ -1637,7 +1646,10 @@ fn backing_works() { ); let backers = { - let num_backers = minimum_backing_votes(group_validators(GroupIndex(0)).unwrap().len()); + let num_backers = effective_minimum_backing_votes( + group_validators(GroupIndex(0)).unwrap().len(), + configuration::Pallet::::config().minimum_backing_votes, + ); backing_bitfield(&(0..num_backers).map(|v| v + 2).collect::>()) }; assert_eq!( @@ -1771,7 +1783,10 @@ fn can_include_candidate_with_ok_code_upgrade() { assert_eq!(occupied_cores, vec![(CoreIndex::from(0), chain_a)]); let backers = { - let num_backers = minimum_backing_votes(group_validators(GroupIndex(0)).unwrap().len()); + let num_backers = effective_minimum_backing_votes( + group_validators(GroupIndex(0)).unwrap().len(), + configuration::Pallet::::config().minimum_backing_votes, + ); backing_bitfield(&(0..num_backers).collect::>()) }; assert_eq!( diff --git a/polkadot/runtime/parachains/src/paras_inherent/tests.rs b/polkadot/runtime/parachains/src/paras_inherent/tests.rs index 1e5271909664d..ab515cb37565b 100644 --- a/polkadot/runtime/parachains/src/paras_inherent/tests.rs +++ b/polkadot/runtime/parachains/src/paras_inherent/tests.rs @@ -948,8 +948,11 @@ fn default_header() -> primitives::Header { mod sanitizers { use super::*; - use crate::inclusion::tests::{ - back_candidate, collator_sign_candidate, BackingKind, TestCandidateBuilder, + use crate::{ + inclusion::tests::{ + back_candidate, collator_sign_candidate, BackingKind, TestCandidateBuilder, + }, + mock::{new_test_ext, MockGenesisConfig}, }; use bitvec::order::Lsb0; use primitives::{ @@ -1207,131 +1210,133 @@ mod sanitizers { #[test] fn candidates() { - const RELAY_PARENT_NUM: u32 = 3; - - let header = default_header(); - let relay_parent = header.hash(); - let session_index = SessionIndex::from(0_u32); - - let keystore = LocalKeystore::in_memory(); - let keystore = Arc::new(keystore) as KeystorePtr; - let signing_context = SigningContext { parent_hash: relay_parent, session_index }; - - let validators = vec![ - keyring::Sr25519Keyring::Alice, - keyring::Sr25519Keyring::Bob, - keyring::Sr25519Keyring::Charlie, - keyring::Sr25519Keyring::Dave, - ]; - for validator in validators.iter() { - Keystore::sr25519_generate_new( - &*keystore, - PARACHAIN_KEY_TYPE_ID, - Some(&validator.to_seed()), - ) - .unwrap(); - } - - let has_concluded_invalid = - |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; - - let entry_ttl = 10_000; - let scheduled = (0_usize..2) - .into_iter() - .map(|idx| { - let core_idx = CoreIndex::from(idx as u32); - let ca = CoreAssignment { - paras_entry: ParasEntry::new( - Assignment::new(ParaId::from(1_u32 + idx as u32)), - entry_ttl, - ), - core: core_idx, - }; - ca - }) - .collect::>(); - - let group_validators = |group_index: GroupIndex| { - match group_index { - group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), - group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), - _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), + new_test_ext(MockGenesisConfig::default()).execute_with(|| { + const RELAY_PARENT_NUM: u32 = 3; + + let header = default_header(); + let relay_parent = header.hash(); + let session_index = SessionIndex::from(0_u32); + + let keystore = LocalKeystore::in_memory(); + let keystore = Arc::new(keystore) as KeystorePtr; + let signing_context = SigningContext { parent_hash: relay_parent, session_index }; + + let validators = vec![ + keyring::Sr25519Keyring::Alice, + keyring::Sr25519Keyring::Bob, + keyring::Sr25519Keyring::Charlie, + keyring::Sr25519Keyring::Dave, + ]; + for validator in validators.iter() { + Keystore::sr25519_generate_new( + &*keystore, + PARACHAIN_KEY_TYPE_ID, + Some(&validator.to_seed()), + ) + .unwrap(); } - .map(|m| m.into_iter().map(ValidatorIndex).collect::>()) - }; - - let backed_candidates = (0_usize..2) - .into_iter() - .map(|idx0| { - let idx1 = idx0 + 1; - let mut candidate = TestCandidateBuilder { - para_id: ParaId::from(idx1), - relay_parent, - pov_hash: Hash::repeat_byte(idx1 as u8), - persisted_validation_data_hash: [42u8; 32].into(), - hrmp_watermark: RELAY_PARENT_NUM, - ..Default::default() - } - .build(); - - collator_sign_candidate(Sr25519Keyring::One, &mut candidate); - let backed = back_candidate( - candidate, - &validators, - group_validators(GroupIndex::from(idx0 as u32)).unwrap().as_ref(), - &keystore, - &signing_context, - BackingKind::Threshold, - ); - backed - }) - .collect::>(); - - // happy path - assert_eq!( - sanitize_backed_candidates::( - backed_candidates.clone(), - has_concluded_invalid, - &scheduled - ), - backed_candidates - ); - - // nothing is scheduled, so no paraids match, thus all backed candidates are skipped - { - let scheduled = &Vec::new(); - assert!(sanitize_backed_candidates::( - backed_candidates.clone(), - has_concluded_invalid, - &scheduled - ) - .is_empty()); - } + let has_concluded_invalid = + |_idx: usize, _backed_candidate: &BackedCandidate| -> bool { false }; - // candidates that have concluded as invalid are filtered out - { - // mark every second one as concluded invalid - let set = { - let mut set = std::collections::HashSet::new(); - for (idx, backed_candidate) in backed_candidates.iter().enumerate() { - if idx & 0x01 == 0 { - set.insert(backed_candidate.hash()); - } + let entry_ttl = 10_000; + let scheduled = (0_usize..2) + .into_iter() + .map(|idx| { + let core_idx = CoreIndex::from(idx as u32); + let ca = CoreAssignment { + paras_entry: ParasEntry::new( + Assignment::new(ParaId::from(1_u32 + idx as u32)), + entry_ttl, + ), + core: core_idx, + }; + ca + }) + .collect::>(); + + let group_validators = |group_index: GroupIndex| { + match group_index { + group_index if group_index == GroupIndex::from(0) => Some(vec![0, 1]), + group_index if group_index == GroupIndex::from(1) => Some(vec![2, 3]), + _ => panic!("Group index out of bounds for 2 parachains and 1 parathread core"), } - set + .map(|m| m.into_iter().map(ValidatorIndex).collect::>()) }; - let has_concluded_invalid = - |_idx: usize, candidate: &BackedCandidate| set.contains(&candidate.hash()); + + let backed_candidates = (0_usize..2) + .into_iter() + .map(|idx0| { + let idx1 = idx0 + 1; + let mut candidate = TestCandidateBuilder { + para_id: ParaId::from(idx1), + relay_parent, + pov_hash: Hash::repeat_byte(idx1 as u8), + persisted_validation_data_hash: [42u8; 32].into(), + hrmp_watermark: RELAY_PARENT_NUM, + ..Default::default() + } + .build(); + + collator_sign_candidate(Sr25519Keyring::One, &mut candidate); + + let backed = back_candidate( + candidate, + &validators, + group_validators(GroupIndex::from(idx0 as u32)).unwrap().as_ref(), + &keystore, + &signing_context, + BackingKind::Threshold, + ); + backed + }) + .collect::>(); + + // happy path assert_eq!( sanitize_backed_candidates::( backed_candidates.clone(), has_concluded_invalid, &scheduled - ) - .len(), - backed_candidates.len() / 2 + ), + backed_candidates ); - } + + // nothing is scheduled, so no paraids match, thus all backed candidates are skipped + { + let scheduled = &Vec::new(); + assert!(sanitize_backed_candidates::( + backed_candidates.clone(), + has_concluded_invalid, + &scheduled + ) + .is_empty()); + } + + // candidates that have concluded as invalid are filtered out + { + // mark every second one as concluded invalid + let set = { + let mut set = std::collections::HashSet::new(); + for (idx, backed_candidate) in backed_candidates.iter().enumerate() { + if idx & 0x01 == 0 { + set.insert(backed_candidate.hash()); + } + } + set + }; + let has_concluded_invalid = + |_idx: usize, candidate: &BackedCandidate| set.contains(&candidate.hash()); + assert_eq!( + sanitize_backed_candidates::( + backed_candidates.clone(), + has_concluded_invalid, + &scheduled + ) + .len(), + backed_candidates.len() / 2 + ); + } + }); } } diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs index 5406428377d0d..deef19d907107 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -118,3 +118,8 @@ pub fn backing_state( pub fn async_backing_params() -> AsyncBackingParams { >::config().async_backing_params } + +/// Return the min backing votes threshold from the configuration. +pub fn minimum_backing_votes() -> u32 { + >::config().minimum_backing_votes +} diff --git a/polkadot/runtime/polkadot/src/lib.rs b/polkadot/runtime/polkadot/src/lib.rs index f97c0981039f0..2efd329eea0cc 100644 --- a/polkadot/runtime/polkadot/src/lib.rs +++ b/polkadot/runtime/polkadot/src/lib.rs @@ -1522,6 +1522,8 @@ pub mod migrations { frame_support::migrations::RemovePallet::DbWeight>, frame_support::migrations::RemovePallet::DbWeight>, frame_support::migrations::RemovePallet::DbWeight>, + + parachains_configuration::migration::v9::MigrateToV9, ); } diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index 192f4117ba6db..a80f45c340d97 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -1549,6 +1549,7 @@ pub mod migrations { assigned_slots::migration::v1::VersionCheckedMigrateToV1, parachains_scheduler::migration::v1::MigrateToV1, parachains_configuration::migration::v8::MigrateToV8, + parachains_configuration::migration::v9::MigrateToV9, ); } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index 12baa20a129f4..73aa4980151ea 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -62,7 +62,9 @@ use runtime_parachains::{ inclusion::{AggregateMessageOrigin, UmpQueueId}, initializer as parachains_initializer, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, reward_points as parachains_reward_points, - runtime_api_impl::v5 as parachains_runtime_api_impl, + runtime_api_impl::{ + v5 as parachains_runtime_api_impl, vstaging as parachains_staging_runtime_api_impl, + }, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -1422,6 +1424,7 @@ pub mod migrations { parachains_scheduler::migration::v1::MigrateToV1, parachains_configuration::migration::v8::MigrateToV8, UpgradeSessionKeys, + parachains_configuration::migration::v9::MigrateToV9, ); } @@ -1557,6 +1560,7 @@ sp_api::impl_runtime_apis! { } } + #[api_version(6)] impl primitives::runtime_api::ParachainHost for Runtime { fn validators() -> Vec { parachains_runtime_api_impl::validators::() @@ -1687,6 +1691,10 @@ sp_api::impl_runtime_apis! { key_ownership_proof, ) } + + fn minimum_backing_votes() -> u32 { + parachains_staging_runtime_api_impl::minimum_backing_votes::() + } } impl beefy_primitives::BeefyApi for Runtime { diff --git a/polkadot/statement-table/src/generic.rs b/polkadot/statement-table/src/generic.rs index a427aae42fb95..22bffde5acc11 100644 --- a/polkadot/statement-table/src/generic.rs +++ b/polkadot/statement-table/src/generic.rs @@ -30,7 +30,10 @@ use std::{ hash::Hash, }; -use primitives::{ValidatorSignature, ValidityAttestation as PrimitiveValidityAttestation}; +use primitives::{ + effective_minimum_backing_votes, ValidatorSignature, + ValidityAttestation as PrimitiveValidityAttestation, +}; use parity_scale_codec::{Decode, Encode}; @@ -57,8 +60,8 @@ pub trait Context { /// Members are meant to submit candidates and vote on validity. fn is_member_of(&self, authority: &Self::AuthorityId, group: &Self::GroupId) -> bool; - /// requisite number of votes for validity from a group. - fn requisite_votes(&self, group: &Self::GroupId) -> usize; + /// Get a validator group size. + fn get_group_size(&self, group: &Self::GroupId) -> Option; } /// Table configuration. @@ -319,9 +322,12 @@ impl Table { &self, digest: &Ctx::Digest, context: &Ctx, + minimum_backing_votes: u32, ) -> Option> { self.candidate_votes.get(digest).and_then(|data| { - let v_threshold = context.requisite_votes(&data.group_id); + let v_threshold = context.get_group_size(&data.group_id).map_or(usize::MAX, |len| { + effective_minimum_backing_votes(len, minimum_backing_votes) + }); data.attested(v_threshold) }) } @@ -636,16 +642,13 @@ mod tests { self.authorities.get(authority).map(|v| v == group).unwrap_or(false) } - fn requisite_votes(&self, id: &GroupId) -> usize { - let mut total_validity = 0; - - for validity in self.authorities.values() { - if validity == id { - total_validity += 1 - } + fn get_group_size(&self, group: &Self::GroupId) -> Option { + let count = self.authorities.values().filter(|g| *g == group).count(); + if count == 0 { + None + } else { + Some(count) } - - total_validity / 2 + 1 } } @@ -910,7 +913,7 @@ mod tests { table.import_statement(&context, statement); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(1))); - assert!(table.attested_candidate(&candidate_digest, &context).is_none()); + assert!(table.attested_candidate(&candidate_digest, &context, 2).is_none()); let vote = SignedStatement { statement: Statement::Valid(candidate_digest), @@ -920,7 +923,7 @@ mod tests { table.import_statement(&context, vote); assert!(!table.detected_misbehavior.contains_key(&AuthorityId(2))); - assert!(table.attested_candidate(&candidate_digest, &context).is_some()); + assert!(table.attested_candidate(&candidate_digest, &context, 2).is_some()); } #[test]