From dd5b371100bf464798267b2e5f32540b615e9b6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sun, 2 Jan 2022 14:12:39 +0100 Subject: [PATCH 1/2] Adds an integration test for testing the migration --- ...ntegration.rs => full_node_catching_up.rs} | 2 +- test/service/tests/migrate_solo_to_para.rs | 109 ++++++++++++++++++ 2 files changed, 110 insertions(+), 1 deletion(-) rename test/service/tests/{integration.rs => full_node_catching_up.rs} (97%) create mode 100644 test/service/tests/migrate_solo_to_para.rs diff --git a/test/service/tests/integration.rs b/test/service/tests/full_node_catching_up.rs similarity index 97% rename from test/service/tests/integration.rs rename to test/service/tests/full_node_catching_up.rs index 2687a510846..ce3fd62c023 100644 --- a/test/service/tests/integration.rs +++ b/test/service/tests/full_node_catching_up.rs @@ -19,7 +19,7 @@ use cumulus_test_service::{initial_head_data, run_relay_chain_validator_node, Ke #[substrate_test_utils::test] #[ignore] -async fn test_collating_and_non_collator_mode_catching_up() { +async fn test_full_node_catching_up() { let mut builder = sc_cli::LoggerBuilder::new(""); builder.with_colors(false); let _ = builder.init(); diff --git a/test/service/tests/migrate_solo_to_para.rs b/test/service/tests/migrate_solo_to_para.rs new file mode 100644 index 00000000000..42a23a4d139 --- /dev/null +++ b/test/service/tests/migrate_solo_to_para.rs @@ -0,0 +1,109 @@ +// Copyright 2020-2021 Parity Technologies (UK) Ltd. +// This file is part of Substrate. + +// Substrate is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. + +// Substrate is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. + +// You should have received a copy of the GNU General Public License +// along with Substrate. If not, see . + +//! Test migration from parachain A to parachain B by returning the header of parachain B. +//! +//! This can be seen as a test of the fundamentals of the solo to parachain migration use case. +//! The prerequisite is to have a running solo chain and a running parachain. The idea is that +//! the solo chain is being stopped at a given point and sends its last header to the running parachain. +//! The parachain will return this header as part of the validation phase on the relay chain to enact +//! this header as its current latest state. As the old running parachain doesn't know this header, it will +//! stop to produce new blocks. However, the old solo chain can now produce blocks using the parachain slot. +//! (Be aware, that this is just a highlevel description and some parts are omitted.) + +use cumulus_primitives_core::ParaId; +use cumulus_test_service::{initial_head_data, run_relay_chain_validator_node, Keyring::*}; +use sc_client_api::{BlockBackend, UsageProvider}; +use sp_runtime::generic::BlockId; +use codec::Encode; + +#[substrate_test_utils::test] +#[ignore] +async fn test_migrate_solo_to_para() { + let mut builder = sc_cli::LoggerBuilder::new(""); + builder.with_colors(false); + let _ = builder.init(); + + let para_id = ParaId::from(100); + + let tokio_handle = tokio::runtime::Handle::current(); + + // start alice + let alice = run_relay_chain_validator_node(tokio_handle.clone(), Alice, || {}, Vec::new()); + + // start bob + let bob = + run_relay_chain_validator_node(tokio_handle.clone(), Bob, || {}, vec![alice.addr.clone()]); + + // register parachain + alice + .register_parachain( + para_id, + cumulus_test_runtime::WASM_BINARY + .expect("You need to build the WASM binary to run this test!") + .to_vec(), + initial_head_data(para_id), + ) + .await + .unwrap(); + + // run the parachain that will be used to return the header of the solo chain. + let parachain = + cumulus_test_service::TestNodeBuilder::new(para_id, tokio_handle.clone(), Charlie) + .enable_collator() + .connect_to_relay_chain_nodes(vec![&alice, &bob]) + .build() + .await; + + // run the solo chain (in our case this is also already a parachain, but as it has a different genesis it will not produce any blocks.) + let solo = cumulus_test_service::TestNodeBuilder::new(para_id, tokio_handle, Dave) + .connect_to_relay_chain_nodes(vec![&alice, &bob]) + // Set some random value in the genesis state to create a different genesis hash. + .update_storage_parachain(|| { + sp_io::storage::set(b"test", b"test"); + }) + .build() + .await; + + parachain.wait_for_blocks(2).await; + + // Ensure that both chains have a different genesis hash. + assert_ne!( + parachain.client.block_hash(0).ok().flatten().unwrap(), + solo.client.block_hash(0).ok().flatten().unwrap(), + ); + + let solo_chain_header = solo.client.header(&BlockId::Number(0)).ok().flatten().unwrap(); + + // Send the transaction to set the custom header, aka the header of the solo chain. + parachain + .send_extrinsic( + cumulus_test_runtime::TestPalletCall::set_custom_validation_head_data { + custom_header: solo_chain_header.encode(), + }, + Alice, + ).await + .unwrap(); + + // Wait until the solo chain produced a block now as a parachain. + solo.wait_for_blocks(1).await; + + let parachain_best = parachain.client.usage_info().chain.best_number; + + // Wait for some more blocks and check that the old parachain doesn't produced/imported any new blocks. + solo.wait_for_blocks(2).await; + assert_eq!(parachain_best, parachain.client.usage_info().chain.best_number); +} From 4d2f1312b78922463b41daa442f14d1b8977436f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20K=C3=B6cher?= Date: Sun, 2 Jan 2022 21:30:07 +0100 Subject: [PATCH 2/2] Fix the custom validation header use case --- client/collator/src/lib.rs | 66 ++++++++++++++----- pallets/parachain-system/src/lib.rs | 10 ++- parachain-template/runtime/src/lib.rs | 4 +- .../rococo-parachain/src/lib.rs | 4 +- polkadot-parachains/seedling/src/lib.rs | 4 +- polkadot-parachains/shell/src/lib.rs | 4 +- polkadot-parachains/statemine/src/lib.rs | 4 +- polkadot-parachains/statemint/src/lib.rs | 4 +- polkadot-parachains/westmint/src/lib.rs | 4 +- primitives/core/src/lib.rs | 43 +++++++++++- test/runtime/src/lib.rs | 4 +- test/service/tests/migrate_solo_to_para.rs | 6 +- 12 files changed, 119 insertions(+), 38 deletions(-) diff --git a/client/collator/src/lib.rs b/client/collator/src/lib.rs index b380c4ce622..054ad6cc4fe 100644 --- a/client/collator/src/lib.rs +++ b/client/collator/src/lib.rs @@ -18,11 +18,12 @@ use cumulus_client_network::WaitToAnnounce; use cumulus_primitives_core::{ - relay_chain::Hash as PHash, CollectCollationInfo, ParachainBlockData, PersistedValidationData, + relay_chain::Hash as PHash, CollationInfo, CollectCollationInfo, ParachainBlockData, + PersistedValidationData, }; use sc_client_api::BlockBackend; -use sp_api::ProvideRuntimeApi; +use sp_api::{ApiExt, ProvideRuntimeApi}; use sp_consensus::BlockStatus; use sp_core::traits::SpawnNamed; use sp_runtime::{ @@ -36,7 +37,7 @@ use polkadot_node_primitives::{ }; use polkadot_node_subsystem::messages::{CollationGenerationMessage, CollatorProtocolMessage}; use polkadot_overseer::Handle as OverseerHandle; -use polkadot_primitives::v1::{CollatorPair, HeadData, Id as ParaId}; +use polkadot_primitives::v1::{CollatorPair, Id as ParaId}; use codec::{Decode, Encode}; use futures::{channel::oneshot, FutureExt}; @@ -144,30 +145,59 @@ where } } + /// Fetch the collation info from the runtime. + /// + /// Returns `Ok(Some(_))` on success, `Err(_)` on error or `Ok(None)` if the runtime api isn't implemented by the runtime. + fn fetch_collation_info( + &self, + block_hash: Block::Hash, + header: &Block::Header, + ) -> Result, sp_api::ApiError> { + let runtime_api = self.runtime_api.runtime_api(); + let block_id = BlockId::Hash(block_hash); + + let api_version = + match runtime_api.api_version::>(&block_id)? { + Some(version) => version, + None => { + tracing::error!( + target: LOG_TARGET, + "Could not fetch `CollectCollationInfo` runtime api version." + ); + return Ok(None) + }, + }; + + let collation_info = if api_version < 2 { + #[allow(deprecated)] + runtime_api + .collect_collation_info_before_version_2(&block_id)? + .into_latest(header.encode().into()) + } else { + runtime_api.collect_collation_info(&block_id, header)? + }; + + Ok(Some(collation_info)) + } + fn build_collation( - &mut self, + &self, block: ParachainBlockData, block_hash: Block::Hash, ) -> Option { let block_data = BlockData(block.encode()); - let header = block.into_header(); - let head_data = HeadData(header.encode()); - let collation_info = match self - .runtime_api - .runtime_api() - .collect_collation_info(&BlockId::Hash(block_hash)) - { - Ok(ci) => ci, - Err(e) => { + let collation_info = self + .fetch_collation_info(block_hash, block.header()) + .map_err(|e| { tracing::error!( target: LOG_TARGET, error = ?e, "Failed to collect collation info.", - ); - return None - }, - }; + ) + }) + .ok() + .flatten()?; Some(Collation { upward_messages: collation_info.upward_messages, @@ -175,7 +205,7 @@ where processed_downward_messages: collation_info.processed_downward_messages, horizontal_messages: collation_info.horizontal_messages, hrmp_watermark: collation_info.hrmp_watermark, - head_data, + head_data: collation_info.head_data, proof_of_validity: PoV { block_data }, }) } diff --git a/pallets/parachain-system/src/lib.rs b/pallets/parachain-system/src/lib.rs index fe2cdb61d00..795f6e29aa9 100644 --- a/pallets/parachain-system/src/lib.rs +++ b/pallets/parachain-system/src/lib.rs @@ -27,6 +27,7 @@ //! //! Users must ensure that they register this pallet as an inherent provider. +use codec::Encode; use cumulus_primitives_core::{ relay_chain, AbridgedHostConfiguration, ChannelStatus, CollationInfo, DmpMessageHandler, GetChannelInfo, InboundDownwardMessage, InboundHrmpMessage, MessageSendError, OnValidationData, @@ -908,15 +909,22 @@ impl Pallet { /// Returns the [`CollationInfo`] of the current active block. /// + /// The given `header` is the header of the built block we are collecting the collation info for. + /// /// This is expected to be used by the /// [`CollectCollationInfo`](cumulus_primitives_core::CollectCollationInfo) runtime api. - pub fn collect_collation_info() -> CollationInfo { + pub fn collect_collation_info(header: &T::Header) -> CollationInfo { CollationInfo { hrmp_watermark: HrmpWatermark::::get(), horizontal_messages: HrmpOutboundMessages::::get(), upward_messages: UpwardMessages::::get(), processed_downward_messages: ProcessedDownwardMessages::::get(), new_validation_code: NewValidationCode::::get().map(Into::into), + // Check if there is a custom header that will also be returned by the validation phase. + // If so, we need to also return it here. + head_data: CustomValidationHeadData::::get() + .map_or_else(|| header.encode(), |v| v) + .into(), } } diff --git a/parachain-template/runtime/src/lib.rs b/parachain-template/runtime/src/lib.rs index 422c637110c..1949fe5e295 100644 --- a/parachain-template/runtime/src/lib.rs +++ b/parachain-template/runtime/src/lib.rs @@ -731,8 +731,8 @@ impl_runtime_apis! { } impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info() -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info() + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) } } diff --git a/polkadot-parachains/rococo-parachain/src/lib.rs b/polkadot-parachains/rococo-parachain/src/lib.rs index 7b358f502d0..94eb03e87a1 100644 --- a/polkadot-parachains/rococo-parachain/src/lib.rs +++ b/polkadot-parachains/rococo-parachain/src/lib.rs @@ -697,8 +697,8 @@ impl_runtime_apis! { } impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info() -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info() + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) } } } diff --git a/polkadot-parachains/seedling/src/lib.rs b/polkadot-parachains/seedling/src/lib.rs index fb10260421d..40d4744549f 100644 --- a/polkadot-parachains/seedling/src/lib.rs +++ b/polkadot-parachains/seedling/src/lib.rs @@ -303,8 +303,8 @@ impl_runtime_apis! { } impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info() -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info() + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) } } } diff --git a/polkadot-parachains/shell/src/lib.rs b/polkadot-parachains/shell/src/lib.rs index 5434bfad724..1580c4b5ec3 100644 --- a/polkadot-parachains/shell/src/lib.rs +++ b/polkadot-parachains/shell/src/lib.rs @@ -381,8 +381,8 @@ impl_runtime_apis! { } impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info() -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info() + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) } } } diff --git a/polkadot-parachains/statemine/src/lib.rs b/polkadot-parachains/statemine/src/lib.rs index 573c5979178..0da8de22380 100644 --- a/polkadot-parachains/statemine/src/lib.rs +++ b/polkadot-parachains/statemine/src/lib.rs @@ -892,8 +892,8 @@ impl_runtime_apis! { } impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info() -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info() + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) } } diff --git a/polkadot-parachains/statemint/src/lib.rs b/polkadot-parachains/statemint/src/lib.rs index fc6829ff9f2..d0aedfe1250 100644 --- a/polkadot-parachains/statemint/src/lib.rs +++ b/polkadot-parachains/statemint/src/lib.rs @@ -893,8 +893,8 @@ impl_runtime_apis! { } impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info() -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info() + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) } } diff --git a/polkadot-parachains/westmint/src/lib.rs b/polkadot-parachains/westmint/src/lib.rs index 911429be0f4..d4102849e7b 100644 --- a/polkadot-parachains/westmint/src/lib.rs +++ b/polkadot-parachains/westmint/src/lib.rs @@ -890,8 +890,8 @@ impl_runtime_apis! { } impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info() -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info() + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) } } diff --git a/primitives/core/src/lib.rs b/primitives/core/src/lib.rs index aed56a8ed6f..6a9b16d79a4 100644 --- a/primitives/core/src/lib.rs +++ b/primitives/core/src/lib.rs @@ -19,6 +19,7 @@ #![cfg_attr(not(feature = "std"), no_std)] use codec::{Decode, Encode}; +use polkadot_parachain::primitives::HeadData; use sp_runtime::{traits::Block as BlockT, RuntimeDebug}; use sp_std::prelude::*; @@ -199,6 +200,37 @@ impl ParachainBlockData { } } +/// Information about a collation. +/// +/// This was used in version 1 of the [`CollectCollationInfo`] runtime api. +#[derive(Clone, Debug, codec::Decode, codec::Encode, PartialEq)] +pub struct CollationInfoV1 { + /// Messages destined to be interpreted by the Relay chain itself. + pub upward_messages: Vec, + /// The horizontal messages sent by the parachain. + pub horizontal_messages: Vec, + /// New validation code. + pub new_validation_code: Option, + /// The number of messages processed from the DMQ. + pub processed_downward_messages: u32, + /// The mark which specifies the block number up to which all inbound HRMP messages are processed. + pub hrmp_watermark: relay_chain::v1::BlockNumber, +} + +impl CollationInfoV1 { + /// Convert into the latest version of the [`CollationInfo`] struct. + pub fn into_latest(self, head_data: HeadData) -> CollationInfo { + CollationInfo { + upward_messages: self.upward_messages, + horizontal_messages: self.horizontal_messages, + new_validation_code: self.new_validation_code, + processed_downward_messages: self.processed_downward_messages, + hrmp_watermark: self.hrmp_watermark, + head_data, + } + } +} + /// Information about a collation. #[derive(Clone, Debug, codec::Decode, codec::Encode, PartialEq)] pub struct CollationInfo { @@ -212,12 +244,21 @@ pub struct CollationInfo { pub processed_downward_messages: u32, /// The mark which specifies the block number up to which all inbound HRMP messages are processed. pub hrmp_watermark: relay_chain::v1::BlockNumber, + /// The head data, aka encoded header, of the block that corresponds to the collation. + pub head_data: HeadData, } sp_api::decl_runtime_apis! { /// Runtime api to collect information about a collation. + #[api_version(2)] pub trait CollectCollationInfo { /// Collect information about a collation. - fn collect_collation_info() -> CollationInfo; + #[changed_in(2)] + fn collect_collation_info() -> CollationInfoV1; + /// Collect information about a collation. + /// + /// The given `header` is the header of the built block for that + /// we are collecting the collation info for. + fn collect_collation_info(header: &Block::Header) -> CollationInfo; } } diff --git a/test/runtime/src/lib.rs b/test/runtime/src/lib.rs index 728a054a3e1..ec35f758d5b 100644 --- a/test/runtime/src/lib.rs +++ b/test/runtime/src/lib.rs @@ -440,8 +440,8 @@ impl_runtime_apis! { } impl cumulus_primitives_core::CollectCollationInfo for Runtime { - fn collect_collation_info() -> cumulus_primitives_core::CollationInfo { - ParachainSystem::collect_collation_info() + fn collect_collation_info(header: &::Header) -> cumulus_primitives_core::CollationInfo { + ParachainSystem::collect_collation_info(header) } } } diff --git a/test/service/tests/migrate_solo_to_para.rs b/test/service/tests/migrate_solo_to_para.rs index 42a23a4d139..686db4d7e1f 100644 --- a/test/service/tests/migrate_solo_to_para.rs +++ b/test/service/tests/migrate_solo_to_para.rs @@ -24,11 +24,11 @@ //! stop to produce new blocks. However, the old solo chain can now produce blocks using the parachain slot. //! (Be aware, that this is just a highlevel description and some parts are omitted.) +use codec::Encode; use cumulus_primitives_core::ParaId; use cumulus_test_service::{initial_head_data, run_relay_chain_validator_node, Keyring::*}; use sc_client_api::{BlockBackend, UsageProvider}; use sp_runtime::generic::BlockId; -use codec::Encode; #[substrate_test_utils::test] #[ignore] @@ -70,6 +70,7 @@ async fn test_migrate_solo_to_para() { // run the solo chain (in our case this is also already a parachain, but as it has a different genesis it will not produce any blocks.) let solo = cumulus_test_service::TestNodeBuilder::new(para_id, tokio_handle, Dave) + .enable_collator() .connect_to_relay_chain_nodes(vec![&alice, &bob]) // Set some random value in the genesis state to create a different genesis hash. .update_storage_parachain(|| { @@ -95,7 +96,8 @@ async fn test_migrate_solo_to_para() { custom_header: solo_chain_header.encode(), }, Alice, - ).await + ) + .await .unwrap(); // Wait until the solo chain produced a block now as a parachain.