diff --git a/node/core/backing/src/lib.rs b/node/core/backing/src/lib.rs index 58763e6d80cc..fa11183a5cc0 100644 --- a/node/core/backing/src/lib.rs +++ b/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::{ @@ -96,8 +96,7 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{ self as util, backing_implicit_view::{FetchError as ImplicitViewFetchError, View as ImplicitView}, - request_from_runtime, request_session_index_for_child, request_validator_groups, - request_validators, + request_from_runtime, request_validator_groups, request_validators, runtime::{prospective_parachains_mode, ProspectiveParachainsMode}, Validator, }; @@ -116,6 +115,7 @@ use statement_table::{ }, Config as TableConfig, Context as TableContextTrait, Table, }; +use util::runtime::RuntimeInfo; mod error; @@ -219,6 +219,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 { @@ -275,6 +277,8 @@ struct State { background_validation_tx: mpsc::Sender<(Hash, ValidatedCandidateCommand)>, /// The handle to the keystore used for signing. keystore: KeystorePtr, + /// The minimum backing votes threshold. + runtime_info: RuntimeInfo, } impl State { @@ -289,6 +293,7 @@ impl State { per_candidate: HashMap::new(), background_validation_tx, keystore, + runtime_info: RuntimeInfo::new(None), } } } @@ -400,8 +405,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()) } } @@ -943,7 +948,14 @@ async fn handle_active_leaves_update( // construct a `PerRelayParent` from the runtime API // and insert it. - let per = construct_per_relay_parent_state(ctx, maybe_new, &state.keystore, mode).await?; + let per = construct_per_relay_parent_state( + ctx, + maybe_new, + &state.keystore, + &mut state.runtime_info, + mode, + ) + .await?; if let Some(per) = per { state.per_relay_parent.insert(maybe_new, per); @@ -959,6 +971,7 @@ async fn construct_per_relay_parent_state( ctx: &mut Context, relay_parent: Hash, keystore: &KeystorePtr, + runtime_info: &mut RuntimeInfo, mode: ProspectiveParachainsMode, ) -> Result, Error> { macro_rules! try_runtime_api { @@ -983,10 +996,14 @@ async fn construct_per_relay_parent_state( let parent = relay_parent; - let (validators, groups, session_index, cores) = futures::try_join!( + let session_index = + try_runtime_api!(runtime_info.get_session_index_for_child(ctx.sender(), parent).await); + let minimum_backing_votes = + runtime_info.get_min_backing_votes(ctx.sender(), session_index, parent).await; + + let (validators, groups, cores) = futures::try_join!( 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) },) @@ -996,8 +1013,8 @@ async fn construct_per_relay_parent_state( 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!(minimum_backing_votes); let signing_context = SigningContext { parent_hash: parent, session_index }; let validator = @@ -1061,6 +1078,7 @@ async fn construct_per_relay_parent_state( issued_statements: HashSet::new(), awaiting_validation: HashSet::new(), fallbacks: HashMap::new(), + minimum_backing_votes, })) } @@ -1563,10 +1581,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 +2030,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/node/core/backing/src/tests/mod.rs b/node/core/backing/src/tests/mod.rs index 054337669c07..5bb8326d409c 100644 --- a/node/core/backing/src/tests/mod.rs +++ b/node/core/backing/src/tests/mod.rs @@ -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: 2, } } } @@ -250,33 +252,50 @@ async fn test_startup(virtual_overseer: &mut VirtualOverseer, test_state: &TestS } ); - // Check that subsystem job issues a request for a validator set. + // 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::Validators(tx)) + RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) ) if parent == test_state.relay_parent => { - tx.send(Ok(test_state.validator_public.clone())).unwrap(); + tx.send(Ok(test_state.signing_context.session_index)).unwrap(); } ); - // Check that subsystem job issues a request for the validator groups. + // Check if subsystem job issues a request for the minimum backing votes. + // This may or may not happen, depending if the minimum backing votes is already cached in the + // RuntimeInfo. + let next_message = { + let msg = virtual_overseer.recv().await; + match msg { + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::MinimumBackingVotes(tx), + )) if parent == test_state.relay_parent => { + tx.send(Ok(test_state.minimum_backing_votes)).unwrap(); + virtual_overseer.recv().await + }, + _ => msg, + } + }; + + // Check that subsystem job issues a request for a validator set. assert_matches!( - virtual_overseer.recv().await, + next_message, AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx)) + RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx)) ) if parent == test_state.relay_parent => { - tx.send(Ok(test_state.validator_groups.clone())).unwrap(); + tx.send(Ok(test_state.validator_public.clone())).unwrap(); } ); - // Check that subsystem job issues a request for the session index for child. + // Check that subsystem job issues a request for the validator groups. assert_matches!( virtual_overseer.recv().await, AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::SessionIndexForChild(tx)) + RuntimeApiMessage::Request(parent, RuntimeApiRequest::ValidatorGroups(tx)) ) if parent == test_state.relay_parent => { - tx.send(Ok(test_state.signing_context.session_index)).unwrap(); + tx.send(Ok(test_state.validator_groups.clone())).unwrap(); } ); diff --git a/node/core/backing/src/tests/prospective_parachains.rs b/node/core/backing/src/tests/prospective_parachains.rs index 7c2773c8e3b6..93a8e94b98de 100644 --- a/node/core/backing/src/tests/prospective_parachains.rs +++ b/node/core/backing/src/tests/prospective_parachains.rs @@ -138,13 +138,41 @@ 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 if subsystem job issues a request for the minimum backing votes. + // This may or may not happen, depending if the minimum backing votes is already cached in + // the `RuntimeInfo`. + let next_message = { + let msg = virtual_overseer.recv().await; + match msg { + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::MinimumBackingVotes(tx), + )) if parent == hash => { + tx.send(Ok(test_state.minimum_backing_votes)).unwrap(); + virtual_overseer.recv().await + }, + _ => msg, + } + }; + + // Check that subsystem job issues a request for a validator set. + assert_matches!( + next_message, AllMessages::RuntimeApi( RuntimeApiMessage::Request(parent, RuntimeApiRequest::Validators(tx)) ) if parent == hash => { @@ -164,16 +192,6 @@ async fn activate_leaf( } ); - // 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 == hash => { - tx.send(Ok(test_state.signing_context.session_index)).unwrap(); - } - ); - // Check that subsystem job issues a request for the availability cores. assert_matches!( virtual_overseer.recv().await, diff --git a/node/core/runtime-api/src/cache.rs b/node/core/runtime-api/src/cache.rs index 26aaf3fb6ec8..07c8095fd34e 100644 --- a/node/core/runtime-api/src/cache.rs +++ b/node/core/runtime-api/src/cache.rs @@ -40,6 +40,7 @@ const DEFAULT_CACHE_CAP: NonZeroUsize = match NonZeroUsize::new(128) { pub(crate) struct RequestResultCache { authorities: LruCache>, validators: LruCache>, + minimum_backing_votes: LruCache, validator_groups: LruCache>, GroupRotationInfo)>, availability_cores: LruCache>, persisted_validation_data: @@ -78,6 +79,7 @@ impl Default for RequestResultCache { Self { authorities: LruCache::new(DEFAULT_CACHE_CAP), validators: LruCache::new(DEFAULT_CACHE_CAP), + minimum_backing_votes: LruCache::new(DEFAULT_CACHE_CAP), validator_groups: LruCache::new(DEFAULT_CACHE_CAP), availability_cores: LruCache::new(DEFAULT_CACHE_CAP), persisted_validation_data: LruCache::new(DEFAULT_CACHE_CAP), @@ -131,6 +133,18 @@ impl RequestResultCache { self.validators.put(relay_parent, validators); } + pub(crate) fn minimum_backing_votes(&mut self, relay_parent: &Hash) -> Option { + self.minimum_backing_votes.get(relay_parent).copied() + } + + pub(crate) fn cache_minimum_backing_votes( + &mut self, + relay_parent: Hash, + minimum_backing_votes: u32, + ) { + self.minimum_backing_votes.put(relay_parent, minimum_backing_votes); + } + pub(crate) fn validator_groups( &mut self, relay_parent: &Hash, @@ -472,6 +486,7 @@ pub(crate) enum RequestResult { // The structure of each variant is (relay_parent, [params,]*, result) Authorities(Hash, Vec), Validators(Hash, Vec), + MinimumBackingVotes(Hash, u32), ValidatorGroups(Hash, (Vec>, GroupRotationInfo)), AvailabilityCores(Hash, Vec), PersistedValidationData(Hash, ParaId, OccupiedCoreAssumption, Option), diff --git a/node/core/runtime-api/src/lib.rs b/node/core/runtime-api/src/lib.rs index 78531d41272b..37e63ff673cd 100644 --- a/node/core/runtime-api/src/lib.rs +++ b/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(relay_parent, minimum_backing_votes) => self + .requests_cache + .cache_minimum_backing_votes(relay_parent, minimum_backing_votes), ValidatorGroups(relay_parent, groups) => self.requests_cache.cache_validator_groups(relay_parent, groups), AvailabilityCores(relay_parent, cores) => @@ -301,6 +304,8 @@ where Request::StagingAsyncBackingParams(sender) => query!(staging_async_backing_params(), sender) .map(|sender| Request::StagingAsyncBackingParams(sender)), + Request::MinimumBackingVotes(sender) => query!(minimum_backing_votes(), sender) + .map(|sender| Request::MinimumBackingVotes(sender)), } } @@ -450,6 +455,9 @@ where Request::Authorities(sender) => query!(Authorities, authorities(), ver = 1, sender), Request::Validators(sender) => query!(Validators, validators(), ver = 1, sender), + Request::MinimumBackingVotes(sender) => + query!(MinimumBackingVotes, minimum_backing_votes(), ver = 1, sender), + Request::ValidatorGroups(sender) => { query!(ValidatorGroups, validator_groups(), ver = 1, sender) }, diff --git a/node/service/src/fake_runtime_api.rs b/node/service/src/fake_runtime_api.rs index d9553afa024b..f8eec2143352 100644 --- a/node/service/src/fake_runtime_api.rs +++ b/node/service/src/fake_runtime_api.rs @@ -121,6 +121,10 @@ sp_api::impl_runtime_apis! { unimplemented!() } + fn minimum_backing_votes() -> u32 { + unimplemented!() + } + fn validator_groups() -> (Vec>, GroupRotationInfo) { unimplemented!() } diff --git a/node/subsystem-types/src/messages.rs b/node/subsystem-types/src/messages.rs index 8adc39eed56d..aafec7c8bbee 100644 --- a/node/subsystem-types/src/messages.rs +++ b/node/subsystem-types/src/messages.rs @@ -606,6 +606,8 @@ pub enum RuntimeApiRequest { Authorities(RuntimeApiSender>), /// Get the current validator set. Validators(RuntimeApiSender>), + /// Get the minimum required backing votes. + MinimumBackingVotes(RuntimeApiSender), /// Get the validator groups and group rotation info. ValidatorGroups(RuntimeApiSender<(Vec>, GroupRotationInfo)>), /// Get information on all availability cores. diff --git a/node/subsystem-types/src/runtime_client.rs b/node/subsystem-types/src/runtime_client.rs index 312cc4eec6ce..72ea2113e4bc 100644 --- a/node/subsystem-types/src/runtime_client.rs +++ b/node/subsystem-types/src/runtime_client.rs @@ -40,6 +40,9 @@ pub trait RuntimeApiSubsystemClient { /// Get the current validators. async fn validators(&self, at: Hash) -> Result, ApiError>; + /// Get the minimum number of backing votes. + async fn minimum_backing_votes(&self, at: Hash) -> Result; + /// Returns the validator groups and rotation info localized based on the hypothetical child /// of a block whose state this is invoked on. Note that `now` in the `GroupRotationInfo` /// should be the successor of the number of the block. @@ -275,6 +278,10 @@ where self.client.runtime_api().validators(at) } + async fn minimum_backing_votes(&self, at: Hash) -> Result { + self.client.runtime_api().minimum_backing_votes(at) + } + async fn validator_groups( &self, at: Hash, diff --git a/node/subsystem-util/src/runtime/mod.rs b/node/subsystem-util/src/runtime/mod.rs index 1f5641e3ea95..534449ca7507 100644 --- a/node/subsystem-util/src/runtime/mod.rs +++ b/node/subsystem-util/src/runtime/mod.rs @@ -26,7 +26,9 @@ 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, @@ -36,9 +38,9 @@ use polkadot_primitives::{ }; 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, }; @@ -74,6 +76,9 @@ pub struct RuntimeInfo { /// Look up cached sessions by `SessionIndex`. session_info_cache: LruCache, + /// Look up minimum validator backing votes threshold by `SessionIndex`. + min_backing_votes: LruCache, + /// Key store for determining whether we are a validator and what `ValidatorIndex` we have. keystore: Option, } @@ -120,6 +125,7 @@ impl RuntimeInfo { .max(NonZeroUsize::new(10).expect("10 is larger than 0; qed")), ), session_info_cache: LruCache::new(cfg.session_cache_lru_size), + min_backing_votes: LruCache::new(cfg.session_cache_lru_size), keystore: cfg.keystore, } } @@ -159,6 +165,34 @@ impl RuntimeInfo { self.get_session_info_by_index(sender, relay_parent, session_index).await } + /// Get minimum_backing_votes by relay parent hash. + pub async fn get_min_backing_votes<'a, Sender>( + &mut self, + sender: &mut Sender, + session_index: SessionIndex, + relay_parent: Hash, + ) -> Result + where + Sender: SubsystemSender, + { + if !self.min_backing_votes.contains(&session_index) { + let min_votes = recv_runtime( + request_from_runtime(relay_parent, sender, |tx| { + RuntimeApiRequest::MinimumBackingVotes(tx) + }) + .await, + ) + .await?; + + self.min_backing_votes.put(session_index, min_votes); + } + + Ok(*self + .min_backing_votes + .get(&session_index) + .expect("We just put the value there. qed.")) + } + /// Get `ExtendedSessionInfo` by session index. /// /// `request_session_info` still requires the parent to be passed in, so we take the parent diff --git a/primitives/src/runtime_api.rs b/primitives/src/runtime_api.rs index 483256fe20f3..0c338e85ef2e 100644 --- a/primitives/src/runtime_api.rs +++ b/primitives/src/runtime_api.rs @@ -240,6 +240,9 @@ sp_api::decl_runtime_apis! { key_ownership_proof: vstaging::slashing::OpaqueKeyOwnershipProof, ) -> Option<()>; + /// Get the minimum number of backing votes for a parachain candidate. + fn minimum_backing_votes() -> u32; + /***** Asynchronous backing *****/ /// Returns the state of parachain backing for a given para. diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index c7077e38a653..41fce1721c76 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -1757,6 +1757,10 @@ sp_api::impl_runtime_apis! { parachains_runtime_api_impl::validators::() } + fn minimum_backing_votes() -> u32 { + parachains_runtime_api_impl::minimum_backing_votes::() + } + fn validator_groups() -> (Vec>, GroupRotationInfo) { parachains_runtime_api_impl::validator_groups::() } diff --git a/runtime/parachains/src/configuration.rs b/runtime/parachains/src/configuration.rs index 03d1ae420495..f3ee2dc80865 100644 --- a/runtime/parachains/src/configuration.rs +++ b/runtime/parachains/src/configuration.rs @@ -242,6 +242,9 @@ pub struct HostConfiguration { /// /// This value should be greater than [`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 { @@ -292,6 +295,7 @@ impl> Default for HostConfiguration 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; @@ -622,6 +611,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. @@ -738,7 +728,11 @@ impl Pallet { match maybe_amount_validated { Ok(amount_validated) => ensure!( - amount_validated >= minimum_backing_votes(group_vals.len()), + amount_validated >= + sp_std::cmp::min( + group_vals.len(), + minimum_backing_votes as usize + ), Error::::InsufficientBacking, ), Err(()) => { diff --git a/runtime/parachains/src/runtime_api_impl/v5.rs b/runtime/parachains/src/runtime_api_impl/v5.rs index cd1579689733..1f88833e2749 100644 --- a/runtime/parachains/src/runtime_api_impl/v5.rs +++ b/runtime/parachains/src/runtime_api_impl/v5.rs @@ -38,6 +38,12 @@ pub fn validators() -> Vec { >::active_validator_keys() } +/// Implementation of the `minimum_backing_votes` function of the runtime API. +pub fn minimum_backing_votes() -> u32 { + let config = >::config(); + config.minimum_backing_votes +} + /// Implementation for the `validator_groups` function of the runtime API. pub fn validator_groups( ) -> (Vec>, GroupRotationInfo>) { diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index ad10de445ab3..88b1d593aaab 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -1695,6 +1695,10 @@ sp_api::impl_runtime_apis! { parachains_runtime_api_impl::validators::() } + fn minimum_backing_votes() -> u32 { + parachains_runtime_api_impl::minimum_backing_votes::() + } + fn validator_groups() -> (Vec>, GroupRotationInfo) { parachains_runtime_api_impl::validator_groups::() } diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index fb2a56c8100c..997f65454f04 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -1720,6 +1720,10 @@ sp_api::impl_runtime_apis! { parachains_runtime_api_impl::validators::() } + fn minimum_backing_votes() -> u32 { + parachains_runtime_api_impl::minimum_backing_votes::() + } + fn validator_groups() -> (Vec>, GroupRotationInfo) { parachains_runtime_api_impl::validator_groups::() } diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index b2397299430d..154ee6001f63 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -826,6 +826,10 @@ sp_api::impl_runtime_apis! { runtime_impl::validators::() } + fn minimum_backing_votes() -> u32 { + runtime_impl::minimum_backing_votes::() + } + fn validator_groups() -> (Vec>, GroupRotationInfo) { runtime_impl::validator_groups::() } diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 3ade28c51fba..67594220a13a 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -1432,6 +1432,10 @@ sp_api::impl_runtime_apis! { parachains_runtime_api_impl::validators::() } + fn minimum_backing_votes() -> u32 { + parachains_runtime_api_impl::minimum_backing_votes::() + } + fn validator_groups() -> (Vec>, GroupRotationInfo) { parachains_runtime_api_impl::validator_groups::() } diff --git a/statement-table/src/generic.rs b/statement-table/src/generic.rs index a427aae42fb9..a40b39381bf0 100644 --- a/statement-table/src/generic.rs +++ b/statement-table/src/generic.rs @@ -57,8 +57,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 +319,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| std::cmp::min(minimum_backing_votes as usize, len)); data.attested(v_threshold) }) } @@ -636,16 +639,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 +910,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 +920,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]