diff --git a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs index f227e3855fa0..88375d583090 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/mod.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/mod.rs @@ -264,7 +264,9 @@ struct State { /// never included in the fragment chains of active leaves which do. In /// particular, this means that if a given relay parent belongs to implicit /// ancestry of some active leaf, then it does support prospective parachains. - implicit_view: ImplicitView, + /// + /// It's `None` if the collator is not yet collating for a paraid. + implicit_view: Option, /// All active leaves observed by us, including both that do and do not /// support prospective parachains. This mapping works as a replacement for @@ -334,7 +336,7 @@ impl State { metrics, collating_on: Default::default(), peer_data: Default::default(), - implicit_view: Default::default(), + implicit_view: None, active_leaves: Default::default(), per_relay_parent: Default::default(), span_per_relay_parent: Default::default(), @@ -539,11 +541,12 @@ async fn distribute_collation( .filter(|(_, PeerData { view: v, .. })| match relay_parent_mode { ProspectiveParachainsMode::Disabled => v.contains(&candidate_relay_parent), ProspectiveParachainsMode::Enabled { .. } => v.iter().any(|block_hash| { - state - .implicit_view - .known_allowed_relay_parents_under(block_hash, Some(id)) - .unwrap_or_default() - .contains(&candidate_relay_parent) + state.implicit_view.as_ref().map(|implicit_view| { + implicit_view + .known_allowed_relay_parents_under(block_hash, Some(id)) + .unwrap_or_default() + .contains(&candidate_relay_parent) + }) == Some(true) }), }); @@ -830,6 +833,7 @@ async fn process_msg( match msg { CollateOn(id) => { state.collating_on = Some(id); + state.implicit_view = Some(ImplicitView::new(Some(id))); }, DistributeCollation { candidate_receipt, @@ -1215,7 +1219,10 @@ async fn handle_peer_view_change( Some(ProspectiveParachainsMode::Disabled) => std::slice::from_ref(&added), Some(ProspectiveParachainsMode::Enabled { .. }) => state .implicit_view - .known_allowed_relay_parents_under(&added, state.collating_on) + .as_ref() + .and_then(|implicit_view| { + implicit_view.known_allowed_relay_parents_under(&added, state.collating_on) + }) .unwrap_or_default(), None => { gum::trace!( @@ -1353,21 +1360,22 @@ where state.per_relay_parent.insert(*leaf, PerRelayParent::new(mode)); if mode.is_enabled() { - state - .implicit_view - .activate_leaf(sender, *leaf) - .await - .map_err(Error::ImplicitViewFetchError)?; + if let Some(ref mut implicit_view) = state.implicit_view { + implicit_view + .activate_leaf(sender, *leaf) + .await + .map_err(Error::ImplicitViewFetchError)?; - let allowed_ancestry = state - .implicit_view - .known_allowed_relay_parents_under(leaf, state.collating_on) - .unwrap_or_default(); - for block_hash in allowed_ancestry { - state - .per_relay_parent - .entry(*block_hash) - .or_insert_with(|| PerRelayParent::new(mode)); + let allowed_ancestry = implicit_view + .known_allowed_relay_parents_under(leaf, state.collating_on) + .unwrap_or_default(); + + for block_hash in allowed_ancestry { + state + .per_relay_parent + .entry(*block_hash) + .or_insert_with(|| PerRelayParent::new(mode)); + } } } } @@ -1378,7 +1386,11 @@ where // of implicit ancestry. Only update the state after the hash is actually // pruned from the block info storage. let pruned = if mode.is_enabled() { - state.implicit_view.deactivate_leaf(*leaf) + state + .implicit_view + .as_mut() + .map(|view| view.deactivate_leaf(*leaf)) + .unwrap_or_default() } else { vec![*leaf] }; diff --git a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs index 707053545630..03cf96beba8c 100644 --- a/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs +++ b/polkadot/node/network/collator-protocol/src/collator_side/tests/prospective_parachains.rs @@ -61,14 +61,14 @@ async fn update_view( let min_number = leaf_number.saturating_sub(ASYNC_BACKING_PARAMETERS.allowed_ancestry_len); - assert_matches!( - overseer_recv(virtual_overseer).await, - AllMessages::ProspectiveParachains( - ProspectiveParachainsMessage::GetMinimumRelayParents(parent, tx), - ) if parent == leaf_hash => { - tx.send(vec![(test_state.para_id, min_number)]).unwrap(); - } - ); + // assert_matches!( + // overseer_recv(virtual_overseer).await, + // AllMessages::ProspectiveParachains( + // ProspectiveParachainsMessage::GetMinimumRelayParents(parent, tx), + // ) if parent == leaf_hash => { + // tx.send(vec![(test_state.para_id, min_number)]).unwrap(); + // } + // ); let ancestry_len = leaf_number + 1 - min_number; let ancestry_hashes = std::iter::successors(Some(leaf_hash), |h| Some(get_parent_hash(*h))) diff --git a/polkadot/node/service/src/overseer.rs b/polkadot/node/service/src/overseer.rs index 26b1446bf515..4b7777a09671 100644 --- a/polkadot/node/service/src/overseer.rs +++ b/polkadot/node/service/src/overseer.rs @@ -385,7 +385,7 @@ pub fn collator_overseer_builder( DummySubsystem, DummySubsystem, DummySubsystem, - ProspectiveParachainsSubsystem, + DummySubsystem, >, Error, > @@ -462,7 +462,7 @@ where .dispute_coordinator(DummySubsystem) .dispute_distribution(DummySubsystem) .chain_selection(DummySubsystem) - .prospective_parachains(ProspectiveParachainsSubsystem::new(Metrics::register(registry)?)) + .prospective_parachains(DummySubsystem) .activation_external_listeners(Default::default()) .span_per_active_leaf(Default::default()) .active_leaves(Default::default()) diff --git a/polkadot/node/subsystem-util/src/backing_implicit_view.rs b/polkadot/node/subsystem-util/src/backing_implicit_view.rs index a14536a17666..ce6b9fc97d19 100644 --- a/polkadot/node/subsystem-util/src/backing_implicit_view.rs +++ b/polkadot/node/subsystem-util/src/backing_implicit_view.rs @@ -17,23 +17,45 @@ use futures::channel::oneshot; use polkadot_node_subsystem::{ errors::ChainApiError, - messages::{ChainApiMessage, ProspectiveParachainsMessage}, + messages::{ChainApiMessage, ProspectiveParachainsMessage, RuntimeApiMessage}, SubsystemSender, }; use polkadot_primitives::{BlockNumber, Hash, Id as ParaId}; use std::collections::HashMap; +use crate::{ + request_session_index_for_child, + runtime::{self, prospective_parachains_mode, recv_runtime, ProspectiveParachainsMode}, +}; + // Always aim to retain 1 block before the active leaves. const MINIMUM_RETAIN_LENGTH: BlockNumber = 2; /// Handles the implicit view of the relay chain derived from the immediate view, which /// is composed of active leaves, and the minimum relay-parents allowed for /// candidates of various parachains at those leaves. -#[derive(Default, Clone)] +#[derive(Clone)] pub struct View { leaves: HashMap, block_info_storage: HashMap, + collating_for: Option, +} + +impl View { + /// Create a new empty view. + /// If `collating_for` is `Some`, the node is a collator and is only interested in the allowed + /// relay parents of a single paraid. When this is true, prospective-parachains is no longer + /// queried. + pub fn new(collating_for: Option) -> Self { + Self { leaves: Default::default(), block_info_storage: Default::default(), collating_for } + } +} + +impl Default for View { + fn default() -> Self { + Self::new(None) + } } // Minimum relay parents implicitly relative to a particular block. @@ -106,15 +128,13 @@ impl View { } /// Activate a leaf in the view. - /// This will request the minimum relay parents from the - /// Prospective Parachains subsystem for each leaf and will load headers in the ancestry of each - /// leaf in the view as needed. These are the 'implicit ancestors' of the leaf. + /// This will request the minimum relay parents the leaf and will load headers in the + /// ancestry of the leaf as needed. These are the 'implicit ancestors' of the leaf. /// /// To maximize reuse of outdated leaves, it's best to activate new leaves before /// deactivating old ones. /// - /// This returns a list of para-ids which are relevant to the leaf, - /// and the allowed relay parents for these paras under this leaf can be + /// The allowed relay parents for the relevant paras under this leaf can be /// queried with [`View::known_allowed_relay_parents_under`]. /// /// No-op for known leaves. @@ -122,10 +142,11 @@ impl View { &mut self, sender: &mut Sender, leaf_hash: Hash, - ) -> Result, FetchError> + ) -> Result<(), FetchError> where - Sender: SubsystemSender, - Sender: SubsystemSender, + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender, { if self.leaves.contains_key(&leaf_hash) { return Err(FetchError::AlreadyKnown) @@ -135,6 +156,7 @@ impl View { leaf_hash, &mut self.block_info_storage, &mut *sender, + self.collating_for, ) .await; @@ -150,7 +172,7 @@ impl View { self.leaves.insert(leaf_hash, ActiveLeafPruningInfo { retain_minimum }); - Ok(fetched.relevant_paras) + Ok(()) }, Err(e) => Err(e), } @@ -249,6 +271,10 @@ pub enum FetchError { /// Request to the Chain API subsystem failed. #[error("The chain API subsystem was unavailable")] ChainApiUnavailable, + + /// Request to the runtime API failed. + #[error("Runtime API error: {0}")] + RuntimeApi(#[from] runtime::Error), } /// Reasons a block header might have been unavailable. @@ -265,30 +291,91 @@ pub enum BlockHeaderUnavailableReason { struct FetchSummary { minimum_ancestor_number: BlockNumber, leaf_number: BlockNumber, - relevant_paras: Vec, } -async fn fetch_fresh_leaf_and_insert_ancestry( +// Request the min relay parents from prospective-parachains. +async fn prospective_parachains_min_relay_parents< + Sender: SubsystemSender, +>( leaf_hash: Hash, - block_info_storage: &mut HashMap, sender: &mut Sender, -) -> Result +) -> Result, FetchError> { + let (tx, rx) = oneshot::channel(); + sender + .send_message(ProspectiveParachainsMessage::GetMinimumRelayParents(leaf_hash, tx)) + .await; + + rx.await.map_err(|_| FetchError::ProspectiveParachainsUnavailable) +} + +// Request the min relay parent for a single para, directly using ChainApi. +async fn para_min_relay_parent( + leaf_hash: Hash, + leaf_number: BlockNumber, + sender: &mut Sender, +) -> Result, FetchError> where - Sender: SubsystemSender, - Sender: SubsystemSender, + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender, { - let min_relay_parents_raw = { - let (tx, rx) = oneshot::channel(); - sender - .send_message(ProspectiveParachainsMessage::GetMinimumRelayParents(leaf_hash, tx)) - .await; + let Ok(ProspectiveParachainsMode::Enabled { allowed_ancestry_len, .. }) = + prospective_parachains_mode(sender, leaf_hash).await + else { + // This should never happen, leaves that don't have prospective parachains mode enabled + // should not use implicit view. + return Ok(None) + }; - match rx.await { - Ok(m) => m, - Err(_) => return Err(FetchError::ProspectiveParachainsUnavailable), + // Fetch the session of the leaf. We must make sure that we stop at the ancestor which has a + // different session index. + let required_session = + recv_runtime(request_session_index_for_child(leaf_hash, sender).await).await?; + + let mut min = leaf_number; + + // Fetch the ancestors, up to allowed_ancestry_len. + let (tx, rx) = oneshot::channel(); + sender + .send_message(ChainApiMessage::Ancestors { + hash: leaf_hash, + k: allowed_ancestry_len, + response_channel: tx, + }) + .await; + let hashes = rx + .await + .map_err(|_| FetchError::ChainApiUnavailable)? + .map_err(|err| FetchError::ChainApiError(leaf_hash, err))?; + + for hash in hashes { + // The relay chain cannot accept blocks backed from previous sessions, with + // potentially previous validators. This is a technical limitation we need to + // respect here. + let session = recv_runtime(request_session_index_for_child(hash, sender).await).await?; + + if session == required_session { + // We should never underflow here, the ChainAPI stops at genesis block. + min = min.saturating_sub(1); + } else { + break } - }; + } + Ok(Some(min)) +} + +async fn fetch_fresh_leaf_and_insert_ancestry( + leaf_hash: Hash, + block_info_storage: &mut HashMap, + sender: &mut Sender, + collating_for: Option, +) -> Result +where + Sender: SubsystemSender + + SubsystemSender + + SubsystemSender, +{ let leaf_header = { let (tx, rx) = oneshot::channel(); sender.send_message(ChainApiMessage::BlockHeader(leaf_hash, tx)).await; @@ -313,8 +400,18 @@ where } }; - let min_min = min_relay_parents_raw.iter().map(|x| x.1).min().unwrap_or(leaf_header.number); - let relevant_paras = min_relay_parents_raw.iter().map(|x| x.0).collect(); + // If the node is a collator, bypass prospective-parachains. We're only interested in the one + // paraid and the subsystem is not present. + let min_relay_parents = if let Some(para_id) = collating_for { + para_min_relay_parent(leaf_hash, leaf_header.number, sender) + .await? + .map(|x| vec![(para_id, x)]) + .unwrap_or_default() + } else { + prospective_parachains_min_relay_parents(leaf_hash, sender).await? + }; + + let min_min = min_relay_parents.iter().map(|x| x.1).min().unwrap_or(leaf_header.number); let expected_ancestry_len = (leaf_header.number.saturating_sub(min_min) as usize) + 1; let ancestry = if leaf_header.number > 0 { @@ -380,14 +477,11 @@ where vec![leaf_hash] }; - let fetched_ancestry = FetchSummary { - minimum_ancestor_number: min_min, - leaf_number: leaf_header.number, - relevant_paras, - }; + let fetched_ancestry = + FetchSummary { minimum_ancestor_number: min_min, leaf_number: leaf_header.number }; let allowed_relay_parents = AllowedRelayParents { - minimum_relay_parents: min_relay_parents_raw.iter().cloned().collect(), + minimum_relay_parents: min_relay_parents.into_iter().collect(), allowed_relay_parents_contiguous: ancestry, }; @@ -531,8 +625,7 @@ mod tests { let min_min_idx = (PARA_B_MIN_PARENT - GENESIS_NUMBER - 1) as usize; let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { - let paras = res.expect("`activate_leaf` timed out").unwrap(); - assert_eq!(paras, vec![PARA_A, PARA_B]); + res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; @@ -568,8 +661,7 @@ mod tests { let blocks = [&[GENESIS_HASH], CHAIN_A].concat(); let fut = view.activate_leaf(ctx.sender(), *leaf).timeout(TIMEOUT).map(|res| { - let paras = res.expect("`activate_leaf` timed out").unwrap(); - assert_eq!(paras, vec![PARA_C]); + res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_min_relay_parents_request(&mut ctx_handle, leaf, prospective_response).await; @@ -595,8 +687,7 @@ mod tests { let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT)]; let fut = view.activate_leaf(ctx.sender(), leaf_a).timeout(TIMEOUT).map(|res| { - let paras = res.expect("`activate_leaf` timed out").unwrap(); - assert_eq!(paras, vec![PARA_A]); + res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_min_relay_parents_request(&mut ctx_handle, &leaf_a, prospective_response).await; @@ -617,8 +708,7 @@ mod tests { let prospective_response = vec![(PARA_B, PARA_B_MIN_PARENT)]; let fut = view.activate_leaf(ctx.sender(), leaf_b).timeout(TIMEOUT).map(|res| { - let paras = res.expect("`activate_leaf` timed out").unwrap(); - assert_eq!(paras, vec![PARA_B]); + res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_min_relay_parents_request(&mut ctx_handle, &leaf_b, prospective_response).await; @@ -721,8 +811,7 @@ mod tests { let prospective_response = vec![(PARA_A, PARA_A_MIN_PARENT)]; let fut = view.activate_leaf(ctx.sender(), GENESIS_HASH).timeout(TIMEOUT).map(|res| { - let paras = res.expect("`activate_leaf` timed out").unwrap(); - assert_eq!(paras, vec![PARA_A]); + res.expect("`activate_leaf` timed out").unwrap(); }); let overseer_fut = async { assert_min_relay_parents_request(&mut ctx_handle, &GENESIS_HASH, prospective_response)