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 = (); }