From e9393a9afc3b33cc2d01b7820a8f186434196758 Mon Sep 17 00:00:00 2001 From: Andrei Sandu <54316454+sandreim@users.noreply.github.com> Date: Thu, 23 Jan 2025 18:53:27 +0200 Subject: [PATCH] Deprecate ParaBackingState API (#6867) Currently the `para_backing_state` API is used only by the prospective parachains subsystems and returns 2 things: the constraints for parachain blocks and the candidates pending availability. This PR deprecates `para_backing_state` and introduces a new `backing_constraints` API that can be used together with `candidates_pending_availability` to get the same information provided by `para_backing_state`. TODO: - [x] PRDoc --------- Signed-off-by: Andrei Sandu <andrei-mihail@parity.io> Co-authored-by: command-bot <> --- .../src/blockchain_rpc_client.rs | 12 +- .../src/rpc_client.rs | 13 +- .../emulated/chains/relays/rococo/src/lib.rs | 2 +- .../emulated/chains/relays/westend/src/lib.rs | 2 +- .../src/fragment_chain/mod.rs | 25 +- .../src/fragment_chain/tests.rs | 1 + .../core/prospective-parachains/src/lib.rs | 89 ++++- .../core/prospective-parachains/src/tests.rs | 369 +++++++++++++++--- polkadot/node/core/runtime-api/src/cache.rs | 24 +- polkadot/node/core/runtime-api/src/lib.rs | 13 + polkadot/node/core/runtime-api/src/tests.rs | 12 +- polkadot/node/subsystem-types/src/messages.rs | 10 +- .../subsystem-types/src/runtime_client.rs | 23 +- .../src/inclusion_emulator/mod.rs | 139 +++++-- polkadot/node/subsystem-util/src/lib.rs | 7 +- polkadot/primitives/src/runtime_api.rs | 10 +- .../primitives/src/vstaging/async_backing.rs | 40 +- polkadot/primitives/src/vstaging/mod.rs | 9 +- .../node/backing/prospective-parachains.md | 3 + .../parachains/src/runtime_api_impl/v11.rs | 19 +- .../src/runtime_api_impl/vstaging.rs | 30 ++ polkadot/runtime/rococo/src/lib.rs | 15 +- polkadot/runtime/test-runtime/src/lib.rs | 1 + polkadot/runtime/westend/src/lib.rs | 15 +- prdoc/pr_6867.prdoc | 30 ++ 25 files changed, 758 insertions(+), 155 deletions(-) create mode 100644 prdoc/pr_6867.prdoc diff --git a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs index 1086e3a52ec..862cf6af979 100644 --- a/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs +++ b/cumulus/client/relay-chain-minimal-node/src/blockchain_rpc_client.rs @@ -26,7 +26,9 @@ use futures::{Stream, StreamExt}; use polkadot_core_primitives::{Block, BlockNumber, Hash, Header}; use polkadot_overseer::{ChainApiBackend, RuntimeApiSubsystemClient}; use polkadot_primitives::{ - async_backing::AsyncBackingParams, slashing, vstaging::async_backing::BackingState, + async_backing::AsyncBackingParams, + slashing, + vstaging::async_backing::{BackingState, Constraints}, ApprovalVotingParams, CoreIndex, NodeFeatures, }; use sc_authority_discovery::{AuthorityDiscovery, Error as AuthorityDiscoveryError}; @@ -454,6 +456,14 @@ impl RuntimeApiSubsystemClient for BlockChainRpcClient { .parachain_host_candidates_pending_availability(at, para_id) .await?) } + + async fn backing_constraints( + &self, + at: Hash, + para_id: ParaId, + ) -> Result<Option<Constraints>, ApiError> { + Ok(self.rpc_client.parachain_host_backing_constraints(at, para_id).await?) + } } #[async_trait::async_trait] diff --git a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs index d7785d92c73..0467b7085ca 100644 --- a/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs +++ b/cumulus/client/relay-chain-rpc-interface/src/rpc_client.rs @@ -35,8 +35,8 @@ use cumulus_primitives_core::{ async_backing::AsyncBackingParams, slashing, vstaging::{ - async_backing::BackingState, CandidateEvent, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, + async_backing::{BackingState, Constraints}, + CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, @@ -720,6 +720,15 @@ impl RelayChainRpcClient { .await } + pub async fn parachain_host_backing_constraints( + &self, + at: RelayHash, + para_id: ParaId, + ) -> Result<Option<Constraints>, RelayChainError> { + self.call_remote_runtime_function("ParachainHost_backing_constraints", at, Some(para_id)) + .await + } + fn send_register_message_to_worker( &self, message: RpcDispatcherMessage, diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs index bd637a5f796..240c0931ae5 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/rococo/src/lib.rs @@ -25,7 +25,7 @@ use emulated_integration_tests_common::{ // Rococo declaration decl_test_relay_chains! { - #[api_version(11)] + #[api_version(12)] pub struct Rococo { genesis = genesis::genesis(), on_init = (), diff --git a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs index ce9fafcd5bd..729bb3ad63d 100644 --- a/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs +++ b/cumulus/parachains/integration-tests/emulated/chains/relays/westend/src/lib.rs @@ -25,7 +25,7 @@ use emulated_integration_tests_common::{ // Westend declaration decl_test_relay_chains! { - #[api_version(11)] + #[api_version(12)] pub struct Westend { genesis = genesis::genesis(), on_init = (), diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs index ded0a3ab73b..72a76537160 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/mod.rs @@ -132,8 +132,8 @@ use std::{ use super::LOG_TARGET; use polkadot_node_subsystem::messages::Ancestors; use polkadot_node_subsystem_util::inclusion_emulator::{ - self, ConstraintModifications, Constraints, Fragment, HypotheticalOrConcreteCandidate, - ProspectiveCandidate, RelayChainBlockInfo, + self, validate_commitments, ConstraintModifications, Constraints, Fragment, + HypotheticalOrConcreteCandidate, ProspectiveCandidate, RelayChainBlockInfo, }; use polkadot_primitives::{ vstaging::CommittedCandidateReceiptV2 as CommittedCandidateReceipt, BlockNumber, @@ -1052,7 +1052,7 @@ impl FragmentChain { // Try seeing if the parent candidate is in the current chain or if it is the latest // included candidate. If so, get the constraints the candidate must satisfy. - let (constraints, maybe_min_relay_parent_number) = + let (is_unconnected, constraints, maybe_min_relay_parent_number) = if let Some(parent_candidate) = self.best_chain.by_output_head.get(&parent_head_hash) { let Some(parent_candidate) = self.best_chain.chain.iter().find(|c| &c.candidate_hash == parent_candidate) @@ -1062,6 +1062,7 @@ impl FragmentChain { }; ( + false, self.scope .base_constraints .apply_modifications(&parent_candidate.cumulative_modifications) @@ -1070,11 +1071,10 @@ impl FragmentChain { ) } else if self.scope.base_constraints.required_parent.hash() == parent_head_hash { // It builds on the latest included candidate. - (self.scope.base_constraints.clone(), None) + (false, self.scope.base_constraints.clone(), None) } else { - // If the parent is not yet part of the chain, there's nothing else we can check for - // now. - return Ok(()) + // The parent is not yet part of the chain + (true, self.scope.base_constraints.clone(), None) }; // Check for cycles or invalid tree transitions. @@ -1088,6 +1088,17 @@ impl FragmentChain { candidate.persisted_validation_data(), candidate.validation_code_hash(), ) { + if is_unconnected { + // If the parent is not yet part of the chain, we can check the commitments only + // if we have the full candidate. + return validate_commitments( + &self.scope.base_constraints, + &relay_parent, + commitments, + &validation_code_hash, + ) + .map_err(Error::CheckAgainstConstraints) + } Fragment::check_against_constraints( &relay_parent, &constraints, diff --git a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs index 624dd74132c..9e7e570bd16 100644 --- a/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/fragment_chain/tests.rs @@ -34,6 +34,7 @@ fn make_constraints( min_relay_parent_number, max_pov_size: 1_000_000, max_code_size: 1_000_000, + max_head_data_size: 20480, ump_remaining: 10, ump_remaining_bytes: 1_000, max_ump_num_per_candidate: 10, diff --git a/polkadot/node/core/prospective-parachains/src/lib.rs b/polkadot/node/core/prospective-parachains/src/lib.rs index 92aea8509f8..7416c97f3cd 100644 --- a/polkadot/node/core/prospective-parachains/src/lib.rs +++ b/polkadot/node/core/prospective-parachains/src/lib.rs @@ -45,15 +45,13 @@ use polkadot_node_subsystem::{ use polkadot_node_subsystem_util::{ backing_implicit_view::{BlockInfoProspectiveParachains as BlockInfo, View as ImplicitView}, inclusion_emulator::{Constraints, RelayChainBlockInfo}, + request_backing_constraints, request_candidates_pending_availability, request_session_index_for_child, runtime::{fetch_claim_queue, prospective_parachains_mode, ProspectiveParachainsMode}, }; use polkadot_primitives::{ - vstaging::{ - async_backing::CandidatePendingAvailability, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - }, - BlockNumber, CandidateHash, Hash, HeadData, Header, Id as ParaId, PersistedValidationData, + vstaging::{CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState}, + BlockNumber, CandidateHash, Hash, Header, Id as ParaId, PersistedValidationData, }; use crate::{ @@ -257,8 +255,9 @@ async fn handle_active_leaves_update<Context>( let mut fragment_chains = HashMap::new(); for para in scheduled_paras { // Find constraints and pending availability candidates. - let backing_state = fetch_backing_state(ctx, hash, para).await?; - let Some((constraints, pending_availability)) = backing_state else { + let Some((constraints, pending_availability)) = + fetch_backing_constraints_and_candidates(ctx, hash, para).await? + else { // This indicates a runtime conflict of some kind. gum::debug!( target: LOG_TARGET, @@ -273,7 +272,7 @@ async fn handle_active_leaves_update<Context>( let pending_availability = preprocess_candidates_pending_availability( ctx, &mut temp_header_cache, - constraints.required_parent.clone(), + &constraints, pending_availability, ) .await?; @@ -445,22 +444,23 @@ struct ImportablePendingAvailability { async fn preprocess_candidates_pending_availability<Context>( ctx: &mut Context, cache: &mut HashMap<Hash, Header>, - required_parent: HeadData, - pending_availability: Vec<CandidatePendingAvailability>, + constraints: &Constraints, + pending_availability: Vec<CommittedCandidateReceipt>, ) -> JfyiErrorResult<Vec<ImportablePendingAvailability>> { - let mut required_parent = required_parent; + let mut required_parent = constraints.required_parent.clone(); let mut importable = Vec::new(); let expected_count = pending_availability.len(); for (i, pending) in pending_availability.into_iter().enumerate() { + let candidate_hash = pending.hash(); let Some(relay_parent) = fetch_block_info(ctx, cache, pending.descriptor.relay_parent()).await? else { let para_id = pending.descriptor.para_id(); gum::debug!( target: LOG_TARGET, - ?pending.candidate_hash, + ?candidate_hash, ?para_id, index = ?i, ?expected_count, @@ -478,12 +478,12 @@ async fn preprocess_candidates_pending_availability<Context>( }, persisted_validation_data: PersistedValidationData { parent_head: required_parent, - max_pov_size: pending.max_pov_size, + max_pov_size: constraints.max_pov_size as _, relay_parent_number: relay_parent.number, relay_parent_storage_root: relay_parent.storage_root, }, compact: fragment_chain::PendingAvailability { - candidate_hash: pending.candidate_hash, + candidate_hash, relay_parent: relay_parent.into(), }, }); @@ -883,7 +883,7 @@ async fn fetch_backing_state<Context>( ctx: &mut Context, relay_parent: Hash, para_id: ParaId, -) -> JfyiErrorResult<Option<(Constraints, Vec<CandidatePendingAvailability>)>> { +) -> JfyiErrorResult<Option<(Constraints, Vec<CommittedCandidateReceipt>)>> { let (tx, rx) = oneshot::channel(); ctx.send_message(RuntimeApiMessage::Request( relay_parent, @@ -891,10 +891,63 @@ async fn fetch_backing_state<Context>( )) .await; - Ok(rx + Ok(rx.await.map_err(JfyiError::RuntimeApiRequestCanceled)??.map(|s| { + ( + From::from(s.constraints), + s.pending_availability + .into_iter() + .map(|c| CommittedCandidateReceipt { + descriptor: c.descriptor, + commitments: c.commitments, + }) + .collect(), + ) + })) +} + +#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] +async fn fetch_backing_constraints_and_candidates<Context>( + ctx: &mut Context, + relay_parent: Hash, + para_id: ParaId, +) -> JfyiErrorResult<Option<(Constraints, Vec<CommittedCandidateReceipt>)>> { + match fetch_backing_constraints_and_candidates_inner(ctx, relay_parent, para_id).await { + Err(error) => { + gum::debug!( + target: LOG_TARGET, + ?para_id, + ?relay_parent, + ?error, + "Failed to get constraints and candidates pending availability." + ); + + // Fallback to backing state. + fetch_backing_state(ctx, relay_parent, para_id).await + }, + Ok(maybe_constraints_and_candidatest) => Ok(maybe_constraints_and_candidatest), + } +} + +#[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] +async fn fetch_backing_constraints_and_candidates_inner<Context>( + ctx: &mut Context, + relay_parent: Hash, + para_id: ParaId, +) -> JfyiErrorResult<Option<(Constraints, Vec<CommittedCandidateReceipt>)>> { + let maybe_constraints = request_backing_constraints(relay_parent, para_id, ctx.sender()) + .await .await - .map_err(JfyiError::RuntimeApiRequestCanceled)?? - .map(|s| (From::from(s.constraints), s.pending_availability))) + .map_err(JfyiError::RuntimeApiRequestCanceled)??; + + let Some(constraints) = maybe_constraints else { return Ok(None) }; + + let pending_availability = + request_candidates_pending_availability(relay_parent, para_id, ctx.sender()) + .await + .await + .map_err(JfyiError::RuntimeApiRequestCanceled)??; + + Ok(Some((From::from(constraints), pending_availability))) } #[overseer::contextbounds(ProspectiveParachains, prefix = self::overseer)] diff --git a/polkadot/node/core/prospective-parachains/src/tests.rs b/polkadot/node/core/prospective-parachains/src/tests.rs index 3f1eaa4e41e..5d1ef2f2f51 100644 --- a/polkadot/node/core/prospective-parachains/src/tests.rs +++ b/polkadot/node/core/prospective-parachains/src/tests.rs @@ -27,8 +27,8 @@ use polkadot_node_subsystem_test_helpers as test_helpers; use polkadot_primitives::{ async_backing::{AsyncBackingParams, Constraints, InboundHrmpLimitations}, vstaging::{ - async_backing::BackingState, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, - MutateDescriptorV2, + async_backing::{BackingState, CandidatePendingAvailability, Constraints as ConstraintsV2}, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, MutateDescriptorV2, }, CoreIndex, HeadData, Header, PersistedValidationData, ScheduledCore, ValidationCodeHash, }; @@ -44,7 +44,7 @@ const ALLOWED_ANCESTRY_LEN: u32 = 3; const ASYNC_BACKING_PARAMETERS: AsyncBackingParams = AsyncBackingParams { max_candidate_depth: 4, allowed_ancestry_len: ALLOWED_ANCESTRY_LEN }; -const ASYNC_BACKING_DISABLED_ERROR: RuntimeApiError = +const RUNTIME_API_NOT_SUPPORTED: RuntimeApiError = RuntimeApiError::NotSupported { runtime_api_name: "test-runtime" }; const MAX_POV_SIZE: u32 = 1_000_000; @@ -76,6 +76,31 @@ fn dummy_constraints( } } +fn dummy_constraints_v2( + min_relay_parent_number: BlockNumber, + valid_watermarks: Vec<BlockNumber>, + required_parent: HeadData, + validation_code_hash: ValidationCodeHash, +) -> ConstraintsV2 { + ConstraintsV2 { + min_relay_parent_number, + max_pov_size: MAX_POV_SIZE, + max_head_data_size: 20480, + max_code_size: 1_000_000, + ump_remaining: 10, + ump_remaining_bytes: 1_000, + max_ump_num_per_candidate: 10, + dmp_remaining_messages: vec![], + hrmp_inbound: InboundHrmpLimitations { valid_watermarks }, + hrmp_channels_out: vec![], + max_hrmp_num_per_candidate: 0, + required_parent, + validation_code_hash, + upgrade_restriction: None, + future_validation_code: None, + } +} + struct TestState { claim_queue: BTreeMap<CoreIndex, VecDeque<ParaId>>, runtime_api_version: u32, @@ -364,47 +389,93 @@ async fn handle_leaf_activation( let paras: HashSet<_> = test_state.claim_queue.values().flatten().collect(); - for _ in 0..paras.len() { + // We expect two messages per parachain block. + for _ in 0..paras.len() * 2 { let message = virtual_overseer.recv().await; - // Get the para we are working with since the order is not deterministic. - let para_id = match &message { + let para_id = match message { + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::ParaBackingState(p_id, tx), + )) if parent == *hash => { + let PerParaData { min_relay_parent, head_data, pending_availability } = + leaf.para_data(p_id); + + let constraints = dummy_constraints( + *min_relay_parent, + vec![*number], + head_data.clone(), + test_state.validation_code_hash, + ); + + tx.send(Ok(Some(BackingState { + constraints, + pending_availability: pending_availability.clone(), + }))) + .unwrap(); + Some(p_id) + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::BackingConstraints(p_id, tx), + )) if parent == *hash && + test_state.runtime_api_version >= + RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT => + { + let PerParaData { min_relay_parent, head_data, pending_availability: _ } = + leaf.para_data(p_id); + let constraints = dummy_constraints_v2( + *min_relay_parent, + vec![*number], + head_data.clone(), + test_state.validation_code_hash, + ); + + tx.send(Ok(Some(constraints))).unwrap(); + None + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( + parent, + RuntimeApiRequest::BackingConstraints(_p_id, tx), + )) if parent == *hash && + test_state.runtime_api_version < + RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT => + { + tx.send(Err(RUNTIME_API_NOT_SUPPORTED)).unwrap(); + None + }, + AllMessages::RuntimeApi(RuntimeApiMessage::Request( - _, - RuntimeApiRequest::ParaBackingState(p_id, _), - )) => *p_id, + parent, + RuntimeApiRequest::CandidatesPendingAvailability(p_id, tx), + )) if parent == *hash => { + tx.send(Ok(leaf + .para_data(p_id) + .pending_availability + .clone() + .into_iter() + .map(|c| CommittedCandidateReceipt { + descriptor: c.descriptor, + commitments: c.commitments, + }) + .collect())) + .unwrap(); + Some(p_id) + }, _ => panic!("received unexpected message {:?}", message), }; - let PerParaData { min_relay_parent, head_data, pending_availability } = - leaf.para_data(para_id); - let constraints = dummy_constraints( - *min_relay_parent, - vec![*number], - head_data.clone(), - test_state.validation_code_hash, - ); - let backing_state = - BackingState { constraints, pending_availability: pending_availability.clone() }; - - assert_matches!( - message, - AllMessages::RuntimeApi( - RuntimeApiMessage::Request(parent, RuntimeApiRequest::ParaBackingState(p_id, tx)) - ) if parent == *hash && p_id == para_id => { - tx.send(Ok(Some(backing_state))).unwrap(); - } - ); - - for pending in pending_availability { - if !used_relay_parents.contains(&pending.descriptor.relay_parent()) { - send_block_header( - virtual_overseer, - pending.descriptor.relay_parent(), - pending.relay_parent_number, - ) - .await; - - used_relay_parents.insert(pending.descriptor.relay_parent()); + if let Some(para_id) = para_id { + for pending in leaf.para_data(para_id).pending_availability.clone() { + if !used_relay_parents.contains(&pending.descriptor.relay_parent()) { + send_block_header( + virtual_overseer, + pending.descriptor.relay_parent(), + pending.relay_parent_number, + ) + .await; + + used_relay_parents.insert(pending.descriptor.relay_parent()); + } } } } @@ -416,7 +487,9 @@ async fn handle_leaf_activation( msg: ProspectiveParachainsMessage::GetMinimumRelayParents(*hash, tx), }) .await; + let mut resp = rx.await.unwrap(); + resp.sort(); let mrp_response: Vec<(ParaId, BlockNumber)> = para_data .iter() @@ -597,7 +670,7 @@ fn should_do_no_work_if_async_backing_disabled_for_leaf() { AllMessages::RuntimeApi( RuntimeApiMessage::Request(parent, RuntimeApiRequest::AsyncBackingParams(tx)) ) if parent == hash => { - tx.send(Err(ASYNC_BACKING_DISABLED_ERROR)).unwrap(); + tx.send(Err(RUNTIME_API_NOT_SUPPORTED)).unwrap(); } ); } @@ -616,9 +689,12 @@ fn should_do_no_work_if_async_backing_disabled_for_leaf() { // - One for leaf B on parachain 1 // - One for leaf C on parachain 2 // Also tests a claim queue size larger than 1. -#[test] -fn introduce_candidates_basic() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidates_basic(#[case] runtime_api_version: u32) { let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let chain_a = ParaId::from(1); let chain_b = ParaId::from(2); @@ -786,9 +862,129 @@ fn introduce_candidates_basic() { assert_eq!(view.active_leaves.len(), 3); } -#[test] -fn introduce_candidate_multiple_times() { - let test_state = TestState::default(); +// Check if candidates are not backed if they fail constraint checks +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidates_error(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + + let view = test_harness(|mut virtual_overseer| async move { + // Leaf A + let leaf_a = TestLeaf { + number: 100, + hash: Default::default(), + para_data: vec![ + (1.into(), PerParaData::new(98, HeadData(vec![1, 2, 3]))), + (2.into(), PerParaData::new(100, HeadData(vec![2, 3, 4]))), + ], + }; + + // Activate leaves. + activate_leaf_with_params( + &mut virtual_overseer, + &leaf_a, + &test_state, + AsyncBackingParams { allowed_ancestry_len: 3, max_candidate_depth: 1 }, + ) + .await; + + // Candidate A. + let (candidate_a, pvd_a) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1, 2, 3]), + HeadData(vec![1]), + test_state.validation_code_hash, + ); + + // Candidate B. + let (candidate_b, pvd_b) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1]), + HeadData(vec![1; 20480]), + test_state.validation_code_hash, + ); + + // Candidate C commits to oversized head data. + let (candidate_c, pvd_c) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![1; 20480]), + HeadData(vec![0; 20485]), + test_state.validation_code_hash, + ); + + // Get hypothetical membership of candidates before adding candidate A. + // Candidate A can be added directly, candidates B and C are potential candidates. + for (candidate, pvd) in + [(candidate_a.clone(), pvd_a.clone()), (candidate_b.clone(), pvd_b.clone())] + { + get_hypothetical_membership( + &mut virtual_overseer, + candidate.hash(), + candidate, + pvd, + vec![leaf_a.hash], + ) + .await; + } + + // Fails constraints check + get_hypothetical_membership( + &mut virtual_overseer, + candidate_c.hash(), + candidate_c.clone(), + pvd_c.clone(), + Vec::new(), + ) + .await; + + // Add candidates + introduce_seconded_candidate(&mut virtual_overseer, candidate_a.clone(), pvd_a.clone()) + .await; + introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone()) + .await; + // Fails constraints check + introduce_seconded_candidate_failed( + &mut virtual_overseer, + candidate_c.clone(), + pvd_c.clone(), + ) + .await; + + back_candidate(&mut virtual_overseer, &candidate_a, candidate_a.hash()).await; + back_candidate(&mut virtual_overseer, &candidate_b, candidate_b.hash()).await; + // This one will not be backed. + back_candidate(&mut virtual_overseer, &candidate_c, candidate_c.hash()).await; + + // Expect only A and B to be backable + get_backable_candidates( + &mut virtual_overseer, + &leaf_a, + 1.into(), + Ancestors::default(), + 5, + vec![(candidate_a.hash(), leaf_a.hash), (candidate_b.hash(), leaf_a.hash)], + ) + .await; + virtual_overseer + }); + + assert_eq!(view.active_leaves.len(), 1); +} + +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidate_multiple_times(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1172,9 +1368,12 @@ fn introduce_candidate_parent_leaving_view() { } // Introduce a candidate to multiple forks, see how the membership is returned. -#[test] -fn introduce_candidate_on_multiple_forks() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn introduce_candidate_on_multiple_forks(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf B let leaf_b = TestLeaf { @@ -1241,11 +1440,14 @@ fn introduce_candidate_on_multiple_forks() { assert_eq!(view.active_leaves.len(), 2); } -#[test] -fn unconnected_candidates_become_connected() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn unconnected_candidates_become_connected(#[case] runtime_api_version: u32) { // This doesn't test all the complicated cases with many unconnected candidates, as it's more // extensively tested in the `fragment_chain::tests` module. - let test_state = TestState::default(); + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1483,9 +1685,14 @@ fn check_backable_query_single_candidate() { } // Backs some candidates and tests `GetBackableCandidates` when requesting a multiple candidates. -#[test] -fn check_backable_query_multiple_candidates() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_backable_query_multiple_candidates(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -1755,9 +1962,13 @@ fn check_backable_query_multiple_candidates() { } // Test hypothetical membership query. -#[test] -fn check_hypothetical_membership_query() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_hypothetical_membership_query(#[case] runtime_api_version: u32) { + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + let view = test_harness(|mut virtual_overseer| async move { // Leaf B let leaf_b = TestLeaf { @@ -1894,6 +2105,17 @@ fn check_hypothetical_membership_query() { ); introduce_seconded_candidate_failed(&mut virtual_overseer, candidate_d, pvd_d).await; + // Candidate E has invalid head data. + let (candidate_e, pvd_e) = make_candidate( + leaf_a.hash, + leaf_a.number, + 1.into(), + HeadData(vec![2]), + HeadData(vec![0; 20481]), + test_state.validation_code_hash, + ); + introduce_seconded_candidate_failed(&mut virtual_overseer, candidate_e, pvd_e).await; + // Add candidate B and back it. introduce_seconded_candidate(&mut virtual_overseer, candidate_b.clone(), pvd_b.clone()) .await; @@ -1921,9 +2143,14 @@ fn check_hypothetical_membership_query() { assert_eq!(view.active_leaves.len(), 2); } -#[test] -fn check_pvd_query() { - let test_state = TestState::default(); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn check_pvd_query(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. + let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let view = test_harness(|mut virtual_overseer| async move { // Leaf A let leaf_a = TestLeaf { @@ -2061,6 +2288,7 @@ fn check_pvd_query() { // This test is parametrised with the runtime api version. For versions that don't support the claim // queue API, we check that av-cores are used. #[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] #[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] #[case(8)] fn correctly_updates_leaves(#[case] runtime_api_version: u32) { @@ -2098,6 +2326,7 @@ fn correctly_updates_leaves(#[case] runtime_api_version: u32) { // Activate leaves. activate_leaf(&mut virtual_overseer, &leaf_a, &test_state).await; + activate_leaf(&mut virtual_overseer, &leaf_b, &test_state).await; // Try activating a duplicate leaf. @@ -2161,10 +2390,15 @@ fn correctly_updates_leaves(#[case] runtime_api_version: u32) { assert_eq!(view.active_leaves.len(), 0); } -#[test] -fn handle_active_leaves_update_gets_candidates_from_parent() { - let para_id = ParaId::from(1); +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn handle_active_leaves_update_gets_candidates_from_parent(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); + let para_id = ParaId::from(1); test_state.claim_queue = test_state .claim_queue .into_iter() @@ -2477,9 +2711,14 @@ fn handle_active_leaves_update_bounded_implicit_view() { ); } -#[test] -fn persists_pending_availability_candidate() { +#[rstest] +#[case(RuntimeApiRequest::CONSTRAINTS_RUNTIME_REQUIREMENT)] +#[case(RuntimeApiRequest::CLAIM_QUEUE_RUNTIME_REQUIREMENT)] +fn persists_pending_availability_candidate(#[case] runtime_api_version: u32) { + // This doesn't test all the complicated cases with many unconnected candidates, as it's more + // extensively tested in the `fragment_chain::tests` module. let mut test_state = TestState::default(); + test_state.set_runtime_api_version(runtime_api_version); let para_id = ParaId::from(1); test_state.claim_queue = test_state .claim_queue diff --git a/polkadot/node/core/runtime-api/src/cache.rs b/polkadot/node/core/runtime-api/src/cache.rs index 7246010711e..8a885ea9cc9 100644 --- a/polkadot/node/core/runtime-api/src/cache.rs +++ b/polkadot/node/core/runtime-api/src/cache.rs @@ -20,10 +20,10 @@ use schnellru::{ByLength, LruMap}; use sp_consensus_babe::Epoch; use polkadot_primitives::{ - async_backing, slashing, vstaging, + async_backing, slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, @@ -75,6 +75,7 @@ pub(crate) struct RequestResultCache { node_features: LruMap<SessionIndex, NodeFeatures>, approval_voting_params: LruMap<SessionIndex, ApprovalVotingParams>, claim_queue: LruMap<Hash, BTreeMap<CoreIndex, VecDeque<ParaId>>>, + backing_constraints: LruMap<(Hash, ParaId), Option<Constraints>>, } impl Default for RequestResultCache { @@ -112,6 +113,7 @@ impl Default for RequestResultCache { async_backing_params: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), node_features: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), claim_queue: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), + backing_constraints: LruMap::new(ByLength::new(DEFAULT_CACHE_CAP)), } } } @@ -559,6 +561,21 @@ impl RequestResultCache { ) { self.claim_queue.insert(relay_parent, value); } + + pub(crate) fn backing_constraints( + &mut self, + key: (Hash, ParaId), + ) -> Option<&Option<Constraints>> { + self.backing_constraints.get(&key).map(|v| &*v) + } + + pub(crate) fn cache_backing_constraints( + &mut self, + key: (Hash, ParaId), + value: Option<Constraints>, + ) { + self.backing_constraints.insert(key, value); + } } pub(crate) enum RequestResult { @@ -610,4 +627,5 @@ pub(crate) enum RequestResult { NodeFeatures(SessionIndex, NodeFeatures), ClaimQueue(Hash, BTreeMap<CoreIndex, VecDeque<ParaId>>), CandidatesPendingAvailability(Hash, ParaId, Vec<CommittedCandidateReceipt>), + BackingConstraints(Hash, ParaId, Option<Constraints>), } diff --git a/polkadot/node/core/runtime-api/src/lib.rs b/polkadot/node/core/runtime-api/src/lib.rs index c8b1d61e7be..4889822b46a 100644 --- a/polkadot/node/core/runtime-api/src/lib.rs +++ b/polkadot/node/core/runtime-api/src/lib.rs @@ -183,6 +183,9 @@ where ClaimQueue(relay_parent, sender) => { self.requests_cache.cache_claim_queue(relay_parent, sender); }, + BackingConstraints(relay_parent, para_id, constraints) => self + .requests_cache + .cache_backing_constraints((relay_parent, para_id), constraints), } } @@ -340,6 +343,8 @@ where }, Request::ClaimQueue(sender) => query!(claim_queue(), sender).map(|sender| Request::ClaimQueue(sender)), + Request::BackingConstraints(para, sender) => query!(backing_constraints(para), sender) + .map(|sender| Request::BackingConstraints(para, sender)), } } @@ -652,5 +657,13 @@ where ver = Request::CLAIM_QUEUE_RUNTIME_REQUIREMENT, sender ), + Request::BackingConstraints(para, sender) => { + query!( + BackingConstraints, + backing_constraints(para), + ver = Request::CONSTRAINTS_RUNTIME_REQUIREMENT, + sender + ) + }, } } diff --git a/polkadot/node/core/runtime-api/src/tests.rs b/polkadot/node/core/runtime-api/src/tests.rs index d4fa0732388..56c60876957 100644 --- a/polkadot/node/core/runtime-api/src/tests.rs +++ b/polkadot/node/core/runtime-api/src/tests.rs @@ -22,8 +22,8 @@ use polkadot_node_subsystem_test_helpers::make_subsystem_context; use polkadot_primitives::{ async_backing, slashing, vstaging, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Id as ParaId, @@ -307,6 +307,14 @@ impl RuntimeApiSubsystemClient for MockSubsystemClient { ) -> Result<BTreeMap<CoreIndex, VecDeque<ParaId>>, ApiError> { todo!("Not required for tests") } + + async fn backing_constraints( + &self, + _at: Hash, + _para_id: ParaId, + ) -> Result<Option<Constraints>, ApiError> { + todo!("Not required for tests") + } } #[test] diff --git a/polkadot/node/subsystem-types/src/messages.rs b/polkadot/node/subsystem-types/src/messages.rs index b541f951921..8a3b91b3ec7 100644 --- a/polkadot/node/subsystem-types/src/messages.rs +++ b/polkadot/node/subsystem-types/src/messages.rs @@ -42,9 +42,9 @@ use polkadot_node_primitives::{ ValidationResult, }; use polkadot_primitives::{ - async_backing, slashing, vstaging, + async_backing, slashing, vstaging::{ - BackedCandidate, CandidateReceiptV2 as CandidateReceipt, + self, async_backing::Constraints, BackedCandidate, CandidateReceiptV2 as CandidateReceipt, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, }, ApprovalVotingParams, AuthorityDiscoveryId, BlockNumber, CandidateCommitments, CandidateHash, @@ -772,6 +772,9 @@ pub enum RuntimeApiRequest { /// Get the candidates pending availability for a particular parachain /// `V11` CandidatesPendingAvailability(ParaId, RuntimeApiSender<Vec<CommittedCandidateReceipt>>), + /// Get the backing constraints for a particular parachain. + /// `V12` + BackingConstraints(ParaId, RuntimeApiSender<Option<Constraints>>), } impl RuntimeApiRequest { @@ -812,6 +815,9 @@ impl RuntimeApiRequest { /// `candidates_pending_availability` pub const CANDIDATES_PENDING_AVAILABILITY_RUNTIME_REQUIREMENT: u32 = 11; + + /// `backing_constraints` + pub const CONSTRAINTS_RUNTIME_REQUIREMENT: u32 = 12; } /// A message to the Runtime API subsystem. diff --git a/polkadot/node/subsystem-types/src/runtime_client.rs b/polkadot/node/subsystem-types/src/runtime_client.rs index 4b96009f44b..018b52bedcd 100644 --- a/polkadot/node/subsystem-types/src/runtime_client.rs +++ b/polkadot/node/subsystem-types/src/runtime_client.rs @@ -18,10 +18,10 @@ use async_trait::async_trait; use polkadot_primitives::{ async_backing, runtime_api::ParachainHost, - slashing, vstaging, + slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, Block, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Header, Id, InboundDownwardMessage, @@ -347,6 +347,15 @@ pub trait RuntimeApiSubsystemClient { at: Hash, para_id: Id, ) -> Result<Vec<CommittedCandidateReceipt<Hash>>, ApiError>; + + // == v12 == + /// Get the constraints on the actions that can be taken by a new parachain + /// block. + async fn backing_constraints( + &self, + at: Hash, + para_id: Id, + ) -> Result<Option<Constraints>, ApiError>; } /// Default implementation of [`RuntimeApiSubsystemClient`] using the client. @@ -624,6 +633,14 @@ where async fn claim_queue(&self, at: Hash) -> Result<BTreeMap<CoreIndex, VecDeque<Id>>, ApiError> { self.client.runtime_api().claim_queue(at) } + + async fn backing_constraints( + &self, + at: Hash, + para_id: Id, + ) -> Result<Option<Constraints>, ApiError> { + self.client.runtime_api().backing_constraints(at, para_id) + } } impl<Client, Block> HeaderBackend<Block> for DefaultSubsystemClient<Client> diff --git a/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs b/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs index 48d3f27b1fa..8a620db4ab0 100644 --- a/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs +++ b/polkadot/node/subsystem-util/src/inclusion_emulator/mod.rs @@ -82,9 +82,10 @@ /// in practice at most once every few weeks. use polkadot_node_subsystem::messages::HypotheticalCandidate; use polkadot_primitives::{ - async_backing::Constraints as PrimitiveConstraints, vstaging::skip_ump_signals, BlockNumber, - CandidateCommitments, CandidateHash, Hash, HeadData, Id as ParaId, PersistedValidationData, - UpgradeRestriction, ValidationCodeHash, + async_backing::Constraints as OldPrimitiveConstraints, + vstaging::{async_backing::Constraints as PrimitiveConstraints, skip_ump_signals}, + BlockNumber, CandidateCommitments, CandidateHash, Hash, HeadData, Id as ParaId, + PersistedValidationData, UpgradeRestriction, ValidationCodeHash, }; use std::{collections::HashMap, sync::Arc}; @@ -115,6 +116,8 @@ pub struct Constraints { pub max_pov_size: usize, /// The maximum new validation code size allowed, in bytes. pub max_code_size: usize, + /// The maximum head-data size, in bytes. + pub max_head_data_size: usize, /// The amount of UMP messages remaining. pub ump_remaining: usize, /// The amount of UMP bytes remaining. @@ -146,6 +149,44 @@ impl From<PrimitiveConstraints> for Constraints { min_relay_parent_number: c.min_relay_parent_number, max_pov_size: c.max_pov_size as _, max_code_size: c.max_code_size as _, + max_head_data_size: c.max_head_data_size as _, + ump_remaining: c.ump_remaining as _, + ump_remaining_bytes: c.ump_remaining_bytes as _, + max_ump_num_per_candidate: c.max_ump_num_per_candidate as _, + dmp_remaining_messages: c.dmp_remaining_messages, + hrmp_inbound: InboundHrmpLimitations { + valid_watermarks: c.hrmp_inbound.valid_watermarks, + }, + hrmp_channels_out: c + .hrmp_channels_out + .into_iter() + .map(|(para_id, limits)| { + ( + para_id, + OutboundHrmpChannelLimitations { + bytes_remaining: limits.bytes_remaining as _, + messages_remaining: limits.messages_remaining as _, + }, + ) + }) + .collect(), + max_hrmp_num_per_candidate: c.max_hrmp_num_per_candidate as _, + required_parent: c.required_parent, + validation_code_hash: c.validation_code_hash, + upgrade_restriction: c.upgrade_restriction, + future_validation_code: c.future_validation_code, + } + } +} + +impl From<OldPrimitiveConstraints> for Constraints { + fn from(c: OldPrimitiveConstraints) -> Self { + Constraints { + min_relay_parent_number: c.min_relay_parent_number, + max_pov_size: c.max_pov_size as _, + max_code_size: c.max_code_size as _, + // Equal to Polkadot/Kusama config. + max_head_data_size: 20480, ump_remaining: c.ump_remaining as _, ump_remaining_bytes: c.ump_remaining_bytes as _, max_ump_num_per_candidate: c.max_ump_num_per_candidate as _, @@ -520,6 +561,10 @@ pub enum FragmentValidityError { /// /// Max allowed, new. CodeSizeTooLarge(usize, usize), + /// Head data size too big. + /// + /// Max allowed, new. + HeadDataTooLarge(usize, usize), /// Relay parent too old. /// /// Min allowed, current. @@ -686,28 +731,13 @@ impl Fragment { } } -fn validate_against_constraints( +/// Validates if the candidate commitments are obeying the constraints. +pub fn validate_commitments( constraints: &Constraints, relay_parent: &RelayChainBlockInfo, commitments: &CandidateCommitments, - persisted_validation_data: &PersistedValidationData, validation_code_hash: &ValidationCodeHash, - modifications: &ConstraintModifications, ) -> Result<(), FragmentValidityError> { - let expected_pvd = PersistedValidationData { - parent_head: constraints.required_parent.clone(), - relay_parent_number: relay_parent.number, - relay_parent_storage_root: relay_parent.storage_root, - max_pov_size: constraints.max_pov_size as u32, - }; - - if expected_pvd != *persisted_validation_data { - return Err(FragmentValidityError::PersistedValidationDataMismatch( - expected_pvd, - persisted_validation_data.clone(), - )) - } - if constraints.validation_code_hash != *validation_code_hash { return Err(FragmentValidityError::ValidationCodeMismatch( constraints.validation_code_hash, @@ -715,6 +745,13 @@ fn validate_against_constraints( )) } + if commitments.head_data.0.len() > constraints.max_head_data_size { + return Err(FragmentValidityError::HeadDataTooLarge( + constraints.max_head_data_size, + commitments.head_data.0.len(), + )) + } + if relay_parent.number < constraints.min_relay_parent_number { return Err(FragmentValidityError::RelayParentTooOld( constraints.min_relay_parent_number, @@ -740,6 +777,39 @@ fn validate_against_constraints( )) } + if commitments.horizontal_messages.len() > constraints.max_hrmp_num_per_candidate { + return Err(FragmentValidityError::HrmpMessagesPerCandidateOverflow { + messages_allowed: constraints.max_hrmp_num_per_candidate, + messages_submitted: commitments.horizontal_messages.len(), + }) + } + + Ok(()) +} + +fn validate_against_constraints( + constraints: &Constraints, + relay_parent: &RelayChainBlockInfo, + commitments: &CandidateCommitments, + persisted_validation_data: &PersistedValidationData, + validation_code_hash: &ValidationCodeHash, + modifications: &ConstraintModifications, +) -> Result<(), FragmentValidityError> { + validate_commitments(constraints, relay_parent, commitments, validation_code_hash)?; + + let expected_pvd = PersistedValidationData { + parent_head: constraints.required_parent.clone(), + relay_parent_number: relay_parent.number, + relay_parent_storage_root: relay_parent.storage_root, + max_pov_size: constraints.max_pov_size as u32, + }; + + if expected_pvd != *persisted_validation_data { + return Err(FragmentValidityError::PersistedValidationDataMismatch( + expected_pvd, + persisted_validation_data.clone(), + )) + } if modifications.dmp_messages_processed == 0 { if constraints .dmp_remaining_messages @@ -750,20 +820,12 @@ fn validate_against_constraints( } } - if commitments.horizontal_messages.len() > constraints.max_hrmp_num_per_candidate { - return Err(FragmentValidityError::HrmpMessagesPerCandidateOverflow { - messages_allowed: constraints.max_hrmp_num_per_candidate, - messages_submitted: commitments.horizontal_messages.len(), - }) - } - if modifications.ump_messages_sent > constraints.max_ump_num_per_candidate { return Err(FragmentValidityError::UmpMessagesPerCandidateOverflow { messages_allowed: constraints.max_ump_num_per_candidate, messages_submitted: commitments.upward_messages.len(), }) } - constraints .check_modifications(&modifications) .map_err(FragmentValidityError::OutputsInvalid) @@ -971,6 +1033,7 @@ mod tests { validation_code_hash: ValidationCode(vec![4, 5, 6]).hash(), upgrade_restriction: None, future_validation_code: None, + max_head_data_size: 1024, } } @@ -1478,4 +1541,24 @@ mod tests { Err(FragmentValidityError::HrmpMessagesDescendingOrDuplicate(1)), ); } + + #[test] + fn head_data_size_too_large() { + let relay_parent = RelayChainBlockInfo { + number: 6, + hash: Hash::repeat_byte(0xcc), + storage_root: Hash::repeat_byte(0xff), + }; + + let constraints = make_constraints(); + let mut candidate = make_candidate(&constraints, &relay_parent); + + let head_data_size = constraints.max_head_data_size; + candidate.commitments.head_data = vec![0; head_data_size + 1].into(); + + assert_eq!( + Fragment::new(relay_parent, constraints, Arc::new(candidate.clone())), + Err(FragmentValidityError::HeadDataTooLarge(head_data_size, head_data_size + 1)), + ); + } } diff --git a/polkadot/node/subsystem-util/src/lib.rs b/polkadot/node/subsystem-util/src/lib.rs index 3bed1855894..6b069ee8611 100644 --- a/polkadot/node/subsystem-util/src/lib.rs +++ b/polkadot/node/subsystem-util/src/lib.rs @@ -43,8 +43,9 @@ use futures::channel::{mpsc, oneshot}; use polkadot_primitives::{ slashing, vstaging::{ - async_backing::BackingState, CandidateEvent, - CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, + async_backing::{BackingState, Constraints}, + CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, + ScrapedOnChainVotes, }, AsyncBackingParams, AuthorityDiscoveryId, CandidateHash, CoreIndex, EncodeAs, ExecutorParams, GroupIndex, GroupRotationInfo, Hash, Id as ParaId, OccupiedCoreAssumption, @@ -313,6 +314,8 @@ specialize_requests! { fn request_async_backing_params() -> AsyncBackingParams; AsyncBackingParams; fn request_claim_queue() -> BTreeMap<CoreIndex, VecDeque<ParaId>>; ClaimQueue; fn request_para_backing_state(para_id: ParaId) -> Option<BackingState>; ParaBackingState; + fn request_backing_constraints(para_id: ParaId) -> Option<Constraints>; BackingConstraints; + } /// Requests executor parameters from the runtime effective at given relay-parent. First obtains diff --git a/polkadot/primitives/src/runtime_api.rs b/polkadot/primitives/src/runtime_api.rs index 3c90c050bae..df1dfbac400 100644 --- a/polkadot/primitives/src/runtime_api.rs +++ b/polkadot/primitives/src/runtime_api.rs @@ -116,8 +116,8 @@ use crate::{ slashing, vstaging::{ - self, CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + self, async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, ApprovalVotingParams, AsyncBackingParams, BlockNumber, CandidateCommitments, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, NodeFeatures, @@ -297,5 +297,11 @@ sp_api::decl_runtime_apis! { /// Elastic scaling support #[api_version(11)] fn candidates_pending_availability(para_id: ppp::Id) -> Vec<CommittedCandidateReceipt<Hash>>; + + /***** Added in v12 *****/ + /// Returns the constraints on the actions that can be taken by a new parachain + /// block. + #[api_version(12)] + fn backing_constraints(para_id: ppp::Id) -> Option<Constraints>; } } diff --git a/polkadot/primitives/src/vstaging/async_backing.rs b/polkadot/primitives/src/vstaging/async_backing.rs index 8706214b5a0..ce995453805 100644 --- a/polkadot/primitives/src/vstaging/async_backing.rs +++ b/polkadot/primitives/src/vstaging/async_backing.rs @@ -50,12 +50,50 @@ impl<H: Copy> From<CandidatePendingAvailability<H>> } } +/// Constraints on the actions that can be taken by a new parachain +/// block. These limitations are implicitly associated with some particular +/// parachain, which should be apparent from usage. +#[derive(RuntimeDebug, Clone, PartialEq, Encode, Decode, TypeInfo)] +pub struct Constraints<N = BlockNumber> { + /// The minimum relay-parent number accepted under these constraints. + pub min_relay_parent_number: N, + /// The maximum Proof-of-Validity size allowed, in bytes. + pub max_pov_size: u32, + /// The maximum new validation code size allowed, in bytes. + pub max_code_size: u32, + /// The maximum head-data size, in bytes. + pub max_head_data_size: u32, + /// The amount of UMP messages remaining. + pub ump_remaining: u32, + /// The amount of UMP bytes remaining. + pub ump_remaining_bytes: u32, + /// The maximum number of UMP messages allowed per candidate. + pub max_ump_num_per_candidate: u32, + /// Remaining DMP queue. Only includes sent-at block numbers. + pub dmp_remaining_messages: Vec<N>, + /// The limitations of all registered inbound HRMP channels. + pub hrmp_inbound: InboundHrmpLimitations<N>, + /// The limitations of all registered outbound HRMP channels. + pub hrmp_channels_out: Vec<(Id, OutboundHrmpChannelLimitations)>, + /// The maximum number of HRMP messages allowed per candidate. + pub max_hrmp_num_per_candidate: u32, + /// The required parent head-data of the parachain. + pub required_parent: HeadData, + /// The expected validation-code-hash of this parachain. + pub validation_code_hash: ValidationCodeHash, + /// The code upgrade restriction signal as-of this parachain. + pub upgrade_restriction: Option<UpgradeRestriction>, + /// The future validation code hash, if any, and at what relay-parent + /// number the upgrade would be minimally applied. + pub future_validation_code: Option<(N, ValidationCodeHash)>, +} + /// The per-parachain state of the backing system, including /// state-machine constraints and candidates pending availability. #[derive(RuntimeDebug, Clone, PartialEq, Encode, Decode, TypeInfo)] pub struct BackingState<H = Hash, N = BlockNumber> { /// The state-machine constraints of the parachain. - pub constraints: Constraints<N>, + pub constraints: crate::async_backing::Constraints<N>, /// The candidates pending availability. These should be ordered, i.e. they should form /// a sub-chain, where the first candidate builds on top of the required parent of the /// constraints and each subsequent builds on top of the previous head-data. diff --git a/polkadot/primitives/src/vstaging/mod.rs b/polkadot/primitives/src/vstaging/mod.rs index c52f3539c3e..5da4595af65 100644 --- a/polkadot/primitives/src/vstaging/mod.rs +++ b/polkadot/primitives/src/vstaging/mod.rs @@ -19,10 +19,11 @@ use crate::{ValidatorIndex, ValidityAttestation}; // Put any primitives used by staging APIs functions here use super::{ - async_backing::Constraints, BlakeTwo256, BlockNumber, CandidateCommitments, - CandidateDescriptor, CandidateHash, CollatorId, CollatorSignature, CoreIndex, GroupIndex, Hash, - HashT, HeadData, Header, Id, Id as ParaId, MultiDisputeStatementSet, ScheduledCore, - UncheckedSignedAvailabilityBitfields, ValidationCodeHash, + async_backing::{InboundHrmpLimitations, OutboundHrmpChannelLimitations}, + BlakeTwo256, BlockNumber, CandidateCommitments, CandidateDescriptor, CandidateHash, CollatorId, + CollatorSignature, CoreIndex, GroupIndex, Hash, HashT, HeadData, Header, Id, Id as ParaId, + MultiDisputeStatementSet, ScheduledCore, UncheckedSignedAvailabilityBitfields, + UpgradeRestriction, ValidationCodeHash, }; use alloc::{ collections::{BTreeMap, BTreeSet, VecDeque}, diff --git a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md index 61278621cf5..0f210a07864 100644 --- a/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md +++ b/polkadot/roadmap/implementers-guide/src/node/backing/prospective-parachains.md @@ -126,6 +126,9 @@ prospective validation data. This is unlikely to change. - `RuntimeApiRequest::ParaBackingState` - Gets the backing state of the given para (the constraints of the para and candidates pending availability). +- `RuntimeApiRequest::BackingConstraints` + - Gets the constraints on the actions that can be taken by a new parachain + block. - `RuntimeApiRequest::AvailabilityCores` - Gets information on all availability cores. - `ChainApiMessage::Ancestors` diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs index e9327bc7641..3f2cb577109 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/v11.rs @@ -401,10 +401,10 @@ pub fn minimum_backing_votes<T: initializer::Config>() -> u32 { configuration::ActiveConfig::<T>::get().minimum_backing_votes } -/// Implementation for `ParaBackingState` function from the runtime API -pub fn backing_state<T: initializer::Config>( +// Helper function that returns the backing constraints given a parachain id. +pub(crate) fn backing_constraints<T: initializer::Config>( para_id: ParaId, -) -> Option<BackingState<T::Hash, BlockNumberFor<T>>> { +) -> Option<Constraints<BlockNumberFor<T>>> { let config = configuration::ActiveConfig::<T>::get(); // Async backing is only expected to be enabled with a tracker capacity of 1. // Subsequent configuration update gets applied on new session, which always @@ -458,7 +458,7 @@ pub fn backing_state<T: initializer::Config>( }) .collect(); - let constraints = Constraints { + Some(Constraints { min_relay_parent_number, max_pov_size: config.max_pov_size, max_code_size: config.max_code_size, @@ -473,7 +473,16 @@ pub fn backing_state<T: initializer::Config>( validation_code_hash, upgrade_restriction, future_validation_code, - }; + }) +} + +/// Implementation for `ParaBackingState` function from the runtime API +#[deprecated(note = "`backing_state` will be removed. Use `backing_constraints` and + `candidates_pending_availability` instead.")] +pub fn backing_state<T: initializer::Config>( + para_id: ParaId, +) -> Option<BackingState<T::Hash, BlockNumberFor<T>>> { + let constraints = backing_constraints::<T>(para_id)?; let pending_availability = { crate::inclusion::PendingAvailability::<T>::get(¶_id) diff --git a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs index d01b543630c..52a9a9e1228 100644 --- a/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs +++ b/polkadot/runtime/parachains/src/runtime_api_impl/vstaging.rs @@ -15,3 +15,33 @@ // along with Polkadot. If not, see <http://www.gnu.org/licenses/>. //! Put implementations of functions from staging APIs here. + +use crate::{configuration, initializer}; +use frame_system::pallet_prelude::*; +use polkadot_primitives::{vstaging::async_backing::Constraints, Id as ParaId}; + +/// Implementation for `constraints` function from the runtime API +pub fn backing_constraints<T: initializer::Config>( + para_id: ParaId, +) -> Option<Constraints<BlockNumberFor<T>>> { + let config = configuration::ActiveConfig::<T>::get(); + let constraints_v11 = super::v11::backing_constraints::<T>(para_id)?; + + Some(Constraints { + min_relay_parent_number: constraints_v11.min_relay_parent_number, + max_pov_size: constraints_v11.max_pov_size, + max_code_size: constraints_v11.max_code_size, + max_head_data_size: config.max_head_data_size, + ump_remaining: constraints_v11.ump_remaining, + ump_remaining_bytes: constraints_v11.ump_remaining_bytes, + max_ump_num_per_candidate: constraints_v11.max_ump_num_per_candidate, + dmp_remaining_messages: constraints_v11.dmp_remaining_messages, + hrmp_inbound: constraints_v11.hrmp_inbound, + hrmp_channels_out: constraints_v11.hrmp_channels_out, + max_hrmp_num_per_candidate: constraints_v11.max_hrmp_num_per_candidate, + required_parent: constraints_v11.required_parent, + validation_code_hash: constraints_v11.validation_code_hash, + upgrade_restriction: constraints_v11.upgrade_restriction, + future_validation_code: constraints_v11.future_validation_code, + }) +} diff --git a/polkadot/runtime/rococo/src/lib.rs b/polkadot/runtime/rococo/src/lib.rs index c2c3d35ee5b..f165091beda 100644 --- a/polkadot/runtime/rococo/src/lib.rs +++ b/polkadot/runtime/rococo/src/lib.rs @@ -49,8 +49,8 @@ use pallet_nis::WithMaximumOf; use polkadot_primitives::{ slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, AccountId, AccountIndex, ApprovalVotingParams, Balance, BlockNumber, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, @@ -78,7 +78,9 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, - runtime_api_impl::v11 as parachains_runtime_api_impl, + runtime_api_impl::{ + v11 as parachains_runtime_api_impl, vstaging as parachains_runtime_vstaging_api_impl, + }, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -1984,7 +1986,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(11)] + #[api_version(12)] impl polkadot_primitives::runtime_api::ParachainHost<Block> for Runtime { fn validators() -> Vec<ValidatorId> { parachains_runtime_api_impl::validators::<Runtime>() @@ -2122,6 +2124,7 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option<polkadot_primitives::vstaging::async_backing::BackingState> { + #[allow(deprecated)] parachains_runtime_api_impl::backing_state::<Runtime>(para_id) } @@ -2148,6 +2151,10 @@ sp_api::impl_runtime_apis! { fn candidates_pending_availability(para_id: ParaId) -> Vec<CommittedCandidateReceipt<Hash>> { parachains_runtime_api_impl::candidates_pending_availability::<Runtime>(para_id) } + + fn backing_constraints(para_id: ParaId) -> Option<Constraints> { + parachains_runtime_vstaging_api_impl::backing_constraints::<Runtime>(para_id) + } } #[api_version(5)] diff --git a/polkadot/runtime/test-runtime/src/lib.rs b/polkadot/runtime/test-runtime/src/lib.rs index cdf6fa92da2..4126193388c 100644 --- a/polkadot/runtime/test-runtime/src/lib.rs +++ b/polkadot/runtime/test-runtime/src/lib.rs @@ -1067,6 +1067,7 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option<polkadot_primitives::vstaging::async_backing::BackingState> { + #[allow(deprecated)] runtime_impl::backing_state::<Runtime>(para_id) } diff --git a/polkadot/runtime/westend/src/lib.rs b/polkadot/runtime/westend/src/lib.rs index a9ba0778fe0..935b62c2338 100644 --- a/polkadot/runtime/westend/src/lib.rs +++ b/polkadot/runtime/westend/src/lib.rs @@ -52,8 +52,8 @@ use pallet_transaction_payment::{FeeDetails, FungibleAdapter, RuntimeDispatchInf use polkadot_primitives::{ slashing, vstaging::{ - CandidateEvent, CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, - ScrapedOnChainVotes, + async_backing::Constraints, CandidateEvent, + CommittedCandidateReceiptV2 as CommittedCandidateReceipt, CoreState, ScrapedOnChainVotes, }, AccountId, AccountIndex, ApprovalVotingParams, Balance, BlockNumber, CandidateHash, CoreIndex, DisputeState, ExecutorParams, GroupRotationInfo, Hash, Id as ParaId, InboundDownwardMessage, @@ -84,7 +84,9 @@ use polkadot_runtime_parachains::{ initializer as parachains_initializer, on_demand as parachains_on_demand, origin as parachains_origin, paras as parachains_paras, paras_inherent as parachains_paras_inherent, reward_points as parachains_reward_points, - runtime_api_impl::v11 as parachains_runtime_api_impl, + runtime_api_impl::{ + v11 as parachains_runtime_api_impl, vstaging as parachains_runtime_vstaging_api_impl, + }, scheduler as parachains_scheduler, session_info as parachains_session_info, shared as parachains_shared, }; @@ -2010,7 +2012,7 @@ sp_api::impl_runtime_apis! { } } - #[api_version(11)] + #[api_version(12)] impl polkadot_primitives::runtime_api::ParachainHost<Block> for Runtime { fn validators() -> Vec<ValidatorId> { parachains_runtime_api_impl::validators::<Runtime>() @@ -2148,6 +2150,7 @@ sp_api::impl_runtime_apis! { } fn para_backing_state(para_id: ParaId) -> Option<polkadot_primitives::vstaging::async_backing::BackingState> { + #[allow(deprecated)] parachains_runtime_api_impl::backing_state::<Runtime>(para_id) } @@ -2174,6 +2177,10 @@ sp_api::impl_runtime_apis! { fn candidates_pending_availability(para_id: ParaId) -> Vec<CommittedCandidateReceipt<Hash>> { parachains_runtime_api_impl::candidates_pending_availability::<Runtime>(para_id) } + + fn backing_constraints(para_id: ParaId) -> Option<Constraints> { + parachains_runtime_vstaging_api_impl::backing_constraints::<Runtime>(para_id) + } } #[api_version(5)] diff --git a/prdoc/pr_6867.prdoc b/prdoc/pr_6867.prdoc new file mode 100644 index 00000000000..afa35533d46 --- /dev/null +++ b/prdoc/pr_6867.prdoc @@ -0,0 +1,30 @@ +title: Deprecate ParaBackingState API +doc: +- audience: [ Runtime Dev, Node Dev ] + description: |- + Deprecates the `para_backing_state` API. Introduces and new `backing_constraints` API that can be used + together with existing `candidates_pending_availability` to retrieve the same information provided by + `para_backing_state`. + +crates: +- name: polkadot-primitives + bump: minor +- name: polkadot-runtime-parachains + bump: minor +- name: rococo-runtime + bump: minor +- name: westend-runtime + bump: minor +- name: cumulus-relay-chain-rpc-interface + bump: minor +- name: polkadot-node-core-prospective-parachains + bump: patch +- name: polkadot-node-core-runtime-api + bump: minor +- name: polkadot-node-subsystem-types + bump: major +- name: polkadot-node-subsystem-util + bump: major +- name: cumulus-relay-chain-minimal-node + bump: minor + -- GitLab