diff --git a/node/core/runtime-api/src/cache.rs b/node/core/runtime-api/src/cache.rs index 9c3aeaa8d819..2a172195510e 100644 --- a/node/core/runtime-api/src/cache.rs +++ b/node/core/runtime-api/src/cache.rs @@ -21,7 +21,7 @@ use parity_util_mem::{MallocSizeOf, MallocSizeOfExt}; use sp_consensus_babe::Epoch; use polkadot_primitives::v1::{ - AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateEvent, + AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, @@ -43,6 +43,8 @@ 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; +/// Seems plenty, considering that we are only storing block numbers here: +const CANDIDATES_INCLUDED_STATE_CACHE_SIZE: usize = 1024; struct ResidentSizeOf(T); @@ -101,6 +103,8 @@ pub(crate) struct RequestResultCache { >, current_babe_epoch: MemoryLruCache>, on_chain_votes: MemoryLruCache>>, + candidates_included_state: + MemoryLruCache<(Hash, SessionIndex, CandidateHash), ResidentSizeOf>>, } impl Default for RequestResultCache { @@ -124,6 +128,7 @@ impl Default for RequestResultCache { 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), + candidates_included_state: MemoryLruCache::new(CANDIDATES_INCLUDED_STATE_CACHE_SIZE), } } } @@ -332,6 +337,13 @@ impl RequestResultCache { self.on_chain_votes.get(relay_parent).map(|v| &v.0) } + pub(crate) fn candidate_included_state( + &mut self, + key: (Hash, SessionIndex, CandidateHash), + ) -> Option<&Option> { + self.candidates_included_state.get(&key).map(|v| &v.0) + } + pub(crate) fn cache_on_chain_votes( &mut self, relay_parent: Hash, @@ -339,6 +351,13 @@ impl RequestResultCache { ) { self.on_chain_votes.insert(relay_parent, ResidentSizeOf(scraped)); } + pub(crate) fn cache_candidate_included_state( + &mut self, + key: (Hash, SessionIndex, CandidateHash), + included: Option, + ) { + self.candidates_included_state.insert(key, ResidentSizeOf(included)); + } } pub(crate) enum RequestResult { @@ -362,4 +381,5 @@ pub(crate) enum RequestResult { ), CurrentBabeEpoch(Hash, Epoch), FetchOnChainVotes(Hash, Option), + CandidateIncludedState(Hash, SessionIndex, CandidateHash, Option), } diff --git a/node/core/runtime-api/src/lib.rs b/node/core/runtime-api/src/lib.rs index a24192c2c54f..7788004ea535 100644 --- a/node/core/runtime-api/src/lib.rs +++ b/node/core/runtime-api/src/lib.rs @@ -145,6 +145,11 @@ where self.requests_cache.cache_current_babe_epoch(relay_parent, epoch), FetchOnChainVotes(relay_parent, scraped) => self.requests_cache.cache_on_chain_votes(relay_parent, scraped), + CandidateIncludedState(relay_parent, session_index, candidate_hash, block_number) => + self.requests_cache.cache_candidate_included_state( + (relay_parent, session_index, candidate_hash), + block_number, + ), } } @@ -213,6 +218,10 @@ where query!(current_babe_epoch(), sender).map(|sender| Request::CurrentBabeEpoch(sender)), Request::FetchOnChainVotes(sender) => query!(on_chain_votes(), sender).map(|sender| Request::FetchOnChainVotes(sender)), + Request::CandidateIncludedState(session_index, candidate_hash, sender) => + query!(candidate_included_state(session_index, candidate_hash), sender).map( + |sender| Request::CandidateIncludedState(session_index, candidate_hash, sender), + ), } } @@ -347,6 +356,11 @@ where 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), + Request::CandidateIncludedState(session_index, candidate_hash, sender) => query!( + CandidateIncludedState, + candidate_included_state(session_index, candidate_hash), + sender + ), } } diff --git a/node/core/runtime-api/src/tests.rs b/node/core/runtime-api/src/tests.rs index 983aa7d89e20..fe87ec0a5f57 100644 --- a/node/core/runtime-api/src/tests.rs +++ b/node/core/runtime-api/src/tests.rs @@ -20,10 +20,10 @@ use futures::channel::oneshot; use polkadot_node_primitives::{BabeAllowedSlots, BabeEpoch, BabeEpochConfiguration}; 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, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, - ValidationCodeHash, ValidatorId, ValidatorIndex, + AuthorityDiscoveryId, BlockNumber, CandidateEvent, CandidateHash, CommittedCandidateReceipt, + CoreState, GroupRotationInfo, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, + OccupiedCoreAssumption, PersistedValidationData, ScrapedOnChainVotes, SessionIndex, + SessionInfo, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, }; use sp_core::testing::TaskExecutor; use std::{ @@ -50,6 +50,7 @@ struct MockRuntimeApi { hrmp_channels: HashMap>>, babe_epoch: Option, on_chain_votes: Option, + candidates_included_state: HashMap<(SessionIndex, CandidateHash), BlockNumber>, } impl ProvideRuntimeApi for MockRuntimeApi { @@ -154,6 +155,10 @@ sp_api::mock_impl_runtime_apis! { fn on_chain_votes(&self) -> Option { self.on_chain_votes.clone() } + + fn candidate_included_state(session_index: SessionIndex, candidate_hash: CandidateHash) -> Option { + self.candidates_included_state.get(&(session_index, candidate_hash)).cloned() + } } impl BabeApi for MockRuntimeApi { @@ -221,6 +226,44 @@ fn requests_authorities() { futures::executor::block_on(future::join(subsystem_task, test_task)); } +#[test] +fn request_included_state() { + let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); + + let mut runtime_api = MockRuntimeApi::default(); + let candidate_hash = CandidateHash(Hash::repeat_byte(0xaa)); + let session_index = 1; + let included_block = 8_u32; + runtime_api + .candidates_included_state + .insert((session_index, candidate_hash.clone()), included_block); + let runtime_api = Arc::new(runtime_api); + + let relay_parent = [1; 32].into(); + let spawner = sp_core::testing::TaskExecutor::new(); + + let subsystem = RuntimeApiSubsystem::new(runtime_api.clone(), Metrics(None), spawner); + let subsystem_task = run(ctx, subsystem).map(|x| x.unwrap()); + let test_task = async move { + let (tx, rx) = oneshot::channel(); + + ctx_handle + .send(FromOverseer::Communication { + msg: RuntimeApiMessage::Request( + relay_parent, + Request::CandidateIncludedState(session_index, candidate_hash, tx), + ), + }) + .await; + + assert_eq!(rx.await.unwrap().unwrap(), Some(included_block)); + + ctx_handle.send(FromOverseer::Signal(OverseerSignal::Conclude)).await; + }; + + futures::executor::block_on(future::join(subsystem_task, test_task)); +} + #[test] fn requests_validators() { let (ctx, mut ctx_handle) = test_helpers::make_subsystem_context(TaskExecutor::new()); diff --git a/node/subsystem-types/src/messages.rs b/node/subsystem-types/src/messages.rs index f9c739f9dbdf..bdaa9d5e3f62 100644 --- a/node/subsystem-types/src/messages.rs +++ b/node/subsystem-types/src/messages.rs @@ -645,6 +645,8 @@ pub enum RuntimeApiRequest { CurrentBabeEpoch(RuntimeApiSender), /// Get all disputes in relation to a relay parent. FetchOnChainVotes(RuntimeApiSender>), + /// Get included state for a particular candidate and session number. + CandidateIncludedState(SessionIndex, CandidateHash, RuntimeApiSender>), } /// A message to the Runtime API subsystem. diff --git a/primitives/Cargo.toml b/primitives/Cargo.toml index 4aa5d00a3a7c..4a444ed069bc 100644 --- a/primitives/Cargo.toml +++ b/primitives/Cargo.toml @@ -56,3 +56,4 @@ std = [ "bitvec/std", "frame-system/std", ] +disputes = [] diff --git a/primitives/src/v1/mod.rs b/primitives/src/v1/mod.rs index cbea6480efef..0d033dc8fda0 100644 --- a/primitives/src/v1/mod.rs +++ b/primitives/src/v1/mod.rs @@ -1036,6 +1036,10 @@ sp_api::decl_runtime_apis! { /// Scrape dispute relevant from on-chain, backing votes and resolved disputes. fn on_chain_votes() -> Option>; + + /// Get included state of a given candidate as known to the disputes module. + #[cfg(feature = "disputes")] + fn candidate_included_state(session_index: SessionIndex, candidate_hash: CandidateHash) -> Option; } } diff --git a/runtime/parachains/src/disputes.rs b/runtime/parachains/src/disputes.rs index 1ec5515667fb..3bc0a24ef025 100644 --- a/runtime/parachains/src/disputes.rs +++ b/runtime/parachains/src/disputes.rs @@ -1113,6 +1113,14 @@ impl Pallet { } } + /// Retrieve the included state of a given candidate in a particular session. + pub(crate) fn get_included_state( + session: &SessionIndex, + candidate_hash: &CandidateHash, + ) -> Option { + >::get(session, candidate_hash) + } + pub(crate) fn concluded_invalid(session: SessionIndex, candidate_hash: CandidateHash) -> bool { >::get(&session, &candidate_hash).map_or(false, |dispute| { // A dispute that has concluded with supermajority-against. diff --git a/runtime/parachains/src/runtime_api_impl/v1.rs b/runtime/parachains/src/runtime_api_impl/v1.rs index 5909ea55290b..0ee81a479b41 100644 --- a/runtime/parachains/src/runtime_api_impl/v1.rs +++ b/runtime/parachains/src/runtime_api_impl/v1.rs @@ -18,12 +18,12 @@ //! functions. use crate::{ - configuration, dmp, hrmp, inclusion, initializer, paras, paras_inherent, scheduler, + configuration, disputes, 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, + AuthorityDiscoveryId, CandidateEvent, CandidateHash, CommittedCandidateReceipt, CoreIndex, + CoreOccupied, CoreState, GroupIndex, GroupRotationInfo, Id as ParaId, InboundDownwardMessage, InboundHrmpMessage, OccupiedCore, OccupiedCoreAssumption, PersistedValidationData, ScheduledCore, ScrapedOnChainVotes, SessionIndex, SessionInfo, ValidationCode, ValidationCodeHash, ValidatorId, ValidatorIndex, @@ -335,3 +335,11 @@ pub fn validation_code_by_hash( pub fn on_chain_votes() -> Option> { >::on_chain_votes() } + +/// Query candidate included state. +pub fn candidate_included_state( + session_index: SessionIndex, + candidate_hash: CandidateHash, +) -> Option { + >::get_included_state(&session_index, &candidate_hash) +} diff --git a/runtime/rococo/Cargo.toml b/runtime/rococo/Cargo.toml index 294cb062a225..19d8ea64e78f 100644 --- a/runtime/rococo/Cargo.toml +++ b/runtime/rococo/Cargo.toml @@ -60,7 +60,7 @@ frame-system = { git = "https://github.com/paritytech/substrate", branch = "mast frame-system-rpc-runtime-api = { git = "https://github.com/paritytech/substrate", branch = "master", default-features = false } runtime-common = { package = "polkadot-runtime-common", path = "../common", default-features = false } -primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false } +primitives = { package = "polkadot-primitives", path = "../../primitives", default-features = false, features = [ "disputes" ] } polkadot-parachain = { path = "../../parachain", default-features = false } runtime-parachains = { package = "polkadot-runtime-parachains", path = "../parachains", default-features = false } diff --git a/runtime/rococo/src/lib.rs b/runtime/rococo/src/lib.rs index a7d1014f10c9..e92f56941c11 100644 --- a/runtime/rococo/src/lib.rs +++ b/runtime/rococo/src/lib.rs @@ -36,11 +36,11 @@ use pallet_session::historical as session_historical; use pallet_transaction_payment::{CurrencyAdapter, FeeDetails, RuntimeDispatchInfo}; 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, ScrapedOnChainVotes, - SessionInfo as SessionInfoData, Signature, ValidationCode, ValidationCodeHash, ValidatorId, - ValidatorIndex, + AccountId, AccountIndex, Balance, BlockNumber, CandidateEvent, CandidateHash, + CommittedCandidateReceipt, CoreState, GroupRotationInfo, Hash, Id, InboundDownwardMessage, + InboundHrmpMessage, Moment, 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, @@ -1290,6 +1290,10 @@ sp_api::impl_runtime_apis! { fn on_chain_votes() -> Option> { runtime_api_impl::on_chain_votes::() } + + fn candidate_included_state(session_index: SessionIndex, candidate_hash: CandidateHash) -> Option { + runtime_api_impl::candidate_included_state::(session_index, candidate_hash) + } } impl fg_primitives::GrandpaApi for Runtime {