From 464e54affd89eeddaf14fa6820be56dd83f18093 Mon Sep 17 00:00:00 2001 From: Sergei Shulepov Date: Wed, 13 Jan 2021 14:40:26 +0100 Subject: [PATCH] Inform the PVF with the latest relevant relay chain state (#279) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update polkadot * Extend cumulus primitives with some relay chain exports Follow https://github.com/paritytech/polkadot/pull/2194 to see the polkadot PR * collator: collect the state proof This commit changes cumulus-collator so that it takes the relay chain state at the relay parent and creates a storage proof that contains all the required data for PVF. * parachain-upgrade: use the proofs instead This change is needed to make cumulus logic to not longer depend on the transient validation data. As part of this change, in order to preserve the current behavior `code_upgrade_allowed` now is computed on the parachain side, rather than provided by polkadot. Turned out that this requires to know the self parachain id so it was added where needed. * message-broker: use relay state to track limits this should make sending messages safe from accidentally running over the relay chain limits that were previously unknown. * Update polkadot So that `relay_storage_root` is available through `ValidationParams` * Check `relay_storage_root` matches expected Check that `relay_storage_root` submitted by the collator matches the one that we receive in `validate_block` through `ValidationParams` * Add a missing check for `dmq_mqc_head` while we are at it * Update polkadot * Fix tests that use the relay storage root * Apply suggestions from code review Co-authored-by: Bastian Köcher * Update message-broker/src/lib.rs Co-authored-by: Bastian Köcher * Remove unneeded (&_) * Fix unwraps * Polish basti's suggestion * Fix merge * Bring back the System::can_set_code check Co-authored-by: Bastian Köcher --- Cargo.lock | 17 ++ Cargo.toml | 1 + collator/src/lib.rs | 92 ++++++- consensus/src/lib.rs | 14 +- message-broker/Cargo.toml | 2 + message-broker/src/lib.rs | 239 +++++++++++++++--- parachain-upgrade/Cargo.toml | 5 + parachain-upgrade/src/lib.rs | 187 +++++++++----- parachain-upgrade/src/relay_state_snapshot.rs | 150 +++++++++++ primitives/src/lib.rs | 18 +- rococo-parachains/runtime/src/lib.rs | 1 + runtime/Cargo.toml | 1 + runtime/src/validate_block/implementation.rs | 8 + runtime/src/validate_block/tests.rs | 60 +++-- test/client/Cargo.toml | 1 + test/client/src/block_builder.rs | 30 ++- test/relay-sproof-builder/Cargo.toml | 28 ++ test/relay-sproof-builder/src/lib.rs | 74 ++++++ test/runtime/src/lib.rs | 1 + 19 files changed, 793 insertions(+), 136 deletions(-) create mode 100644 parachain-upgrade/src/relay_state_snapshot.rs create mode 100644 test/relay-sproof-builder/Cargo.toml create mode 100644 test/relay-sproof-builder/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index f6ebcec4f5..0f3a825e79 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1090,6 +1090,7 @@ dependencies = [ name = "cumulus-message-broker" version = "0.1.0" dependencies = [ + "cumulus-parachain-upgrade", "cumulus-primitives", "frame-support", "frame-system", @@ -1134,8 +1135,10 @@ version = "0.1.0" dependencies = [ "cumulus-primitives", "cumulus-runtime", + "cumulus-test-relay-sproof-builder", "frame-support", "frame-system", + "hash-db", "pallet-balances", "parity-scale-codec", "polkadot-parachain", @@ -1147,6 +1150,7 @@ dependencies = [ "sp-runtime", "sp-state-machine", "sp-std", + "sp-trie", "sp-version", "substrate-test-runtime-client", ] @@ -1174,6 +1178,7 @@ version = "0.1.0" dependencies = [ "cumulus-primitives", "cumulus-test-client", + "cumulus-test-relay-sproof-builder", "env_logger", "frame-executive", "hash-db", @@ -1223,6 +1228,7 @@ name = "cumulus-test-client" version = "0.1.0" dependencies = [ "cumulus-primitives", + "cumulus-test-relay-sproof-builder", "cumulus-test-runtime", "cumulus-test-service", "frame-system", @@ -1284,6 +1290,17 @@ dependencies = [ "xcm-handler", ] +[[package]] +name = "cumulus-test-relay-sproof-builder" +version = "0.1.0" +dependencies = [ + "cumulus-primitives", + "parity-scale-codec", + "polkadot-primitives", + "sp-runtime", + "sp-state-machine", +] + [[package]] name = "cumulus-test-runtime" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index b935b7ea0d..de32338563 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "test/runtime", "test/client", "test/service", + "test/relay-sproof-builder", "xcm-handler", ] diff --git a/collator/src/lib.rs b/collator/src/lib.rs index 33abd35618..32c84f60a4 100644 --- a/collator/src/lib.rs +++ b/collator/src/lib.rs @@ -20,7 +20,7 @@ use cumulus_network::WaitToAnnounce; use cumulus_primitives::{ inherents::{self, VALIDATION_DATA_IDENTIFIER}, well_known_keys, InboundDownwardMessage, InboundHrmpMessage, OutboundHrmpMessage, - ValidationData, + ValidationData, relay_chain, }; use cumulus_runtime::ParachainBlockData; @@ -42,7 +42,7 @@ use polkadot_node_subsystem::messages::{CollationGenerationMessage, CollatorProt use polkadot_overseer::OverseerHandler; use polkadot_primitives::v1::{ Block as PBlock, BlockData, BlockNumber as PBlockNumber, CollatorPair, Hash as PHash, HeadData, - Id as ParaId, PoV, UpwardMessage, + Id as ParaId, PoV, UpwardMessage, HrmpChannelId, }; use polkadot_service::RuntimeApiCollection; @@ -99,10 +99,10 @@ where PF: Environment + 'static + Send, PF::Proposer: Send, BI: BlockImport< - Block, - Error = ConsensusError, - Transaction = >::Transaction, - > + Send + Block, + Error = ConsensusError, + Transaction = >::Transaction, + > + Send + Sync + 'static, BS: BlockBackend, @@ -195,6 +195,79 @@ where .ok() } + /// Collect the relevant relay chain state in form of a proof for putting it into the validation + /// data inherent. + fn collect_relay_storage_proof( + &self, + relay_parent: PHash, + ) -> Option { + use relay_chain::well_known_keys as relay_well_known_keys; + + let relay_parent_state_backend = self + .polkadot_backend + .state_at(BlockId::Hash(relay_parent)) + .map_err(|e| { + error!( + target: "cumulus-collator", + "Cannot obtain the state of the relay chain at `{:?}`: {:?}", + relay_parent, + e, + ) + }) + .ok()?; + + let egress_channels = relay_parent_state_backend + .storage(&relay_well_known_keys::hrmp_egress_channel_index( + self.para_id, + )) + .map_err(|e| { + error!( + target: "cumulus-collator", + "Cannot obtain the hrmp egress channel index: {:?}", + e, + ) + }) + .ok()?; + let egress_channels = egress_channels + .map(|raw| >::decode(&mut &raw[..])) + .transpose() + .map_err(|e| { + error!( + target: "cumulus-collator", + "Cannot decode the hrmp egress channel index: {:?}", + e, + ) + }) + .ok()? + .unwrap_or_default(); + + let mut relevant_keys = vec![]; + relevant_keys.push(relay_well_known_keys::ACTIVE_CONFIG.to_vec()); + relevant_keys.push(relay_well_known_keys::relay_dispatch_queue_size( + self.para_id, + )); + relevant_keys.push(relay_well_known_keys::hrmp_egress_channel_index( + self.para_id, + )); + relevant_keys.extend(egress_channels.into_iter().map(|recipient| { + relay_well_known_keys::hrmp_channels(HrmpChannelId { + sender: self.para_id, + recipient, + }) + })); + + sp_state_machine::prove_read(relay_parent_state_backend, relevant_keys) + .map_err(|e| { + error!( + target: "cumulus-collator", + "Failed to collect required relay chain state storage proof at `{:?}`: {:?}", + relay_parent, + e, + ) + }) + .ok() + } + /// Get the inherent data with validation function parameters injected fn inherent_data( &mut self, @@ -214,8 +287,7 @@ where .ok()?; let validation_data = { - // TODO: Actual proof is to be created in the upcoming PRs. - let relay_chain_state = sp_state_machine::StorageProof::empty(); + let relay_chain_state = self.collect_relay_storage_proof(relay_parent)?; inherents::ValidationDataType { validation_data: validation_data.clone(), relay_chain_state, @@ -680,7 +752,9 @@ mod tests { _: RecordProof, ) -> Self::Proposal { let block_id = BlockId::Hash(self.header.hash()); - let builder = self.client.init_block_builder_at(&block_id, None); + let builder = self + .client + .init_block_builder_at(&block_id, None, Default::default()); let (block, storage_changes, proof) = builder.build().expect("Creates block").into_inner(); diff --git a/consensus/src/lib.rs b/consensus/src/lib.rs index 4eb7f660aa..e35b03a8a0 100644 --- a/consensus/src/lib.rs +++ b/consensus/src/lib.rs @@ -596,7 +596,7 @@ mod tests { } fn build_and_import_block(mut client: Arc) -> Block { - let builder = client.init_block_builder(None); + let builder = client.init_block_builder(None, Default::default()); let block = builder.build().unwrap().block; let (header, body) = block.clone().deconstruct(); @@ -704,7 +704,11 @@ mod tests { let block = build_and_import_block(client.clone()); let unknown_block = { - let block_builder = client.init_block_builder_at(&BlockId::Hash(block.hash()), None); + let block_builder = client.init_block_builder_at( + &BlockId::Hash(block.hash()), + None, + Default::default(), + ); block_builder.build().unwrap().block }; @@ -762,7 +766,11 @@ mod tests { let block = build_and_import_block(client.clone()); let unknown_block = { - let block_builder = client.init_block_builder_at(&BlockId::Hash(block.hash()), None); + let block_builder = client.init_block_builder_at( + &BlockId::Hash(block.hash()), + None, + Default::default(), + ); block_builder.build().unwrap().block }; diff --git a/message-broker/Cargo.toml b/message-broker/Cargo.toml index 43bcf7d92c..1629305e95 100644 --- a/message-broker/Cargo.toml +++ b/message-broker/Cargo.toml @@ -16,6 +16,7 @@ codec = { package = "parity-scale-codec", version = "1.3.0", features = [ "deriv # Cumulus dependencies cumulus-primitives = { path = "../primitives", default-features = false } +cumulus-parachain-upgrade = { path = "../parachain-upgrade", default-features = false } [features] default = [ "std" ] @@ -24,5 +25,6 @@ std = [ "frame-system/std", "sp-inherents/std", "cumulus-primitives/std", + "cumulus-parachain-upgrade/std", "sp-std/std" ] diff --git a/message-broker/src/lib.rs b/message-broker/src/lib.rs index e0c59cd3b7..7291118e0f 100644 --- a/message-broker/src/lib.rs +++ b/message-broker/src/lib.rs @@ -37,12 +37,8 @@ use cumulus_primitives::{ OutboundHrmpMessage, ParaId, UpwardMessage, UpwardMessageSender, }; -// TODO: these should be not a constant, but sourced from the relay-chain configuration. -const UMP_MSG_NUM_PER_CANDIDATE: usize = 5; -const HRMP_MSG_NUM_PER_CANDIDATE: usize = 5; - /// Configuration trait of the message broker pallet. -pub trait Config: frame_system::Config { +pub trait Config: frame_system::Config + cumulus_parachain_upgrade::Config { /// The downward message handlers that will be informed when a message is received. type DownwardMessageHandlers: DownwardMessageHandler; /// The HRMP message handlers that will be informed when a message is received. @@ -58,6 +54,9 @@ decl_storage! { /// HRMP channels with the given recipients are awaiting to be processed. If a `ParaId` is /// present in this vector then `OutboundHrmpMessages` for it should be not empty. NonEmptyHrmpChannels: Vec; + /// The number of HRMP messages we observed in `on_initialize` and thus used that number for + /// announcing the weight of `on_initialize` and `on_finialize`. + AnnouncedHrmpMessagesPerCandidate: u32; } } @@ -127,53 +126,158 @@ decl_module! { storage::unhashed::kill(well_known_keys::UPWARD_MESSAGES); storage::unhashed::kill(well_known_keys::HRMP_OUTBOUND_MESSAGES); - // Reads and writes performed by `on_finalize`. This may actually turn out to be lower, - // but we should err on the safe side. + // Here, in `on_initialize` we must report the weight for both `on_initialize` and + // `on_finalize`. + // + // One complication here, is that the `host_configuration` is updated by an inherent and + // those are processed after the block initialization phase. Therefore, we have to be + // content only with the configuration as per the previous block. That means that + // the configuration can be either stale (or be abscent altogether in case of the + // beginning of the chain). + // + // In order to mitigate this, we do the following. At the time, we are only concerned + // about `hrmp_max_message_num_per_candidate`. We reserve the amount of weight to process + // the number of HRMP messages according to the potentially stale configuration. In + // `on_finalize` we will process only the maximum between the announced number of messages + // and the actual received in the fresh configuration. + // + // In the common case, they will be the same. In the case the actual value is smaller + // than the announced, we would waste some of weight. In the case the actual value is + // greater than the announced, we will miss opportunity to send a couple of messages. + weight += T::DbWeight::get().reads_writes(1, 1); + let hrmp_max_message_num_per_candidate = + >::host_configuration() + .map(|cfg| cfg.hrmp_max_message_num_per_candidate) + .unwrap_or(0); + AnnouncedHrmpMessagesPerCandidate::put(hrmp_max_message_num_per_candidate); + + // NOTE that the actual weight consumed by `on_finalize` may turn out lower. weight += T::DbWeight::get().reads_writes( - 2 + HRMP_MSG_NUM_PER_CANDIDATE as u64, - 4 + HRMP_MSG_NUM_PER_CANDIDATE as u64, + 3 + hrmp_max_message_num_per_candidate as u64, + 4 + hrmp_max_message_num_per_candidate as u64, ); weight } fn on_finalize() { + let host_config = >::host_configuration() + .expect("host configuration is promised to set until `on_finalize`; qed"); + let relevant_messaging_state = >::relevant_messaging_state() + .expect("relevant messaging state is promised to be set until `on_finalize`; qed"); + ::PendingUpwardMessages::mutate(|up| { - let num = cmp::min(UMP_MSG_NUM_PER_CANDIDATE, up.len()); - storage::unhashed::put( - well_known_keys::UPWARD_MESSAGES, - &up[0..num], + let (count, size) = relevant_messaging_state.relay_dispatch_queue_size; + + let available_capacity = cmp::min( + host_config.max_upward_queue_count.saturating_sub(count), + host_config.max_upward_message_num_per_candidate, ); + let available_size = host_config.max_upward_queue_size.saturating_sub(size); + + // Count the number of messages we can possibly fit in the given constraints, i.e. + // available_capacity and available_size. + let num = up + .iter() + .scan( + (available_capacity as usize, available_size as usize), + |(cnt, size), msg| match (cnt.checked_sub(1), size.checked_sub(msg.len())) { + (Some(cnt), Some(size)) => Some((cnt, size)), + _ => None, + }, + ) + .count(); + + // TODO: #274 Return back messages that do not longer fit into the queue. + + storage::unhashed::put(well_known_keys::UPWARD_MESSAGES, &up[0..num]); *up = up.split_off(num); }); - // Sending HRMP messages is a little bit more involved. On top of the number of messages - // per block limit, there is also a constraint that it's possible to send only a single - // message to a given recipient per candidate. + // Sending HRMP messages is a little bit more involved. There are the following + // constraints: + // + // - a channel should exist (and it can be closed while a message is buffered), + // - at most one message can be sent in a channel, + // - the sent out messages should be ordered by ascension of recipient para id. + // - the capacity and total size of the channel is limited, + // - the maximum size of a message is limited (and can potentially be changed), + let mut non_empty_hrmp_channels = NonEmptyHrmpChannels::get(); - let outbound_hrmp_num = cmp::min(HRMP_MSG_NUM_PER_CANDIDATE, non_empty_hrmp_channels.len()); + // The number of messages we can send is limited by all of: + // - the number of non empty channels + // - the maximum number of messages per candidate according to the fresh config + // - the maximum number of messages per candidate according to the stale config + let outbound_hrmp_num = + non_empty_hrmp_channels.len() + .min(host_config.hrmp_max_message_num_per_candidate as usize) + .min(AnnouncedHrmpMessagesPerCandidate::get() as usize); + let mut outbound_hrmp_messages = Vec::with_capacity(outbound_hrmp_num); let mut prune_empty = Vec::with_capacity(outbound_hrmp_num); - for &recipient in non_empty_hrmp_channels.iter().take(outbound_hrmp_num) { - let (message_payload, became_empty) = - ::OutboundHrmpMessages::mutate(&recipient, |v| { - // this panics if `v` is empty. However, we are iterating only once over non-empty - // channels, therefore it cannot panic. - let first = v.remove(0); - let became_empty = v.is_empty(); - (first, became_empty) - }); + for &recipient in non_empty_hrmp_channels.iter() { + let idx = match relevant_messaging_state + .egress_channels + .binary_search_by_key(&recipient, |(recipient, _)| *recipient) + { + Ok(m) => m, + Err(_) => { + // TODO: #274 This means that there is no such channel anymore. Means that we should + // return back the messages from this channel. + // + // Until then pretend it became empty + prune_empty.push(recipient); + continue; + } + }; + + let channel_meta = &relevant_messaging_state.egress_channels[idx].1; + if channel_meta.msg_count + 1 > channel_meta.max_capacity { + // The channel is at its capacity. Skip it for now. + continue; + } + + let mut pending = ::OutboundHrmpMessages::get(&recipient); + + // This panics if `v` is empty. However, we are iterating only once over non-empty + // channels, therefore it cannot panic. + let message_payload = pending.remove(0); + let became_empty = pending.is_empty(); + + if channel_meta.total_size + message_payload.len() as u32 > channel_meta.max_total_size { + // Sending this message will make the channel total size overflow. Skip it for now. + continue; + } + + // If we reached here, then the channel has capacity to receive this message. However, + // it doesn't mean that we are sending it just yet. + if became_empty { + OutboundHrmpMessages::remove(&recipient); + prune_empty.push(recipient); + } else { + OutboundHrmpMessages::insert(&recipient, pending); + } + + if message_payload.len() as u32 > channel_meta.max_message_size { + // Apparently, the max message size was decreased since the message while the + // message was buffered. While it's possible to make another iteration to fetch + // the next message, we just keep going here to not complicate the logic too much. + // + // TODO: #274 Return back this message to sender. + continue; + } outbound_hrmp_messages.push(OutboundHrmpMessage { recipient, data: message_payload, }); - if became_empty { - prune_empty.push(recipient); - } } + // Sort the outbound messages by asceding recipient para id to satisfy the acceptance + // criteria requirement. + outbound_hrmp_messages.sort_by_key(|m| m.recipient); + // Prune hrmp channels that became empty. Additionally, because it may so happen that we // only gave attention to some channels in `non_empty_hrmp_channels` it's important to // change the order. Otherwise, the next `on_finalize` we will again give attention @@ -210,20 +314,83 @@ pub enum SendHorizontalErr { impl Module { pub fn send_upward_message(message: UpwardMessage) -> Result<(), SendUpErr> { - // TODO: check the message against the limit. The limit should be sourced from the - // relay-chain configuration. + // Check if the message fits into the relay-chain constraints. + // + // Note, that we are using `host_configuration` here which may be from the previous + // block, in case this is called from `on_initialize`, i.e. before the inherent with fresh + // data is submitted. + // + // That shouldn't be a problem since this is a preliminary check and the actual check would + // be performed just before submitting the message from the candidate, and it already can + // happen that during the time the message is buffered for sending the relay-chain setting + // may change so that the message is no longer valid. + // + // However, changing this setting is expected to be rare. + match >::host_configuration() { + Some(cfg) => { + if message.len() > cfg.max_upward_message_size as usize { + return Err(SendUpErr::TooBig) + } + }, + None => { + // This storage field should carry over from the previous block. So if it's None + // then it must be that this is an edge-case where a message is attempted to be + // sent at the first block. + // + // Let's pass this message through. I think it's not unreasonable to expect that the + // message is not huge and it comes through, but if it doesn't it can be returned + // back to the sender. + // + // Thus fall through here. + } + }; ::PendingUpwardMessages::append(message); Ok(()) } pub fn send_hrmp_message(message: OutboundHrmpMessage) -> Result<(), SendHorizontalErr> { - // TODO: - // (a) check against the size limit sourced from the relay-chain configuration - // (b) check if the channel to the recipient is actually opened. - let OutboundHrmpMessage { recipient, data } = message; - ::OutboundHrmpMessages::append(&recipient, data); + // First, check if the message is addressed into an opened channel. + // + // Note, that we are using `relevant_messaging_state` which may be from the previous + // block, in case this is called from `on_initialize`, i.e. before the inherent with fresh + // data is submitted. + // + // That shouldn't be a problem though because this is anticipated and already can happen. + // This is because sending implies that a message is buffered until there is space to send + // a message in the candidate. After a while waiting in a buffer, it may be discovered that + // the channel to which a message were addressed is now closed. Another possibility, is that + // the maximum message size was decreased so that a message in the bufer doesn't fit. Should + // any of that happen the sender should be notified about the message was discarded. + // + // Here it a similar case, with the difference that the realization that the channel is closed + // came the same block. + let relevant_messaging_state = match >::relevant_messaging_state() { + Some(s) => s, + None => { + // This storage field should carry over from the previous block. So if it's None + // then it must be that this is an edge-case where a message is attempted to be + // sent at the first block. It should be safe to assume that there are no channels + // opened at all so early. At least, relying on this assumption seems to be a better + // tradeoff, compared to introducing an error variant that the clients should be + // prepared to handle. + return Err(SendHorizontalErr::NoChannel) + } + }; + let channel_meta = match relevant_messaging_state + .egress_channels + .binary_search_by_key(&recipient, |(recipient, _)| *recipient) + { + Ok(idx) => &relevant_messaging_state.egress_channels[idx].1, + Err(_) => return Err(SendHorizontalErr::NoChannel), + }; + if data.len() as u32 > channel_meta.max_message_size { + return Err(SendHorizontalErr::TooBig) + } + + // And then at last update the storage. + ::OutboundHrmpMessages::append(&recipient, data); ::NonEmptyHrmpChannels::mutate(|v| { if !v.contains(&recipient) { v.push(recipient); diff --git a/parachain-upgrade/Cargo.toml b/parachain-upgrade/Cargo.toml index 5f979cbaa2..de16330e4c 100644 --- a/parachain-upgrade/Cargo.toml +++ b/parachain-upgrade/Cargo.toml @@ -24,15 +24,18 @@ sp-runtime = { git = "https://github.com/paritytech/substrate", default-features sp-version = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" } frame-system = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" } sp-state-machine = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" } +sp-trie = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } # Other Dependencies codec = { package = "parity-scale-codec", version = "1.0.0", default-features = false, features = ["derive"]} serde = { version = "1.0.101", optional = true, features = ["derive"] } +hash-db = { version = "0.15.2", default-features = false } [dev-dependencies] sp-externalities = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" } substrate-test-runtime-client = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" } sp-version = { git = "https://github.com/paritytech/substrate", default-features = false , branch = "master" } +cumulus-test-relay-sproof-builder = { path = "../test/relay-sproof-builder" } [features] default = ['std'] @@ -46,7 +49,9 @@ std = [ 'sp-runtime/std', 'sp-io/std', 'sp-std/std', + 'hash-db/std', 'sp-state-machine/std', + 'sp-trie/std', 'frame-system/std', 'cumulus-primitives/std', ] diff --git a/parachain-upgrade/src/lib.rs b/parachain-upgrade/src/lib.rs index c1a8f48854..7ca338d39a 100644 --- a/parachain-upgrade/src/lib.rs +++ b/parachain-upgrade/src/lib.rs @@ -31,12 +31,12 @@ use cumulus_primitives::{ inherents::{ValidationDataType, VALIDATION_DATA_IDENTIFIER as INHERENT_IDENTIFIER}, - well_known_keys::{NEW_VALIDATION_CODE, VALIDATION_DATA}, - OnValidationData, ValidationData, + well_known_keys::{NEW_VALIDATION_CODE, VALIDATION_DATA}, AbridgedHostConfiguration, + OnValidationData, ValidationData, ParaId, relay_chain, }; use frame_support::{ decl_error, decl_event, decl_module, decl_storage, ensure, storage, - weights::{DispatchClass, Weight}, + weights::{DispatchClass, Weight}, dispatch::DispatchResult, traits::Get, }; use frame_system::{ensure_none, ensure_root}; use parachain::primitives::RelayChainBlockNumber; @@ -44,7 +44,9 @@ use sp_core::storage::well_known_keys; use sp_inherents::{InherentData, InherentIdentifier, ProvideInherent}; use sp_std::vec::Vec; -type System = frame_system::Module; +mod relay_state_snapshot; + +pub use relay_state_snapshot::MessagingStateSnapshot; /// The pallet's configuration trait. pub trait Config: frame_system::Config { @@ -53,6 +55,9 @@ pub trait Config: frame_system::Config { /// Something which can be notified when the validation data is set. type OnValidationData: OnValidationData; + + /// Returns the parachain ID we are running with. + type SelfParaId: Get; } // This pallet's storage items. @@ -68,6 +73,25 @@ decl_storage! { /// Were the validation data set to notify the relay chain? DidSetValidationCode: bool; + + /// The last relay parent block number at which we signalled the code upgrade. + LastUpgrade: relay_chain::BlockNumber; + + /// The snapshot of some state related to messaging relevant to the current parachain as per + /// the relay parent. + /// + /// This field is meant to be updated each block with the validation data inherent. Therefore, + /// before processing of the inherent, e.g. in `on_initialize` this data may be stale. + /// + /// This data is also absent from the genesis. + RelevantMessagingState get(fn relevant_messaging_state): Option; + /// The parachain host configuration that was obtained from the relay parent. + /// + /// This field is meant to be updated each block with the validation data inherent. Therefore, + /// before processing of the inherent, e.g. in `on_initialize` this data may be stale. + /// + /// This data is also absent from the genesis. + HostConfiguration get(fn host_configuration): Option; } } @@ -82,7 +106,7 @@ decl_module! { #[weight = (0, DispatchClass::Operational)] pub fn schedule_upgrade(origin, validation_function: Vec) { ensure_root(origin)?; - System::::can_set_code(&validation_function)?; + >::can_set_code(&validation_function)?; Self::schedule_upgrade_impl(validation_function)?; } @@ -106,7 +130,7 @@ decl_module! { /// As a side effect, this function upgrades the current validation function /// if the appropriate time has come. #[weight = (0, DispatchClass::Mandatory)] - fn set_validation_data(origin, data: ValidationDataType) { + fn set_validation_data(origin, data: ValidationDataType) -> DispatchResult { ensure_none(origin)?; assert!(!DidUpdateValidationData::exists(), "ValidationData must be updated only once in a block"); @@ -121,18 +145,31 @@ decl_module! { if let Some((apply_block, validation_function)) = PendingValidationFunction::get() { if vfp.persisted.block_number >= apply_block { PendingValidationFunction::kill(); + LastUpgrade::put(&apply_block); Self::put_parachain_code(&validation_function); Self::deposit_event(Event::ValidationFunctionApplied(vfp.persisted.block_number)); } } + let (host_config, relevant_messaging_state) = + relay_state_snapshot::extract_from_proof( + T::SelfParaId::get(), + vfp.persisted.relay_storage_root, + relay_chain_state + ) + .map_err(|err| { + frame_support::debug::print!("invalid relay chain merkle proof: {:?}", err); + Error::::InvalidRelayChainMerkleProof + })?; + storage::unhashed::put(VALIDATION_DATA, &vfp); DidUpdateValidationData::put(true); - - // TODO: here we should extract the key value pairs out of the storage proof. - drop(relay_chain_state); + RelevantMessagingState::put(relevant_messaging_state); + HostConfiguration::put(host_config); ::on_validation_data(vfp); + + Ok(()) } fn on_finalize() { @@ -177,35 +214,53 @@ impl Module { storage::unhashed::put_raw(well_known_keys::CODE, code); } - /// `true` when a code upgrade is currently legal - pub fn can_set_code() -> bool { - Self::validation_data() - .map(|vfp| vfp.transient.code_upgrade_allowed.is_some()) - .unwrap_or_default() + /// The maximum code size permitted, in bytes. + /// + /// Returns `None` if the relay chain parachain host configuration hasn't been submitted yet. + pub fn max_code_size() -> Option { + HostConfiguration::get().map(|cfg| cfg.max_code_size) } - /// The maximum code size permitted, in bytes. - pub fn max_code_size() -> Option { - Self::validation_data().map(|vfp| vfp.transient.max_code_size) + /// Returns if a PVF/runtime upgrade could be signalled at the current block, and if so + /// when the new code will take the effect. + fn code_upgrade_allowed( + vfp: &ValidationData, + cfg: &AbridgedHostConfiguration, + ) -> Option { + if PendingValidationFunction::get().is_some() { + // There is already upgrade scheduled. Upgrade is not allowed. + return None; + } + + let relay_blocks_since_last_upgrade = vfp + .persisted + .block_number + .saturating_sub(LastUpgrade::get()); + + if relay_blocks_since_last_upgrade <= cfg.validation_upgrade_frequency { + // The cooldown after the last upgrade hasn't elapsed yet. Upgrade is not allowed. + return None; + } + + Some(vfp.persisted.block_number + cfg.validation_upgrade_delay) } /// The implementation of the runtime upgrade scheduling. fn schedule_upgrade_impl( validation_function: Vec, - ) -> frame_support::dispatch::DispatchResult { + ) -> DispatchResult { ensure!( !PendingValidationFunction::exists(), Error::::OverlappingUpgrades ); let vfp = Self::validation_data().ok_or(Error::::ValidationDataNotAvailable)?; + let cfg = Self::host_configuration().ok_or(Error::::HostConfigurationNotAvailable)?; ensure!( - validation_function.len() <= vfp.transient.max_code_size as usize, + validation_function.len() <= cfg.max_code_size as usize, Error::::TooBig ); - let apply_block = vfp - .transient - .code_upgrade_allowed - .ok_or(Error::::ProhibitedByPolkadot)?; + let apply_block = + Self::code_upgrade_allowed(&vfp, &cfg).ok_or(Error::::ProhibitedByPolkadot)?; // When a code upgrade is scheduled, it has to be applied in two // places, synchronized: both polkadot and the individual parachain @@ -257,6 +312,10 @@ decl_error! { TooBig, /// The inherent which supplies the validation data did not run this block ValidationDataNotAvailable, + /// The inherent which supplies the host configuration did not run this block + HostConfigurationNotAvailable, + /// Invalid relay-chain storage merkle proof + InvalidRelayChainMerkleProof, } } @@ -267,6 +326,7 @@ mod tests { use codec::Encode; use cumulus_primitives::{PersistedValidationData, TransientValidationData}; + use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use frame_support::{ assert_ok, dispatch::UnfilteredDispatchable, @@ -312,6 +372,7 @@ mod tests { apis: sp_version::create_apis_vec!([]), transaction_version: 1, }; + pub const ParachainId: ParaId = ParaId::new(200); } impl frame_system::Config for Test { type Origin = Origin; @@ -340,9 +401,11 @@ mod tests { impl Config for Test { type Event = TestEvent; type OnValidationData = (); + type SelfParaId = ParachainId; } type ParachainUpgrade = Module; + type System = frame_system::Module; // This function basically just builds a genesis storage key/value store according to // our desired mockup. @@ -398,7 +461,9 @@ mod tests { tests: Vec, pending_upgrade: Option, ran: bool, - vfp_maker: Option ValidationData>>, + relay_sproof_builder_hook: Option< + Box + >, } impl BlockTests { @@ -439,11 +504,11 @@ mod tests { }) } - fn with_validation_data(mut self, f: F) -> Self + fn with_relay_sproof_builder(mut self, f: F) -> Self where - F: 'static + Fn(&BlockTests, RelayChainBlockNumber) -> ValidationData, + F: 'static + Fn(&BlockTests, RelayChainBlockNumber, &mut RelayStateSproofBuilder) { - self.vfp_maker = Some(Box::new(f)); + self.relay_sproof_builder_hook = Some(Box::new(f)); self } @@ -464,7 +529,7 @@ mod tests { } // begin initialization - System::::initialize( + System::initialize( &n, &Default::default(), &Default::default(), @@ -472,24 +537,21 @@ mod tests { ); // now mess with the storage the way validate_block does - let vfp = match self.vfp_maker { - None => ValidationData { - persisted: PersistedValidationData { - block_number: *n as RelayChainBlockNumber, - ..Default::default() - }, - transient: TransientValidationData { - max_code_size: 10 * 1024 * 1024, // 10 mb - code_upgrade_allowed: if self.pending_upgrade.is_some() { - None - } else { - Some(*n as RelayChainBlockNumber + 1000) - }, - ..Default::default() - }, + let mut sproof_builder = RelayStateSproofBuilder::default(); + if let Some(ref hook) = self.relay_sproof_builder_hook { + hook(self, *n as RelayChainBlockNumber, &mut sproof_builder); + } + let (relay_storage_root, relay_chain_state) = + sproof_builder.into_state_root_and_proof(); + let vfp = ValidationData { + persisted: PersistedValidationData { + block_number: *n as RelayChainBlockNumber, + relay_storage_root, + ..Default::default() }, - Some(ref maker) => maker(self, *n as RelayChainBlockNumber), + transient: TransientValidationData::default(), }; + storage::unhashed::put(VALIDATION_DATA, &vfp); storage::unhashed::kill(NEW_VALIDATION_CODE); @@ -498,13 +560,10 @@ mod tests { let inherent_data = { let mut inherent_data = InherentData::default(); inherent_data - .put_data( - INHERENT_IDENTIFIER, - &ValidationDataType { - validation_data: vfp.clone(), - relay_chain_state: sp_state_machine::StorageProof::empty(), - }, - ) + .put_data(INHERENT_IDENTIFIER, &ValidationDataType { + validation_data: vfp.clone(), + relay_chain_state, + }) .expect("failed to put VFP inherent"); inherent_data }; @@ -527,7 +586,7 @@ mod tests { } // clean up - System::::finalize(); + System::finalize(); if let Some(after_block) = after_block { after_block(); } @@ -575,6 +634,9 @@ mod tests { #[test] fn events() { BlockTests::new() + .with_relay_sproof_builder(|_, _, builder| { + builder.host_config.validation_upgrade_delay = 1000; + }) .add_with_post_test( 123, || { @@ -584,7 +646,7 @@ mod tests { )); }, || { - let events = System::::events(); + let events = System::events(); assert_eq!( events[0].event, TestEvent::parachain_upgrade(Event::ValidationFunctionStored(1123)) @@ -595,7 +657,7 @@ mod tests { 1234, || {}, || { - let events = System::::events(); + let events = System::events(); assert_eq!( events[0].event, TestEvent::parachain_upgrade(Event::ValidationFunctionApplied(1234)) @@ -607,6 +669,9 @@ mod tests { #[test] fn non_overlapping() { BlockTests::new() + .with_relay_sproof_builder(|_, _, builder| { + builder.host_config.validation_upgrade_delay = 1000; + }) .add(123, || { assert_ok!(ParachainUpgrade::schedule_upgrade( RawOrigin::Root.into(), @@ -615,7 +680,7 @@ mod tests { }) .add(234, || { assert_eq!( - ParachainUpgrade::schedule_upgrade(RawOrigin::Root.into(), Default::default(),), + ParachainUpgrade::schedule_upgrade(RawOrigin::Root.into(), Default::default()), Err(Error::::OverlappingUpgrades.into()), ) }); @@ -653,16 +718,8 @@ mod tests { #[test] fn checks_size() { BlockTests::new() - .with_validation_data(|_, n| ValidationData { - persisted: PersistedValidationData { - block_number: n, - ..Default::default() - }, - transient: TransientValidationData { - max_code_size: 32, - code_upgrade_allowed: Some(n + 1000), - ..Default::default() - }, + .with_relay_sproof_builder(|_, _, builder| { + builder.host_config.max_code_size = 8; }) .add(123, || { assert_eq!( diff --git a/parachain-upgrade/src/relay_state_snapshot.rs b/parachain-upgrade/src/relay_state_snapshot.rs new file mode 100644 index 0000000000..8391a3e4c4 --- /dev/null +++ b/parachain-upgrade/src/relay_state_snapshot.rs @@ -0,0 +1,150 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +use codec::{Encode, Decode}; +use cumulus_primitives::{relay_chain, AbridgedHostConfiguration, AbridgedHrmpChannel, ParaId}; +use hash_db::{HashDB, EMPTY_PREFIX}; +use sp_runtime::traits::HashFor; +use sp_state_machine::{Backend, TrieBackend}; +use sp_trie::StorageProof; +use sp_std::vec::Vec; + +/// A snapshot of some messaging related state of relay chain pertaining to the current parachain. +/// +/// This data is essential for making sure that the parachain is aware of current resource use on +/// the relay chain and that the candidates produced for this parachain do not exceed any of these +/// limits. +#[derive(Encode, Decode)] +pub struct MessagingStateSnapshot { + /// The current capacity of the upward message queue of the current parachain on the relay chain. + /// + /// The capacity is represented by a tuple that consist of the `count` of the messages and the + /// `total_size` expressed as the sum of byte sizes of all messages in the queue. + pub relay_dispatch_queue_size: (u32, u32), + + /// Information about all the outbound HRMP channels. + /// + /// These are structured as a list of tuples. The para id in the tuple specifies the recipient + /// of the channel. Obviously, the sender is the current parachain. + /// + /// The channels are sorted by the recipient para id ascension. + pub egress_channels: Vec<(ParaId, AbridgedHrmpChannel)>, +} + +#[derive(Debug)] +pub enum Error { + /// The provided proof was created against unexpected storage root. + RootMismatch, + /// The host configuration cannot be extracted. + Config(ReadEntryErr), + /// Relay dispatch queue cannot be extracted. + RelayDispatchQueueSize(ReadEntryErr), + /// The hrmp egress channel index cannot be extracted. + HrmpEgressChannelIndex(ReadEntryErr), + /// The hrmp channel for the given recipient cannot be extracted. + HrmpChannel(ParaId, ReadEntryErr), +} + +#[derive(Debug)] +pub enum ReadEntryErr { + /// The value cannot be extracted from the proof. + Proof, + /// The value cannot be decoded. + Decode, + /// The value is expected to be present on the relay chain, but it doesn't exist. + Absent, +} + +/// Read an entry given by the key and try to decode it. If the value specified by the key according +/// to the proof is empty, the `fallback` value will be returned. +/// +/// Returns `Err` in case the backend can't return the value under the specific key (likely due to +/// a malformed proof), in case the decoding fails, or in case where the value is empty in the relay +/// chain state and no fallback was provided. +fn read_entry(backend: &B, key: &[u8], fallback: Option) -> Result +where + T: Decode, + B: Backend>, +{ + backend + .storage(key) + .map_err(|_| ReadEntryErr::Proof)? + .map(|raw_entry| T::decode(&mut &raw_entry[..]).map_err(|_| ReadEntryErr::Decode)) + .transpose()? + .or(fallback) + .ok_or(ReadEntryErr::Absent) +} + +/// Extract the relay chain state from the given storage proof. This function accepts the `para_id` +/// of the current parachain and the expected storage root the proof should stem from. +pub fn extract_from_proof( + para_id: ParaId, + relay_storage_root: relay_chain::v1::Hash, + proof: StorageProof, +) -> Result<(AbridgedHostConfiguration, MessagingStateSnapshot), Error> { + let db = proof.into_memory_db::>(); + if !db.contains(&relay_storage_root, EMPTY_PREFIX) { + return Err(Error::RootMismatch); + } + let backend = TrieBackend::new(db, relay_storage_root); + + let host_config: AbridgedHostConfiguration = read_entry( + &backend, + relay_chain::well_known_keys::ACTIVE_CONFIG, + None, + ) + .map_err(Error::Config)?; + + let relay_dispatch_queue_size: (u32, u32) = read_entry( + &backend, + &relay_chain::well_known_keys::relay_dispatch_queue_size(para_id), + Some((0, 0)), + ) + .map_err(Error::RelayDispatchQueueSize)?; + + let egress_channel_index: Vec = read_entry( + &backend, + &relay_chain::well_known_keys::hrmp_egress_channel_index(para_id), + Some(Vec::new()), + ) + .map_err(Error::HrmpEgressChannelIndex)?; + + let mut egress_channels = Vec::with_capacity(egress_channel_index.len()); + for recipient in egress_channel_index { + let channel_id = relay_chain::v1::HrmpChannelId { + sender: para_id, + recipient, + }; + let hrmp_channel: AbridgedHrmpChannel = read_entry( + &backend, + &relay_chain::well_known_keys::hrmp_channels(channel_id), + None, + ) + .map_err(|read_err| Error::HrmpChannel(recipient, read_err))?; + egress_channels.push((recipient, hrmp_channel)); + } + + // NOTE that egress_channels promises to be sorted. We satisfy this property by relying on + // the fact that `egress_channel_index` is itself sorted. + + Ok(( + host_config, + MessagingStateSnapshot { + relay_dispatch_queue_size, + egress_channels, + }, + )) +} diff --git a/primitives/src/lib.rs b/primitives/src/lib.rs index 5b21288667..51a1a292c1 100644 --- a/primitives/src/lib.rs +++ b/primitives/src/lib.rs @@ -18,16 +18,23 @@ #![cfg_attr(not(feature = "std"), no_std)] -pub use polkadot_core_primitives as relay_chain; pub use polkadot_core_primitives::InboundDownwardMessage; pub use polkadot_parachain::primitives::{Id as ParaId, UpwardMessage, ValidationParams}; pub use polkadot_primitives::v1::{ - PersistedValidationData, TransientValidationData, ValidationData, + PersistedValidationData, TransientValidationData, ValidationData, AbridgedHostConfiguration, + AbridgedHrmpChannel, }; #[cfg(feature = "std")] pub mod genesis; +/// A module that re-exports relevant relay chain definitions. +pub mod relay_chain { + pub use polkadot_core_primitives::*; + pub use polkadot_primitives::v1; + pub use polkadot_primitives::v1::well_known_keys; +} + /// An inbound HRMP message. pub type InboundHrmpMessage = polkadot_primitives::v1::InboundHrmpMessage; @@ -62,7 +69,12 @@ pub mod inherents { pub validation_data: crate::ValidationData, /// A storage proof of a predefined set of keys from the relay-chain. /// - /// The set of keys is TBD + /// Specifically this witness contains the data for: + /// + /// - active host configuration as per the relay parent, + /// - the relay dispatch queue sizes + /// - the list of egress HRMP channels (in the list of recipients form) + /// - the metadata for the egress HRMP channels pub relay_chain_state: sp_trie::StorageProof, } } diff --git a/rococo-parachains/runtime/src/lib.rs b/rococo-parachains/runtime/src/lib.rs index 54c9b8ee73..509b4f9198 100644 --- a/rococo-parachains/runtime/src/lib.rs +++ b/rococo-parachains/runtime/src/lib.rs @@ -232,6 +232,7 @@ impl pallet_sudo::Config for Runtime { impl cumulus_parachain_upgrade::Config for Runtime { type Event = Event; type OnValidationData = (); + type SelfParaId = parachain_info::Module; } impl cumulus_message_broker::Config for Runtime { diff --git a/runtime/Cargo.toml b/runtime/Cargo.toml index 6f0f7432e7..a28f534576 100644 --- a/runtime/Cargo.toml +++ b/runtime/Cargo.toml @@ -33,6 +33,7 @@ sp-keyring = { git = "https://github.com/paritytech/substrate", branch = "master sp-blockchain = { git = "https://github.com/paritytech/substrate", branch = "master" } sc-executor = { git = "https://github.com/paritytech/substrate", branch = "master" } sp-consensus = { git = "https://github.com/paritytech/substrate", branch = "master" } +cumulus-test-relay-sproof-builder = { path = "../test/relay-sproof-builder" } cumulus-test-client = { path = "../test/client" } env_logger = "0.7.1" diff --git a/runtime/src/validate_block/implementation.rs b/runtime/src/validate_block/implementation.rs index a5e5614d1d..50ee63233c 100644 --- a/runtime/src/validate_block/implementation.rs +++ b/runtime/src/validate_block/implementation.rs @@ -215,6 +215,14 @@ impl<'a, B: BlockT> WitnessExt<'a, B> { self.params.hrmp_mqc_heads, validation_data.persisted.hrmp_mqc_heads ); + assert_eq!( + self.params.dmq_mqc_head, + validation_data.persisted.dmq_mqc_head, + ); + assert_eq!( + self.params.relay_storage_root, + validation_data.persisted.relay_storage_root, + ); } } diff --git a/runtime/src/validate_block/tests.rs b/runtime/src/validate_block/tests.rs index 6e640021cf..42ca15c21d 100644 --- a/runtime/src/validate_block/tests.rs +++ b/runtime/src/validate_block/tests.rs @@ -22,6 +22,7 @@ use cumulus_test_client::{ transfer, Client, DefaultTestClientBuilderExt, InitBlockBuilder, LongestChain, TestClientBuilder, TestClientBuilderExt, }; +use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use parachain::primitives::{BlockData, HeadData, ValidationParams, ValidationResult}; use sc_executor::{ error::Result, sp_wasm_interface::HostFunctions, WasmExecutionMethod, WasmExecutor, @@ -41,6 +42,7 @@ use codec::{Decode, Encode}; fn call_validate_block( parent_head: Header, block_data: ParachainBlockData, + relay_storage_root: Hash, ) -> Result
{ let mut ext = TestExternalities::default(); let mut ext_ext = ext.ext(); @@ -48,9 +50,9 @@ fn call_validate_block( block_data: BlockData(block_data.encode()), parent_head: HeadData(parent_head.encode()), relay_chain_height: 1, + relay_storage_root, hrmp_mqc_heads: Vec::new(), dmq_mqc_head: Default::default(), - relay_storage_root: Default::default(), } .encode(); @@ -82,11 +84,19 @@ fn create_test_client() -> (Client, LongestChain) { .build_with_longest_chain() } -fn build_block_with_proof( +struct TestBlockData { + block: Block, + witness: sp_trie::StorageProof, + relay_storage_root: Hash, +} + +fn build_block_with_witness( client: &Client, extra_extrinsics: Vec, parent_head: Header, -) -> (Block, sp_trie::StorageProof) { +) -> TestBlockData { + let sproof_builder = RelayStateSproofBuilder::default(); + let (relay_storage_root, _) = sproof_builder.clone().into_state_root_and_proof(); let block_id = BlockId::Hash(client.info().best_hash); let mut builder = client.init_block_builder_at( &block_id, @@ -98,6 +108,7 @@ fn build_block_with_proof( }, ..Default::default() }), + sproof_builder, ); extra_extrinsics @@ -106,12 +117,13 @@ fn build_block_with_proof( let built_block = builder.build().expect("Creates block"); - ( - built_block.block, - built_block + TestBlockData { + block: built_block.block, + witness: built_block .proof .expect("We enabled proof recording before."), - ) + relay_storage_root, + } } #[test] @@ -120,12 +132,17 @@ fn validate_block_no_extra_extrinsics() { let (client, longest_chain) = create_test_client(); let parent_head = longest_chain.best_chain().expect("Best block exists"); - let (block, witness_data) = build_block_with_proof(&client, vec![], parent_head.clone()); + let TestBlockData { + block, + witness, + relay_storage_root, + } = build_block_with_witness(&client, vec![], parent_head.clone()); let (header, extrinsics) = block.deconstruct(); - let block_data = ParachainBlockData::new(header.clone(), extrinsics, witness_data); + let block_data = ParachainBlockData::new(header.clone(), extrinsics, witness); - let res_header = call_validate_block(parent_head, block_data).expect("Calls `validate_block`"); + let res_header = call_validate_block(parent_head, block_data, relay_storage_root) + .expect("Calls `validate_block`"); assert_eq!(header, res_header); } @@ -141,13 +158,17 @@ fn validate_block_with_extra_extrinsics() { transfer(&client, Charlie, Alice, 500), ]; - let (block, witness_data) = - build_block_with_proof(&client, extra_extrinsics, parent_head.clone()); + let TestBlockData { + block, + witness, + relay_storage_root, + } = build_block_with_witness(&client, extra_extrinsics, parent_head.clone()); let (header, extrinsics) = block.deconstruct(); - let block_data = ParachainBlockData::new(header.clone(), extrinsics, witness_data); + let block_data = ParachainBlockData::new(header.clone(), extrinsics, witness); - let res_header = call_validate_block(parent_head, block_data).expect("Calls `validate_block`"); + let res_header = call_validate_block(parent_head, block_data, relay_storage_root) + .expect("Calls `validate_block`"); assert_eq!(header, res_header); } @@ -158,10 +179,15 @@ fn validate_block_invalid_parent_hash() { let (client, longest_chain) = create_test_client(); let parent_head = longest_chain.best_chain().expect("Best block exists"); - let (block, witness_data) = build_block_with_proof(&client, vec![], parent_head.clone()); + let TestBlockData { + block, + witness, + relay_storage_root, + } = build_block_with_witness(&client, vec![], parent_head.clone()); let (mut header, extrinsics) = block.deconstruct(); header.set_parent_hash(Hash::from_low_u64_be(1)); - let block_data = ParachainBlockData::new(header, extrinsics, witness_data); - call_validate_block(parent_head, block_data).expect("Calls `validate_block`"); + let block_data = ParachainBlockData::new(header, extrinsics, witness); + call_validate_block(parent_head, block_data, relay_storage_root) + .expect("Calls `validate_block`"); } diff --git a/test/client/Cargo.toml b/test/client/Cargo.toml index bf9b7d74c1..3872860eea 100644 --- a/test/client/Cargo.toml +++ b/test/client/Cargo.toml @@ -25,6 +25,7 @@ pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "m # Cumulus deps cumulus-test-runtime = { path = "../runtime" } cumulus-test-service = { path = "../service" } +cumulus-test-relay-sproof-builder = { path = "../relay-sproof-builder" } cumulus-primitives = { path = "../../primitives" } # Polkadot deps diff --git a/test/client/src/block_builder.rs b/test/client/src/block_builder.rs index a25fa620cb..1748e6aaaa 100644 --- a/test/client/src/block_builder.rs +++ b/test/client/src/block_builder.rs @@ -20,6 +20,7 @@ use cumulus_primitives::{ ValidationData, }; use cumulus_test_runtime::{Block, GetLastTimestamp}; +use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder; use polkadot_primitives::v1::BlockNumber as PBlockNumber; use sc_block_builder::{BlockBuilder, BlockBuilderProvider}; use sp_api::ProvideRuntimeApi; @@ -31,9 +32,13 @@ pub trait InitBlockBuilder { /// /// This will automatically create and push the inherents for you to make the block /// valid for the test runtime. + /// + /// You can use the relay chain state sproof builder to arrange required relay chain state or + /// just use a default one. fn init_block_builder( &self, validation_data: Option>, + relay_sproof_builder: RelayStateSproofBuilder, ) -> sc_block_builder::BlockBuilder; /// Init a specific block builder at a specific block that works for the test runtime. @@ -44,6 +49,7 @@ pub trait InitBlockBuilder { &self, at: &BlockId, validation_data: Option>, + relay_sproof_builder: RelayStateSproofBuilder, ) -> sc_block_builder::BlockBuilder; } @@ -51,15 +57,21 @@ impl InitBlockBuilder for Client { fn init_block_builder( &self, validation_data: Option>, + relay_sproof_builder: RelayStateSproofBuilder, ) -> BlockBuilder { let chain_info = self.chain_info(); - self.init_block_builder_at(&BlockId::Hash(chain_info.best_hash), validation_data) + self.init_block_builder_at( + &BlockId::Hash(chain_info.best_hash), + validation_data, + relay_sproof_builder, + ) } fn init_block_builder_at( &self, at: &BlockId, validation_data: Option>, + relay_sproof_builder: RelayStateSproofBuilder, ) -> BlockBuilder { let mut block_builder = self .new_block_at(at, Default::default(), true) @@ -76,12 +88,24 @@ impl InitBlockBuilder for Client { inherent_data .put_data(sp_timestamp::INHERENT_IDENTIFIER, ×tamp) .expect("Put timestamp failed"); + + let (relay_storage_root, relay_chain_state) = + relay_sproof_builder.into_state_root_and_proof(); + + let mut validation_data = validation_data.unwrap_or_default(); + assert_eq!( + validation_data.persisted.relay_storage_root, + Default::default(), + "Overriding the relay storage root is not implemented", + ); + validation_data.persisted.relay_storage_root = relay_storage_root; + inherent_data .put_data( VALIDATION_DATA_IDENTIFIER, &ValidationDataType { - validation_data: validation_data.unwrap_or_default(), - relay_chain_state: sp_state_machine::StorageProof::empty(), + validation_data, + relay_chain_state, }, ) .expect("Put validation function params failed"); diff --git a/test/relay-sproof-builder/Cargo.toml b/test/relay-sproof-builder/Cargo.toml new file mode 100644 index 0000000000..11bb442236 --- /dev/null +++ b/test/relay-sproof-builder/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "cumulus-test-relay-sproof-builder" +version = '0.1.0' +authors = ["Parity Technologies "] +edition = '2018' + +[dependencies] +# Other dependencies +codec = { package = "parity-scale-codec", version = "1.0.5", default-features = false, features = [ "derive" ] } + +# Substrate dependencies +sp-state-machine = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } +sp-runtime = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" } + +# Polkadot dependencies +polkadot-primitives = { git = "https://github.com/paritytech/polkadot", default-features = false, branch = "master" } + +# Cumulus dependencies +cumulus-primitives = { path = "../../primitives", default-features = false } + +[features] +default = [ "std" ] +std = [ + "codec/std", + "sp-state-machine/std", + "sp-runtime/std", + "cumulus-primitives/std", +] diff --git a/test/relay-sproof-builder/src/lib.rs b/test/relay-sproof-builder/src/lib.rs new file mode 100644 index 0000000000..e240385463 --- /dev/null +++ b/test/relay-sproof-builder/src/lib.rs @@ -0,0 +1,74 @@ +// Copyright 2021 Parity Technologies (UK) Ltd. +// This file is part of Cumulus. + +// Cumulus 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. + +// Cumulus 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 Cumulus. If not, see . + +use sp_runtime::traits::HashFor; +use sp_state_machine::MemoryDB; +use cumulus_primitives::relay_chain; + +/// Builds a sproof (portmanteau of 'spoof' and 'proof') of the relay chain state. +#[derive(Clone)] +pub struct RelayStateSproofBuilder { + pub host_config: cumulus_primitives::AbridgedHostConfiguration, +} + +impl Default for RelayStateSproofBuilder { + fn default() -> Self { + RelayStateSproofBuilder { + host_config: cumulus_primitives::AbridgedHostConfiguration { + max_code_size: 2 * 1024 * 1024, + max_head_data_size: 1024 * 1024, + max_upward_queue_count: 8, + max_upward_queue_size: 1024, + max_upward_message_size: 256, + max_upward_message_num_per_candidate: 5, + hrmp_max_message_num_per_candidate: 5, + validation_upgrade_frequency: 6, + validation_upgrade_delay: 6, + }, + } + } +} + +impl RelayStateSproofBuilder { + pub fn into_state_root_and_proof( + self, + ) -> ( + polkadot_primitives::v1::Hash, + sp_state_machine::StorageProof, + ) { + let (db, root) = MemoryDB::>::default_with_root(); + let mut backend = sp_state_machine::TrieBackend::new(db, root); + + let mut relevant_keys = vec![]; + { + use codec::Encode as _; + + let mut insert = |key: Vec, value: Vec| { + relevant_keys.push(key.clone()); + backend.insert(vec![(None, vec![(key, Some(value))])]); + }; + + insert( + relay_chain::well_known_keys::ACTIVE_CONFIG.to_vec(), + self.host_config.encode(), + ); + } + + let root = backend.root().clone(); + let proof = sp_state_machine::prove_read(backend, relevant_keys).expect("prove read"); + (root, proof) + } +} diff --git a/test/runtime/src/lib.rs b/test/runtime/src/lib.rs index 483452284f..5bcf615df0 100644 --- a/test/runtime/src/lib.rs +++ b/test/runtime/src/lib.rs @@ -208,6 +208,7 @@ impl pallet_sudo::Config for Runtime { } impl cumulus_parachain_upgrade::Config for Runtime { + type SelfParaId = ParachainId; type Event = Event; type OnValidationData = (); }