From b74335acfbae7957fd3b124b358a5ccda1112c3e Mon Sep 17 00:00:00 2001 From: Bernhard Schuster Date: Wed, 6 Oct 2021 21:16:23 +0200 Subject: [PATCH] collect included disputes from on-chain (#3924) * dummy: impl another runtime API * query the on chain disputes, and inform self * make use of the refactor * minro * SPLIT ME * write dispute values * wip * impl for all runtimes * chore: fmt * [] -> get * fixup mock runtime * fixup * fixup discovery for overseer init * chore: fmt * spellcheck * rename imported_on_chain_disputes -> on_chain_votes * reduction * make it mockable * rename and refactor * don't query on chain info if it's not needed * yikes * fmt * fix test * minimal fix for existing tests * attempt to fetch the session info from the rolling window before falling back * moved * comments * comments * test for backing votes * rename * Update runtime/polkadot/src/lib.rs * chore: spellcheck + dict * chore: fmt * fixup cache size * add warning * logging, rationale, less defense * introduce new unchecked, that still checks in debug builds * fix * draft alt approach * fix unused imports * include the session * Update node/core/dispute-coordinator/src/real/mod.rs Co-authored-by: Robert Habermeier * provide where possible * expand comment * fixin * fixup * ValidityVote <-> ValidityAttestation <-> CompactStatement has a 1:1 representation * mark TODO * Update primitives/src/v1/mod.rs Co-authored-by: Robert Habermeier * address review comments * update docs Co-authored-by: Robert Habermeier --- doc/shell-completion.md | 1 + node/core/dispute-coordinator/src/real/mod.rs | 283 ++++++++++++++++-- .../dispute-coordinator/src/real/tests.rs | 11 + node/core/runtime-api/src/cache.rs | 22 +- node/core/runtime-api/src/lib.rs | 5 + node/core/runtime-api/src/tests.rs | 9 +- node/primitives/src/disputes/mod.rs | 20 ++ node/subsystem-types/src/messages.rs | 2 + primitives/src/v0.rs | 13 + primitives/src/v1/mod.rs | 25 +- .../src/node/disputes/dispute-coordinator.md | 2 + .../src/runtime/parainherent.md | 13 +- .../implementers-guide/src/types/disputes.md | 17 ++ runtime/kusama/src/lib.rs | 8 +- runtime/parachains/src/inclusion.rs | 183 ++++++++--- runtime/parachains/src/paras_inherent.rs | 23 +- runtime/parachains/src/runtime_api_impl/v1.rs | 12 +- runtime/polkadot/src/lib.rs | 8 +- runtime/rococo/src/lib.rs | 9 +- runtime/test-runtime/src/lib.rs | 8 +- runtime/westend/src/lib.rs | 8 +- scripts/gitlab/lingua.dic | 2 + statement-table/src/generic.rs | 6 +- 23 files changed, 606 insertions(+), 84 deletions(-) diff --git a/doc/shell-completion.md b/doc/shell-completion.md index 965a722308c3..986609392e34 100644 --- a/doc/shell-completion.md +++ b/doc/shell-completion.md @@ -10,6 +10,7 @@ source target/release/completion-scripts/polkadot.bash ``` You can find completion scripts for: + - bash - fish - zsh diff --git a/node/core/dispute-coordinator/src/real/mod.rs b/node/core/dispute-coordinator/src/real/mod.rs index b4a1437d7d55..e03fc2edd1e7 100644 --- a/node/core/dispute-coordinator/src/real/mod.rs +++ b/node/core/dispute-coordinator/src/real/mod.rs @@ -31,6 +31,9 @@ use std::{ time::{SystemTime, UNIX_EPOCH}, }; +use futures::{channel::oneshot, prelude::*}; +use kvdb::KeyValueDB; +use parity_scale_codec::{Decode, Encode, Error as CodecError}; use polkadot_node_primitives::{ CandidateVotes, DisputeMessage, DisputeMessageCheckError, SignedDisputeStatement, DISPUTE_WINDOW, @@ -39,7 +42,7 @@ use polkadot_node_subsystem::{ errors::{ChainApiError, RuntimeApiError}, messages::{ BlockDescription, DisputeCoordinatorMessage, DisputeDistributionMessage, - DisputeParticipationMessage, ImportStatementsResult, + DisputeParticipationMessage, ImportStatementsResult, RuntimeApiMessage, RuntimeApiRequest, }, overseer, FromOverseer, OverseerSignal, SpawnedSubsystem, SubsystemContext, SubsystemError, }; @@ -47,13 +50,10 @@ use polkadot_node_subsystem_util::rolling_session_window::{ RollingSessionWindow, SessionWindowUpdate, }; use polkadot_primitives::v1::{ - BlockNumber, CandidateHash, CandidateReceipt, DisputeStatement, Hash, SessionIndex, - SessionInfo, ValidatorId, ValidatorIndex, ValidatorPair, ValidatorSignature, + BlockNumber, CandidateHash, CandidateReceipt, CompactStatement, DisputeStatement, + DisputeStatementSet, Hash, ScrapedOnChainVotes, SessionIndex, SessionInfo, + ValidDisputeStatementKind, ValidatorId, ValidatorIndex, ValidatorPair, ValidatorSignature, }; - -use futures::{channel::oneshot, prelude::*}; -use kvdb::KeyValueDB; -use parity_scale_codec::{Decode, Encode, Error as CodecError}; use sc_keystore::LocalKeystore; use crate::metrics::Metrics; @@ -348,6 +348,8 @@ where &mut overlay_db, &mut state, update.activated.into_iter().map(|a| a.hash), + clock.now(), + &metrics, ) .await?; if !state.recovery_state.complete() { @@ -476,6 +478,8 @@ async fn handle_new_activations( overlay_db: &mut OverlayedBackend<'_, impl Backend>, state: &mut State, new_activations: impl IntoIterator, + now: u64, + metrics: &Metrics, ) -> Result<(), Error> { for new_leaf in new_activations { match state.rolling_session_window.cache_session_info_for_head(ctx, new_leaf).await { @@ -485,7 +489,6 @@ async fn handle_new_activations( err = ?e, "Failed to update session cache for disputes", ); - continue }, Ok(SessionWindowUpdate::Initialized { window_end, .. }) | @@ -499,10 +502,217 @@ async fn handle_new_activations( db::v1::note_current_session(overlay_db, session)?; } }, - _ => {}, + Ok(SessionWindowUpdate::Unchanged) => {}, + }; + scrape_on_chain_votes(ctx, overlay_db, state, new_leaf, now, metrics).await?; + } + + Ok(()) +} + +/// Scrapes on-chain votes (backing votes and concluded disputes) for a active leaf of the relay chain. +async fn scrape_on_chain_votes( + ctx: &mut (impl SubsystemContext + + overseer::SubsystemContext), + overlay_db: &mut OverlayedBackend<'_, impl Backend>, + state: &mut State, + new_leaf: Hash, + now: u64, + metrics: &Metrics, +) -> Result<(), Error> { + // obtain the concluded disputes as well as the candidate backing votes + // from the new leaf + let ScrapedOnChainVotes { session, backing_validators_per_candidate, disputes } = { + let (tx, rx) = oneshot::channel(); + ctx.send_message(RuntimeApiMessage::Request( + new_leaf, + RuntimeApiRequest::FetchOnChainVotes(tx), + )) + .await; + match rx.await { + Ok(Ok(Some(val))) => val, + Ok(Ok(None)) => { + tracing::trace!( + target: LOG_TARGET, + relay_parent = ?new_leaf, + "No on chain votes stored for relay chain leaf"); + return Ok(()) + }, + Ok(Err(e)) => { + tracing::debug!( + target: LOG_TARGET, + relay_parent = ?new_leaf, + error = ?e, + "Could not retrieve on chain votes due to an API error"); + return Ok(()) + }, + Err(e) => { + tracing::debug!( + target: LOG_TARGET, + relay_parent = ?new_leaf, + error = ?e, + "Could not retrieve onchain votes due to oneshot cancellation"); + return Ok(()) + }, + } + }; + + if backing_validators_per_candidate.is_empty() && disputes.is_empty() { + return Ok(()) + } + + // Obtain the session info, for sake of `ValidatorId`s + // either from the rolling session window. + // Must be called _after_ `fn cache_session_info_for_head` + // which guarantees that the session info is available + // for the current session. + let session_info: SessionInfo = + if let Some(session_info) = state.rolling_session_window.session_info(session) { + session_info.clone() + } else { + tracing::warn!( + target: LOG_TARGET, + relay_parent = ?new_leaf, + "Could not retrieve session info from rolling session window"); + return Ok(()) + }; + + // Scraped on-chain backing votes for the candidates with + // the new active leaf as if we received them via gossip. + for (candidate_receipt, backers) in backing_validators_per_candidate { + let candidate_hash = candidate_receipt.hash(); + let statements = backers.into_iter().filter_map(|(validator_index, attestation)| { + let validator_public: ValidatorId = session_info + .validators + .get(validator_index.0 as usize) + .or_else(|| { + tracing::error!( + target: LOG_TARGET, + relay_parent = ?new_leaf, + "Missing public key for validator {:?}", + &validator_index); + None + }) + .cloned()?; + let validator_signature = attestation.signature().clone(); + let valid_statement_kind = match attestation.to_compact_statement(candidate_hash) { + CompactStatement::Seconded(_) => + ValidDisputeStatementKind::BackingSeconded(new_leaf), + CompactStatement::Valid(_) => ValidDisputeStatementKind::BackingValid(new_leaf), + }; + let signed_dispute_statement = + SignedDisputeStatement::new_unchecked_from_trusted_source( + DisputeStatement::Valid(valid_statement_kind), + candidate_hash, + session, + validator_public, + validator_signature, + ); + Some((signed_dispute_statement, validator_index)) + }); + let import_result = handle_import_statements( + ctx, + overlay_db, + state, + candidate_hash, + MaybeCandidateReceipt::Provides(candidate_receipt), + session, + statements, + now, + metrics, + ) + .await?; + match import_result { + ImportStatementsResult::ValidImport => tracing::trace!(target: LOG_TARGET, + relay_parent = ?new_leaf, + ?session, + "Imported backing vote from on-chain"), + ImportStatementsResult::InvalidImport => tracing::warn!(target: LOG_TARGET, + relay_parent = ?new_leaf, + ?session, + "Attempted import of on-chain backing votes failed"), } } + if disputes.is_empty() { + return Ok(()) + } + + // Import concluded disputes from on-chain, this already went through a vote so it's assumed + // as verified. This will only be stored, gossiping it is not necessary. + + // First try to obtain all the backings which ultimately contain the candidate + // receipt which we need. + + for DisputeStatementSet { candidate_hash, session, statements } in disputes { + let statements = statements + .into_iter() + .filter_map(|(dispute_statement, validator_index, validator_signature)| { + let session_info: SessionInfo = if let Some(session_info) = + state.rolling_session_window.session_info(session) + { + session_info.clone() + } else { + tracing::warn!( + target: LOG_TARGET, + relay_parent = ?new_leaf, + ?session, + "Could not retrieve session info from rolling session window for recently concluded dispute"); + return None + }; + + let validator_public: ValidatorId = session_info + .validators + .get(validator_index.0 as usize) + .or_else(|| { + tracing::error!( + target: LOG_TARGET, + relay_parent = ?new_leaf, + ?session, + "Missing public key for validator {:?} that participated in concluded dispute", + &validator_index); + None + }) + .cloned()?; + + Some(( + SignedDisputeStatement::new_unchecked_from_trusted_source( + dispute_statement, + candidate_hash, + session, + validator_public, + validator_signature, + ), + validator_index, + )) + }) + .collect::>(); + let import_result = handle_import_statements( + ctx, + overlay_db, + state, + candidate_hash, + // TODO + MaybeCandidateReceipt::AssumeBackingVotePresent, + session, + statements, + now, + metrics, + ) + .await?; + match import_result { + ImportStatementsResult::ValidImport => tracing::trace!(target: LOG_TARGET, + relay_parent = ?new_leaf, + ?candidate_hash, + ?session, + "Imported statement of conlcuded dispute from on-chain"), + ImportStatementsResult::InvalidImport => tracing::warn!(target: LOG_TARGET, + relay_parent = ?new_leaf, + ?candidate_hash, + ?session, + "Attempted import of on-chain statement of concluded dispute failed"), + } + } Ok(()) } @@ -527,7 +737,7 @@ async fn handle_incoming( overlay_db, state, candidate_hash, - candidate_receipt, + MaybeCandidateReceipt::Provides(candidate_receipt), session, statements, now, @@ -622,14 +832,22 @@ fn insert_into_statement_vec( vec.insert(pos, (tag, val_index, val_signature)); } +#[derive(Debug, Clone)] +enum MaybeCandidateReceipt { + /// Directly provides the candiate receipt. + Provides(CandidateReceipt), + /// Assumes it was seen before by means of seconded message. + AssumeBackingVotePresent, +} + async fn handle_import_statements( ctx: &mut impl SubsystemContext, overlay_db: &mut OverlayedBackend<'_, impl Backend>, state: &mut State, candidate_hash: CandidateHash, - candidate_receipt: CandidateReceipt, + candidate_receipt: MaybeCandidateReceipt, session: SessionIndex, - statements: Vec<(SignedDisputeStatement, ValidatorIndex)>, + statements: impl IntoIterator, now: Timestamp, metrics: &Metrics, ) -> Result { @@ -638,7 +856,7 @@ async fn handle_import_statements( return Ok(ImportStatementsResult::InvalidImport) } - let validators = match state.rolling_session_window.session_info(session) { + let session_info = match state.rolling_session_window.session_info(session) { None => { tracing::warn!( target: LOG_TARGET, @@ -648,21 +866,40 @@ async fn handle_import_statements( return Ok(ImportStatementsResult::InvalidImport) }, - Some(info) => info.validators.clone(), + Some(info) => info, }; + let validators = session_info.validators.clone(); let n_validators = validators.len(); let supermajority_threshold = polkadot_primitives::v1::supermajority_threshold(n_validators); - let mut votes = overlay_db + // In case we are not provided with a candidate receipt + // we operate under the assumption, that a previous vote + // which included a `CandidateReceipt` was seen. + // This holds since every block is preceeded by the `Backing`-phase. + // + // There is one exception: A sufficiently sophisticated attacker could prevent + // us from seeing the backing votes by witholding arbitrary blocks, and hence we do + // not have a `CandidateReceipt` available. + let mut votes = match overlay_db .load_candidate_votes(session, &candidate_hash)? .map(CandidateVotes::from) - .unwrap_or_else(|| CandidateVotes { - candidate_receipt: candidate_receipt.clone(), - valid: Vec::new(), - invalid: Vec::new(), - }); + { + Some(votes) => votes, + None => + if let MaybeCandidateReceipt::Provides(candidate_receipt) = candidate_receipt { + CandidateVotes { candidate_receipt, valid: Vec::new(), invalid: Vec::new() } + } else { + tracing::warn!( + target: LOG_TARGET, + session, + "Missing info for session which has an active dispute", + ); + return Ok(ImportStatementsResult::InvalidImport) + }, + }; + let candidate_receipt = votes.candidate_receipt.clone(); // Update candidate votes. for (statement, val_index) in statements { @@ -906,7 +1143,7 @@ async fn issue_local_statement( overlay_db, state, candidate_hash, - candidate_receipt, + MaybeCandidateReceipt::Provides(candidate_receipt), session, statements, now, @@ -927,7 +1164,7 @@ async fn issue_local_statement( target: LOG_TARGET, ?candidate_hash, ?session, - "handle_import_statements` considers our own votes invalid!" + "`handle_import_statements` considers our own votes invalid!" ); }, Ok(ImportStatementsResult::ValidImport) => { @@ -935,7 +1172,7 @@ async fn issue_local_statement( target: LOG_TARGET, ?candidate_hash, ?session, - "handle_import_statements` successfully imported our vote!" + "`handle_import_statements` successfully imported our vote!" ); }, } diff --git a/node/core/dispute-coordinator/src/real/tests.rs b/node/core/dispute-coordinator/src/real/tests.rs index c8709da92916..147fcede8a58 100644 --- a/node/core/dispute-coordinator/src/real/tests.rs +++ b/node/core/dispute-coordinator/src/real/tests.rs @@ -203,6 +203,17 @@ impl TestState { } ) } + + assert_matches!( + virtual_overseer.recv().await, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + _new_leaf, + RuntimeApiRequest::FetchOnChainVotes(tx), + )) => { + // add some `BackedCandidates` or resolved disputes here as needed + tx.send(Ok(Some(ScrapedOnChainVotes::default()))).unwrap(); + } + ) } async fn handle_resume_sync( diff --git a/node/core/runtime-api/src/cache.rs b/node/core/runtime-api/src/cache.rs index 962959a23841..9c3aeaa8d819 100644 --- a/node/core/runtime-api/src/cache.rs +++ b/node/core/runtime-api/src/cache.rs @@ -24,7 +24,8 @@ use polkadot_primitives::v1::{ AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, - SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, + ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, + ValidatorId, ValidatorIndex, }; const AUTHORITIES_CACHE_SIZE: usize = 128 * 1024; @@ -41,6 +42,7 @@ const SESSION_INFO_CACHE_SIZE: usize = 64 * 1024; const DMQ_CONTENTS_CACHE_SIZE: usize = 64 * 1024; const INBOUND_HRMP_CHANNELS_CACHE_SIZE: usize = 64 * 1024; const CURRENT_BABE_EPOCH_CACHE_SIZE: usize = 64 * 1024; +const ON_CHAIN_VOTES_CACHE_SIZE: usize = 3 * 1024; struct ResidentSizeOf(T); @@ -98,6 +100,7 @@ pub(crate) struct RequestResultCache { ResidentSizeOf>>>, >, current_babe_epoch: MemoryLruCache>, + on_chain_votes: MemoryLruCache>>, } impl Default for RequestResultCache { @@ -120,6 +123,7 @@ impl Default for RequestResultCache { dmq_contents: MemoryLruCache::new(DMQ_CONTENTS_CACHE_SIZE), inbound_hrmp_channels_contents: MemoryLruCache::new(INBOUND_HRMP_CHANNELS_CACHE_SIZE), current_babe_epoch: MemoryLruCache::new(CURRENT_BABE_EPOCH_CACHE_SIZE), + on_chain_votes: MemoryLruCache::new(ON_CHAIN_VOTES_CACHE_SIZE), } } } @@ -320,6 +324,21 @@ impl RequestResultCache { pub(crate) fn cache_current_babe_epoch(&mut self, relay_parent: Hash, epoch: Epoch) { self.current_babe_epoch.insert(relay_parent, DoesNotAllocate(epoch)); } + + pub(crate) fn on_chain_votes( + &mut self, + relay_parent: &Hash, + ) -> Option<&Option> { + self.on_chain_votes.get(relay_parent).map(|v| &v.0) + } + + pub(crate) fn cache_on_chain_votes( + &mut self, + relay_parent: Hash, + scraped: Option, + ) { + self.on_chain_votes.insert(relay_parent, ResidentSizeOf(scraped)); + } } pub(crate) enum RequestResult { @@ -342,4 +361,5 @@ pub(crate) enum RequestResult { BTreeMap>>, ), CurrentBabeEpoch(Hash, Epoch), + FetchOnChainVotes(Hash, Option), } diff --git a/node/core/runtime-api/src/lib.rs b/node/core/runtime-api/src/lib.rs index cf2ec719c8e5..a24192c2c54f 100644 --- a/node/core/runtime-api/src/lib.rs +++ b/node/core/runtime-api/src/lib.rs @@ -143,6 +143,8 @@ where .cache_inbound_hrmp_channel_contents((relay_parent, para_id), contents), CurrentBabeEpoch(relay_parent, epoch) => self.requests_cache.cache_current_babe_epoch(relay_parent, epoch), + FetchOnChainVotes(relay_parent, scraped) => + self.requests_cache.cache_on_chain_votes(relay_parent, scraped), } } @@ -209,6 +211,8 @@ where .map(|sender| Request::InboundHrmpChannelsContents(id, sender)), Request::CurrentBabeEpoch(sender) => query!(current_babe_epoch(), sender).map(|sender| Request::CurrentBabeEpoch(sender)), + Request::FetchOnChainVotes(sender) => + query!(on_chain_votes(), sender).map(|sender| Request::FetchOnChainVotes(sender)), } } @@ -342,6 +346,7 @@ where Request::InboundHrmpChannelsContents(id, sender) => query!(InboundHrmpChannelsContents, inbound_hrmp_channels_contents(id), sender), Request::CurrentBabeEpoch(sender) => query!(CurrentBabeEpoch, current_epoch(), sender), + Request::FetchOnChainVotes(sender) => query!(FetchOnChainVotes, on_chain_votes(), sender), } } diff --git a/node/core/runtime-api/src/tests.rs b/node/core/runtime-api/src/tests.rs index 18906e196b82..983aa7d89e20 100644 --- a/node/core/runtime-api/src/tests.rs +++ b/node/core/runtime-api/src/tests.rs @@ -22,8 +22,8 @@ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::v1::{ AuthorityDiscoveryId, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, - PersistedValidationData, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, - ValidatorId, ValidatorIndex, + PersistedValidationData, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, + ValidationCodeHash, ValidatorId, ValidatorIndex, }; use sp_core::testing::TaskExecutor; use std::{ @@ -49,6 +49,7 @@ struct MockRuntimeApi { dmq_contents: HashMap>, hrmp_channels: HashMap>>, babe_epoch: Option, + on_chain_votes: Option, } impl ProvideRuntimeApi for MockRuntimeApi { @@ -149,6 +150,10 @@ sp_api::mock_impl_runtime_apis! { ) -> Option { self.validation_code_by_hash.get(&hash).map(|c| c.clone()) } + + fn on_chain_votes(&self) -> Option { + self.on_chain_votes.clone() + } } impl BabeApi for MockRuntimeApi { diff --git a/node/primitives/src/disputes/mod.rs b/node/primitives/src/disputes/mod.rs index a787fdd4dd0a..fe5df710c76c 100644 --- a/node/primitives/src/disputes/mod.rs +++ b/node/primitives/src/disputes/mod.rs @@ -66,6 +66,26 @@ impl CandidateVotes { } impl SignedDisputeStatement { + /// Create a new `SignedDisputeStatement` from information + /// that is available on-chain, and hence already can be trusted. + /// + /// Attention: Not to be used other than with guaranteed fetches. + pub fn new_unchecked_from_trusted_source( + dispute_statement: DisputeStatement, + candidate_hash: CandidateHash, + session_index: SessionIndex, + validator_public: ValidatorId, + validator_signature: ValidatorSignature, + ) -> Self { + SignedDisputeStatement { + dispute_statement, + candidate_hash, + validator_public, + validator_signature, + session_index, + } + } + /// Create a new `SignedDisputeStatement`, which is only possible by checking the signature. pub fn new_checked( dispute_statement: DisputeStatement, diff --git a/node/subsystem-types/src/messages.rs b/node/subsystem-types/src/messages.rs index ea70d3b2707a..f9c739f9dbdf 100644 --- a/node/subsystem-types/src/messages.rs +++ b/node/subsystem-types/src/messages.rs @@ -643,6 +643,8 @@ pub enum RuntimeApiRequest { ), /// Get information about the BABE epoch the block was included in. CurrentBabeEpoch(RuntimeApiSender), + /// Get all disputes in relation to a relay parent. + FetchOnChainVotes(RuntimeApiSender>), } /// A message to the Runtime API subsystem. diff --git a/primitives/src/v0.rs b/primitives/src/v0.rs index 7e15a0b87308..2000c173b879 100644 --- a/primitives/src/v0.rs +++ b/primitives/src/v0.rs @@ -735,6 +735,7 @@ impl CompactStatement { /// An either implicit or explicit attestation to the validity of a parachain /// candidate. #[derive(Clone, Eq, PartialEq, Decode, Encode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(MallocSizeOf))] pub enum ValidityAttestation { /// Implicit validity attestation by issuing. /// This corresponds to issuance of a `Candidate` statement. @@ -747,6 +748,18 @@ pub enum ValidityAttestation { } impl ValidityAttestation { + /// Produce the underlying signed payload of the attestation, given the hash of the candidate, + /// which should be known in context. + pub fn to_compact_statement(&self, candidate_hash: CandidateHash) -> CompactStatement { + // Explicit and implicit map directly from + // `ValidityVote::Valid` and `ValidityVote::Issued`, and hence there is a + // `1:1` relationshow which enables the conversion. + match *self { + ValidityAttestation::Implicit(_) => CompactStatement::Seconded(candidate_hash), + ValidityAttestation::Explicit(_) => CompactStatement::Valid(candidate_hash), + } + } + /// Get a reference to the signature. pub fn signature(&self) -> &ValidatorSignature { match *self { diff --git a/primitives/src/v1/mod.rs b/primitives/src/v1/mod.rs index 40ca368fc6e9..cbea6480efef 100644 --- a/primitives/src/v1/mod.rs +++ b/primitives/src/v1/mod.rs @@ -945,6 +945,22 @@ pub struct SessionInfo { pub needed_approvals: u32, } +/// Scraped runtime backing votes and resolved disputes. +#[derive(Clone, Encode, Decode, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(PartialEq, Default, MallocSizeOf))] +pub struct ScrapedOnChainVotes { + /// The session in which the block was included. + pub session: SessionIndex, + /// Set of backing validators for each candidate, represented by its candidate + /// receipt. + pub backing_validators_per_candidate: + Vec<(CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>)>, + /// On-chain-recorded set of disputes. + /// Note that the above `backing_validators` are + /// unrelated to the backers of the disputes candidates. + pub disputes: MultiDisputeStatementSet, +} + /// A vote of approval on a candidate. #[derive(Clone, RuntimeDebug)] pub struct ApprovalVote(pub CandidateHash); @@ -960,7 +976,7 @@ impl ApprovalVote { sp_api::decl_runtime_apis! { /// The API for querying the state of parachains on-chain. - pub trait ParachainHost { + pub trait ParachainHost { /// Get the current validators. fn validators() -> Vec; @@ -1017,6 +1033,9 @@ sp_api::decl_runtime_apis! { /// Get the validation code from its hash. fn validation_code_by_hash(hash: ValidationCodeHash) -> Option; + + /// Scrape dispute relevant from on-chain, backing votes and resolved disputes. + fn on_chain_votes() -> Option>; } } @@ -1182,6 +1201,7 @@ impl From for runtime_primitives::DigestItem { /// /// Statements are either in favor of the candidate's validity or against it. #[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(MallocSizeOf))] pub enum DisputeStatement { /// A valid statement, of the given kind. #[codec(index = 0)] @@ -1251,6 +1271,7 @@ impl DisputeStatement { /// Different kinds of statements of validity on a candidate. #[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(MallocSizeOf))] pub enum ValidDisputeStatementKind { /// An explicit statement issued as part of a dispute. #[codec(index = 0)] @@ -1268,6 +1289,7 @@ pub enum ValidDisputeStatementKind { /// Different kinds of statements of invalidity on a candidate. #[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(MallocSizeOf))] pub enum InvalidDisputeStatementKind { /// An explicit statement issued as part of a dispute. #[codec(index = 0)] @@ -1296,6 +1318,7 @@ impl ExplicitDisputeStatement { /// A set of statements about a specific candidate. #[derive(Encode, Decode, Clone, PartialEq, RuntimeDebug, TypeInfo)] +#[cfg_attr(feature = "std", derive(MallocSizeOf))] pub struct DisputeStatementSet { /// The candidate referenced by this set. pub candidate_hash: CandidateHash, diff --git a/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md b/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md index 2b4f936f1b6e..c8bf0153f494 100644 --- a/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md +++ b/roadmap/implementers-guide/src/node/disputes/dispute-coordinator.md @@ -89,6 +89,8 @@ For each leaf in the leaves update: * Use `iter_with_prefix` to remove everything from `"earliest-session"` up to `state.highest_session - DISPUTE_WINDOW` from the DB under `"candidate-votes"`. * Update `"earliest-session"` to be equal to `state.highest_session - DISPUTE_WINDOW`. * For each new block, explicitly or implicitly, under the new leaf, scan for a dispute digest which indicates a rollback. If a rollback is detected, use the `ChainApi` subsystem to blacklist the chain. +* For each new block, use the `RuntimeApi` to obtain a `ScrapedOnChainVotes` and handle them as if they were provided by means of a incoming `DisputeCoordinatorMessage::ImportStatement` message. + * In the case of a concluded dispute, there are some cases that do not guarantee the presence of a `CandidateReceipt`, where handling has to be defered . ### On `OverseerSignal::Conclude` diff --git a/roadmap/implementers-guide/src/runtime/parainherent.md b/roadmap/implementers-guide/src/runtime/parainherent.md index f9aacc2c3578..cc5e209362e9 100644 --- a/roadmap/implementers-guide/src/runtime/parainherent.md +++ b/roadmap/implementers-guide/src/runtime/parainherent.md @@ -5,6 +5,7 @@ This module is responsible for providing all data given to the runtime by the bl This module does not have the same initialization/finalization concerns as the others, as it only requires that entry points be triggered after all modules have initialized and that finalization happens after entry points are triggered. Both of these are assumptions we have already made about the runtime's order of operations, so this module doesn't need to be initialized or finalized by the `Initializer`. There are a couple of important notes to the operations in this inherent as they relate to disputes. + 1. We don't accept bitfields or backed candidates if in "governance-only" mode from having a local dispute conclude on this fork. 1. When disputes are initiated, we remove the block from pending availability. This allows us to roll back chains to the block before blocks are included as opposed to backing. It's important to do this before processing bitfields. 1. `Inclusion::collect_disputed` is kind of expensive so it's important to gate this on whether there are actually any new disputes. Which should be never. @@ -13,9 +14,15 @@ There are a couple of important notes to the operations in this inherent as they ## Storage ```rust +/// Whether the para inherent was included or not. Included: Option<()>, ``` +```rust +/// Scraped on chain votes to be used in disputes off-chain. +OnChainVotes: Option, +``` + ## Finalization 1. Take (get and clear) the value of `Included`. If it is not `Some`, throw an unrecoverable error. @@ -27,7 +34,7 @@ Included: Option<()>, 1. Invoke `Disputes::provide_multi_dispute_data`. 1. If `Disputes::is_frozen`, return and set `Included` to `Some(())`. 1. If there are any concluded disputes from the current session, invoke `Inclusion::collect_disputed` with the disputed candidates. Annotate each returned core with `FreedReason::Concluded`, sort them, and invoke `Scheduler::free_cores` with them. - 1. The `Bitfields` are first forwarded to the `Inclusion::process_bitfields` routine, returning a set of freed cores. Provide the number of availability cores (`Scheduler::availability_cores().len()`) as the expected number of bits and a `Scheduler::core_para` as a core-lookup to the `process_bitfields` routine. Annotate each of these freed cores with `FreedReason::Concluded`. + 1. The `Bitfields` are first forwarded to the `Inclusion::process_bitfields` routine, returning a set included candidates and the respective freed cores. Provide the number of availability cores (`Scheduler::availability_cores().len()`) as the expected number of bits and a `Scheduler::core_para` as a core-lookup to the `process_bitfields` routine. Annotate each of these freed cores with `FreedReason::Concluded`. 1. For each freed candidate from the `Inclusion::process_bitfields` call, invoke `Disputes::note_included(current_session, candidate)`. 1. If `Scheduler::availability_timeout_predicate` is `Some`, invoke `Inclusion::collect_pending` using it and annotate each of those freed cores with `FreedReason::TimedOut`. 1. Combine and sort the the bitfield-freed cores and the timed-out cores. @@ -36,6 +43,8 @@ Included: Option<()>, 1. Extract `parent_storage_root` from the parent header, 1. If `Disputes::concluded_invalid(current_session, candidate)` is true for any of the `backed_candidates`, fail. 1. Invoke the `Inclusion::process_candidates` routine with the parameters `(parent_storage_root, backed_candidates, Scheduler::scheduled(), Scheduler::group_validators)`. - 1. Call `Scheduler::occupied` using the return value of the `Inclusion::process_candidates` call above, first sorting the list of assigned core indices. + 1. Deconstruct the returned `ProcessedCandidates` value into `occupied` core indices, and backing validators by candidate `backing_validators_per_candidate` represented by `Vec<(CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>)>`. + 1. Set `OnChainVotes` to `ScrapedOnChainVotes`, based on the `current_session`, concluded `disputes`, and `backing_validators_per_candidate`. + 1. Call `Scheduler::occupied` using the `occupied` core indices of the returned above, first sorting the list of assigned core indices. 1. Call the `Ump::process_pending_upward_messages` routine to execute all messages in upward dispatch queues. 1. If all of the above succeeds, set `Included` to `Some(())`. diff --git a/roadmap/implementers-guide/src/types/disputes.md b/roadmap/implementers-guide/src/types/disputes.md index 3043b7615abd..24f152b1308f 100644 --- a/roadmap/implementers-guide/src/types/disputes.md +++ b/roadmap/implementers-guide/src/types/disputes.md @@ -71,3 +71,20 @@ struct DisputeState { concluded_at: Option, } ``` + +## `ScrapedOnChainVotes` + +```rust +/// Type for transcending recorded on-chain +/// dispute relevant votes and conclusions to +/// the off-chain `DisputesCoordinator`. +struct ScrapedOnChainVotes { + /// The session index at which the block was included. + session: SessionIndex, + /// The backing and seconding validity attestations for all candidates, provigind the full candidate receipt. + backing_validators_per_candidate: Vec<(CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>)> + /// Set of concluded disputes that were recorded + /// on chain within the inherent. + disputes: MultiDisputeStatementSet, +} +``` diff --git a/runtime/kusama/src/lib.rs b/runtime/kusama/src/lib.rs index f8b296bf6695..95381b2b561c 100644 --- a/runtime/kusama/src/lib.rs +++ b/runtime/kusama/src/lib.rs @@ -25,8 +25,8 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::v1::{ AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, - Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, SessionInfo, Signature, - ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, + Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, + SessionInfo, Signature, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, }; use runtime_common::{ auctions, claims, crowdloan, impls::DealWithFees, paras_registrar, slots, xcm_sender, @@ -1862,6 +1862,10 @@ sp_api::impl_runtime_apis! { fn validation_code_by_hash(hash: ValidationCodeHash) -> Option { parachains_runtime_api_impl::validation_code_by_hash::(hash) } + + fn on_chain_votes() -> Option> { + parachains_runtime_api_impl::on_chain_votes::() + } } impl beefy_primitives::BeefyApi for Runtime { diff --git a/runtime/parachains/src/inclusion.rs b/runtime/parachains/src/inclusion.rs index 7150dbbfb798..dd865bc8572b 100644 --- a/runtime/parachains/src/inclusion.rs +++ b/runtime/parachains/src/inclusion.rs @@ -25,8 +25,9 @@ use frame_support::pallet_prelude::*; use parity_scale_codec::{Decode, Encode}; use primitives::v1::{ AvailabilityBitfield, BackedCandidate, CandidateCommitments, CandidateDescriptor, - CandidateHash, CandidateReceipt, CommittedCandidateReceipt, CoreIndex, GroupIndex, HeadData, - Id as ParaId, SigningContext, UncheckedSignedAvailabilityBitfields, ValidatorIndex, + CandidateHash, CandidateReceipt, CommittedCandidateReceipt, CoreIndex, GroupIndex, Hash, + HeadData, Id as ParaId, SigningContext, UncheckedSignedAvailabilityBitfields, ValidatorIndex, + ValidityAttestation, }; use scale_info::TypeInfo; use sp_runtime::{ @@ -110,6 +111,24 @@ pub trait RewardValidators { fn reward_bitfields(validators: impl IntoIterator); } +/// Helper return type for `process_candidates`. +#[derive(Encode, Decode, PartialEq, TypeInfo)] +#[cfg_attr(test, derive(Debug))] +pub(crate) struct ProcessedCandidates { + pub(crate) core_indices: Vec, + pub(crate) candidate_receipt_with_backing_validator_indices: + Vec<(CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>)>, +} + +impl Default for ProcessedCandidates { + fn default() -> Self { + Self { + core_indices: Vec::new(), + candidate_receipt_with_backing_validator_indices: Vec::new(), + } + } +} + #[frame_support::pallet] pub mod pallet { use super::*; @@ -392,11 +411,11 @@ impl Pallet { candidates: Vec>, scheduled: Vec, group_validators: impl Fn(GroupIndex) -> Option>, - ) -> Result, DispatchError> { + ) -> Result, DispatchError> { ensure!(candidates.len() <= scheduled.len(), Error::::UnscheduledCandidate); if scheduled.is_empty() { - return Ok(Vec::new()) + return Ok(ProcessedCandidates::default()) } let validators = shared::Pallet::::active_validator_keys(); @@ -408,7 +427,11 @@ impl Pallet { let relay_parent_number = now - One::one(); let check_cx = CandidateCheckContext::::new(now, relay_parent_number); - // do all checks before writing storage. + // Collect candidate receipts with backers. + let mut candidate_receipt_with_backing_validator_indices = + Vec::with_capacity(candidates.len()); + + // Do all checks before writing storage. let core_indices_and_backers = { let mut skip = 0; let mut core_indices_and_backers = Vec::with_capacity(candidates.len()); @@ -437,17 +460,17 @@ impl Pallet { // // In the meantime, we do certain sanity checks on the candidates and on the scheduled // list. - 'a: for (candidate_idx, candidate) in candidates.iter().enumerate() { - let para_id = candidate.descriptor().para_id; + 'a: for (candidate_idx, backed_candidate) in candidates.iter().enumerate() { + let para_id = backed_candidate.descriptor().para_id; let mut backers = bitvec::bitvec![BitOrderLsb0, u8; 0; validators.len()]; // we require that the candidate is in the context of the parent block. ensure!( - candidate.descriptor().relay_parent == parent_hash, + backed_candidate.descriptor().relay_parent == parent_hash, Error::::CandidateNotInParentContext, ); ensure!( - candidate.descriptor().check_collator_signature().is_ok(), + backed_candidate.descriptor().check_collator_signature().is_ok(), Error::::NotCollatorSigned, ); @@ -456,24 +479,24 @@ impl Pallet { // A candidate for a parachain without current validation code is not scheduled. .ok_or_else(|| Error::::UnscheduledCandidate)?; ensure!( - candidate.descriptor().validation_code_hash == validation_code_hash, + backed_candidate.descriptor().validation_code_hash == validation_code_hash, Error::::InvalidValidationCodeHash, ); ensure!( - candidate.descriptor().para_head == - candidate.candidate.commitments.head_data.hash(), + backed_candidate.descriptor().para_head == + backed_candidate.candidate.commitments.head_data.hash(), Error::::ParaHeadMismatch, ); if let Err(err) = check_cx.check_validation_outputs( para_id, - &candidate.candidate.commitments.head_data, - &candidate.candidate.commitments.new_validation_code, - candidate.candidate.commitments.processed_downward_messages, - &candidate.candidate.commitments.upward_messages, - T::BlockNumber::from(candidate.candidate.commitments.hrmp_watermark), - &candidate.candidate.commitments.horizontal_messages, + &backed_candidate.candidate.commitments.head_data, + &backed_candidate.candidate.commitments.new_validation_code, + backed_candidate.candidate.commitments.processed_downward_messages, + &backed_candidate.candidate.commitments.upward_messages, + T::BlockNumber::from(backed_candidate.candidate.commitments.hrmp_watermark), + &backed_candidate.candidate.commitments.horizontal_messages, ) { log::debug!( target: LOG_TARGET, @@ -491,7 +514,7 @@ impl Pallet { if para_id == assignment.para_id { if let Some(required_collator) = assignment.required_collator() { ensure!( - required_collator == &candidate.descriptor().collator, + required_collator == &backed_candidate.descriptor().collator, Error::::WrongCollator, ); } @@ -509,14 +532,15 @@ impl Pallet { // We don't want to error out here because it will // brick the relay-chain. So we return early without // doing anything. - return Ok(Vec::new()) + return Ok(ProcessedCandidates::default()) }, }; let expected = persisted_validation_data.hash(); ensure!( - expected == candidate.descriptor().persisted_validation_data_hash, + expected == + backed_candidate.descriptor().persisted_validation_data_hash, Error::::ValidationDataHashMismatch, ); } @@ -536,7 +560,7 @@ impl Pallet { // check the signatures in the backing and that it is a majority. { let maybe_amount_validated = primitives::v1::check_candidate_backing( - &candidate, + &backed_candidate, &signing_context, group_vals.len(), |idx| { @@ -557,17 +581,28 @@ impl Pallet { }, } - for (bit_idx, _) in candidate + let mut backer_idx_and_attestation = + Vec::<(ValidatorIndex, ValidityAttestation)>::with_capacity( + backed_candidate.validator_indices.count_ones(), + ); + let candidate_receipt = backed_candidate.receipt(); + + for ((bit_idx, _), attestation) in backed_candidate .validator_indices .iter() .enumerate() .filter(|(_, signed)| **signed) + .zip(backed_candidate.validity_votes.iter().cloned()) { - let val_idx = - group_vals.get(bit_idx).expect("this query done above; qed"); + let val_idx = group_vals + .get(bit_idx) + .expect("this query succeeded above; qed"); + backer_idx_and_attestation.push((*val_idx, attestation)); backers.set(val_idx.0 as _, true); } + candidate_receipt_with_backing_validator_indices + .push((candidate_receipt, backer_idx_and_attestation)); } core_indices_and_backers.push(( @@ -625,7 +660,7 @@ impl Pallet { descriptor, availability_votes, relay_parent_number, - backers, + backers: backers.to_bitvec(), backed_in_number: check_cx.now, backing_group: group, }, @@ -633,7 +668,10 @@ impl Pallet { >::insert(¶_id, commitments); } - Ok(core_indices) + Ok(ProcessedCandidates:: { + core_indices, + candidate_receipt_with_backing_validator_indices, + }) } /// Run the acceptance criteria checks on the given candidate commitments. @@ -2386,9 +2424,32 @@ mod tests { BackingKind::Threshold, )); - let occupied_cores = ParaInclusion::process_candidates( + let backed_candidates = vec![backed_a, backed_b, backed_c]; + let get_backing_group_idx = { + // the order defines the group implicitly for this test case + let backed_candidates_with_groups = backed_candidates + .iter() + .enumerate() + .map(|(idx, backed_candidate)| (backed_candidate.hash(), GroupIndex(idx as _))) + .collect::>(); + + move |candidate_hash_x: CandidateHash| -> Option { + backed_candidates_with_groups.iter().find_map(|(candidate_hash, grp)| { + if *candidate_hash == candidate_hash_x { + Some(*grp) + } else { + None + } + }) + } + }; + + let ProcessedCandidates { + core_indices: occupied_cores, + candidate_receipt_with_backing_validator_indices, + } = ParaInclusion::process_candidates( Default::default(), - vec![backed_a, backed_b, backed_c], + backed_candidates.clone(), vec![ chain_a_assignment.clone(), chain_b_assignment.clone(), @@ -2403,6 +2464,55 @@ mod tests { vec![CoreIndex::from(0), CoreIndex::from(1), CoreIndex::from(2)] ); + // Transform the votes into the setup we expect + let expected = { + let mut intermediate = std::collections::HashMap::< + CandidateHash, + (CandidateReceipt, Vec<(ValidatorIndex, ValidityAttestation)>), + >::new(); + backed_candidates.into_iter().for_each(|backed_candidate| { + let candidate_receipt_with_backers = intermediate + .entry(backed_candidate.hash()) + .or_insert_with(|| (backed_candidate.receipt(), Vec::new())); + + assert_eq!( + backed_candidate.validity_votes.len(), + backed_candidate.validator_indices.count_ones() + ); + candidate_receipt_with_backers.1.extend( + backed_candidate + .validator_indices + .iter() + .enumerate() + .filter(|(_, signed)| **signed) + .zip(backed_candidate.validity_votes.iter().cloned()) + .filter_map(|((validator_index_within_group, _), attestation)| { + let grp_idx = + get_backing_group_idx(backed_candidate.hash()).unwrap(); + group_validators(grp_idx).map(|validator_indices| { + (validator_indices[validator_index_within_group], attestation) + }) + }), + ); + }); + intermediate.into_values().collect::>() + }; + + // sort, since we use a hashmap above + let assure_candidate_sorting = |mut candidate_receipts_with_backers: Vec<( + CandidateReceipt, + Vec<(ValidatorIndex, ValidityAttestation)>, + )>| { + candidate_receipts_with_backers.sort_by(|(cr1, _), (cr2, _)| { + cr1.descriptor().para_id.cmp(&cr2.descriptor().para_id) + }); + candidate_receipts_with_backers + }; + assert_eq!( + assure_candidate_sorting(expected), + assure_candidate_sorting(candidate_receipt_with_backing_validator_indices) + ); + assert_eq!( >::get(&chain_a), Some(CandidatePendingAvailability { @@ -2530,13 +2640,14 @@ mod tests { BackingKind::Threshold, )); - let occupied_cores = ParaInclusion::process_candidates( - Default::default(), - vec![backed_a], - vec![chain_a_assignment.clone()], - &group_validators, - ) - .expect("candidates scheduled, in order, and backed"); + let ProcessedCandidates { core_indices: occupied_cores, .. } = + ParaInclusion::process_candidates( + Default::default(), + vec![backed_a], + vec![chain_a_assignment.clone()], + &group_validators, + ) + .expect("candidates scheduled, in order, and backed"); assert_eq!(occupied_cores, vec![CoreIndex::from(0)]); diff --git a/runtime/parachains/src/paras_inherent.rs b/runtime/parachains/src/paras_inherent.rs index cbffb9ff7937..c866a077ccb2 100644 --- a/runtime/parachains/src/paras_inherent.rs +++ b/runtime/parachains/src/paras_inherent.rs @@ -33,7 +33,8 @@ use frame_support::{ }; use frame_system::pallet_prelude::*; use primitives::v1::{ - BackedCandidate, InherentData as ParachainsInherentData, PARACHAINS_INHERENT_IDENTIFIER, + BackedCandidate, InherentData as ParachainsInherentData, ScrapedOnChainVotes, + PARACHAINS_INHERENT_IDENTIFIER, }; use sp_runtime::traits::Header as HeaderT; use sp_std::prelude::*; @@ -79,6 +80,11 @@ pub mod pallet { #[pallet::storage] pub(crate) type Included = StorageValue<_, ()>; + /// Scraped on chain data for extracting resolved disputes as well as backing votes. + #[pallet::storage] + #[pallet::getter(fn on_chain_votes)] + pub(crate) type OnChainVotes = StorageValue<_, ScrapedOnChainVotes>; + #[pallet::hooks] impl Hooks> for Pallet { fn on_initialize(_: T::BlockNumber) -> Weight { @@ -180,7 +186,7 @@ pub mod pallet { .map(|s| (s.session, s.candidate_hash)) .collect(); - let _ = T::DisputesHandler::provide_multi_dispute_data(disputes)?; + let _ = T::DisputesHandler::provide_multi_dispute_data(disputes.clone())?; if T::DisputesHandler::is_frozen() { // The relay chain we are currently on is invalid. Proceed no further on parachains. Included::::set(Some(())); @@ -262,13 +268,24 @@ pub mod pallet { // Process backed candidates according to scheduled cores. let parent_storage_root = parent_header.state_root().clone(); - let occupied = >::process_candidates( + let inclusion::ProcessedCandidates::<::Hash> { + core_indices: occupied, + candidate_receipt_with_backing_validator_indices, + } = >::process_candidates( parent_storage_root, backed_candidates, >::scheduled(), >::group_validators, )?; + // The number of disputes included in a block is + // limited by the weight as well as the number of candidate blocks. + OnChainVotes::::put(ScrapedOnChainVotes::<::Hash> { + session: current_session, + backing_validators_per_candidate: candidate_receipt_with_backing_validator_indices, + disputes, + }); + // Note which of the scheduled cores were actually occupied by a backed candidate. >::occupied(&occupied); diff --git a/runtime/parachains/src/runtime_api_impl/v1.rs b/runtime/parachains/src/runtime_api_impl/v1.rs index 3100a324f3c5..5909ea55290b 100644 --- a/runtime/parachains/src/runtime_api_impl/v1.rs +++ b/runtime/parachains/src/runtime_api_impl/v1.rs @@ -18,14 +18,15 @@ //! functions. use crate::{ - configuration, dmp, hrmp, inclusion, initializer, paras, scheduler, session_info, shared, + configuration, dmp, hrmp, inclusion, initializer, paras, paras_inherent, scheduler, + session_info, shared, }; use primitives::v1::{ AuthorityDiscoveryId, CandidateEvent, CommittedCandidateReceipt, CoreIndex, CoreOccupied, CoreState, GroupIndex, GroupRotationInfo, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCore, OccupiedCoreAssumption, PersistedValidationData, - ScheduledCore, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, ValidatorId, - ValidatorIndex, + ScheduledCore, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, + ValidationCodeHash, ValidatorId, ValidatorIndex, }; use sp_runtime::traits::One; use sp_std::{collections::btree_map::BTreeMap, prelude::*}; @@ -329,3 +330,8 @@ pub fn validation_code_by_hash( ) -> Option { >::code_by_hash(hash) } + +/// Disputes imported via means of on-chain imports. +pub fn on_chain_votes() -> Option> { + >::on_chain_votes() +} diff --git a/runtime/polkadot/src/lib.rs b/runtime/polkadot/src/lib.rs index 20f8ac789bf9..0c4a7ac38fbc 100644 --- a/runtime/polkadot/src/lib.rs +++ b/runtime/polkadot/src/lib.rs @@ -54,8 +54,8 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::v1::{ AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, - Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, SessionInfo, Signature, - ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, + Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, + SessionInfo, Signature, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, }; use sp_core::{ u32_trait::{_1, _2, _3, _4, _5}, @@ -1694,6 +1694,10 @@ sp_api::impl_runtime_apis! { fn validation_code_by_hash(hash: ValidationCodeHash) -> Option { parachains_runtime_api_impl::validation_code_by_hash::(hash) } + + fn on_chain_votes() -> Option> { + parachains_runtime_api_impl::on_chain_votes::() + } } impl beefy_primitives::BeefyApi for Runtime { diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index 5825bca2bbd0..a7d1014f10c9 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -38,8 +38,9 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::v1::{ AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash, Id, InboundDownwardMessage, InboundHrmpMessage, Moment, - Nonce, OccupiedCoreAssumption, PersistedValidationData, SessionInfo as SessionInfoData, - Signature, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, + Nonce, OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, + SessionInfo as SessionInfoData, Signature, ValidationCode, ValidationCodeHash, ValidatorId, + ValidatorIndex, }; use runtime_common::{ auctions, crowdloan, impls::ToAuthor, paras_registrar, paras_sudo_wrapper, slots, xcm_sender, @@ -1285,6 +1286,10 @@ sp_api::impl_runtime_apis! { fn validation_code_by_hash(hash: ValidationCodeHash) -> Option { runtime_api_impl::validation_code_by_hash::(hash) } + + fn on_chain_votes() -> Option> { + runtime_api_impl::on_chain_votes::() + } } impl fg_primitives::GrandpaApi for Runtime { diff --git a/runtime/test-runtime/src/lib.rs b/runtime/test-runtime/src/lib.rs index fd4742136c13..bb1db523d33e 100644 --- a/runtime/test-runtime/src/lib.rs +++ b/runtime/test-runtime/src/lib.rs @@ -48,8 +48,8 @@ use primitives::v1::{ AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash as HashT, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, - SessionInfo as SessionInfoData, Signature, ValidationCode, ValidationCodeHash, ValidatorId, - ValidatorIndex, + ScrapedOnChainVotes, SessionInfo as SessionInfoData, Signature, ValidationCode, + ValidationCodeHash, ValidatorId, ValidatorIndex, }; use runtime_common::{ claims, paras_sudo_wrapper, BlockHashCount, BlockLength, BlockWeights, SlowAdjustingFeeUpdate, @@ -846,6 +846,10 @@ sp_api::impl_runtime_apis! { fn validation_code_by_hash(hash: ValidationCodeHash) -> Option { runtime_impl::validation_code_by_hash::(hash) } + + fn on_chain_votes() -> Option> { + runtime_impl::on_chain_votes::() + } } impl beefy_primitives::BeefyApi for Runtime { diff --git a/runtime/westend/src/lib.rs b/runtime/westend/src/lib.rs index 23c085f5783a..e0ef2e3d1f5f 100644 --- a/runtime/westend/src/lib.rs +++ b/runtime/westend/src/lib.rs @@ -26,8 +26,8 @@ use parity_scale_codec::{Decode, Encode, MaxEncodedLen}; use primitives::v1::{ AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, - Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, SessionInfo, Signature, - ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, + Moment, Nonce, OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, + SessionInfo, Signature, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, }; use runtime_common::{ auctions, crowdloan, impls::ToAuthor, paras_registrar, paras_sudo_wrapper, slots, xcm_sender, @@ -1284,6 +1284,10 @@ sp_api::impl_runtime_apis! { fn validation_code_by_hash(hash: ValidationCodeHash) -> Option { parachains_runtime_api_impl::validation_code_by_hash::(hash) } + + fn on_chain_votes() -> Option> { + parachains_runtime_api_impl::on_chain_votes::() + } } impl beefy_primitives::BeefyApi for Runtime { diff --git a/scripts/gitlab/lingua.dic b/scripts/gitlab/lingua.dic index a32366d63ee6..7c50d1fbc666 100644 --- a/scripts/gitlab/lingua.dic +++ b/scripts/gitlab/lingua.dic @@ -180,6 +180,7 @@ phragmen picosecond/SM PoA/MS polkadot/MS +Polkadot/MS PoS/MS PoV/MS PoW/MS @@ -214,6 +215,7 @@ rpc RPC/MS runtime/MS rustc/MS +sybil SAFT scalable scalability diff --git a/statement-table/src/generic.rs b/statement-table/src/generic.rs index 9120010986a4..db40c88d75c1 100644 --- a/statement-table/src/generic.rs +++ b/statement-table/src/generic.rs @@ -172,12 +172,12 @@ pub type MisbehaviorFor = Misbehavior< ::Signature, >; -// kinds of votes for validity +// Kinds of votes for validity on a particular candidate. #[derive(Clone, PartialEq, Eq)] enum ValidityVote { - // implicit validity vote by issuing + // Implicit validity vote. Issued(Signature), - // direct validity vote + // Direct validity vote. Valid(Signature), }