From 229000c5fc8f5d7833c9cd1646d399c0acf19409 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Bastian=20K=C3=B6cher?= <bkchr@users.noreply.github.com>
Date: Fri, 24 Dec 2021 18:06:36 +0100
Subject: [PATCH] Mock XCM (#876)
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

* sketch downward messages

* bring in attempt to mock mqc-head from moonbeam

* just patch individual crates

* fing comma

* add some logs

* Holy shit, we actually imported a block!

* Actually mock the message queue chain

* use relay parent number for `sent_at`

* finish moving MQC to primitives

* more complete mock and better config type

* change name

* fix export

* better map types

* fix dependencies after rebase

* try-rejigging branches because this is an override

* try to re-jig for hrmp mcqs

* fix branches

* actually fix branches better

* even better

* Removestray log lines

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* Nicer handling of default `ParachainSystem` name

* better docs

* Default MockXcm for people who only who don't care to mock xcm.

* cargo fmt

* trailing commas

* Apply suggestions from code review

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* use the variable for hrmp to

* fix deref

* deduplicate MessageQueueChain

* better docs for MessageQueueChain

* Use `Vec<u8>` instead of `&'static [u8]`

Co-authored-by: Bastian Köcher <bkchr@users.noreply.github.com>

* cargo fmt

* associated changes for using Vec<u8>

* Unused import

* Fix compilation

Co-authored-by: Joshy Orndorff <admin@joshyorndorff.com>
Co-authored-by: Joshy Orndorff <JoshOrndorff@users.noreply.github.com>
---
 cumulus/Cargo.lock                            |   1 +
 cumulus/pallets/parachain-system/src/lib.rs   |  45 +----
 cumulus/pallets/parachain-system/src/tests.rs |   5 +-
 .../primitives/parachain-inherent/Cargo.toml  |   3 +
 .../primitives/parachain-inherent/src/lib.rs  |  50 +++++-
 .../primitives/parachain-inherent/src/mock.rs | 161 ++++++++++++++++--
 6 files changed, 205 insertions(+), 60 deletions(-)

diff --git a/cumulus/Cargo.lock b/cumulus/Cargo.lock
index 2b55b95a469..9397bb398d9 100644
--- a/cumulus/Cargo.lock
+++ b/cumulus/Cargo.lock
@@ -1795,6 +1795,7 @@ dependencies = [
  "sp-runtime",
  "sp-state-machine",
  "sp-std",
+ "sp-storage",
  "sp-trie",
  "tracing",
 ]
diff --git a/cumulus/pallets/parachain-system/src/lib.rs b/cumulus/pallets/parachain-system/src/lib.rs
index f096e742f1a..fe2cdb61d00 100644
--- a/cumulus/pallets/parachain-system/src/lib.rs
+++ b/cumulus/pallets/parachain-system/src/lib.rs
@@ -33,7 +33,7 @@ use cumulus_primitives_core::{
 	OutboundHrmpMessage, ParaId, PersistedValidationData, UpwardMessage, UpwardMessageSender,
 	XcmpMessageHandler, XcmpMessageSource,
 };
-use cumulus_primitives_parachain_inherent::ParachainInherentData;
+use cumulus_primitives_parachain_inherent::{MessageQueueChain, ParachainInherentData};
 use frame_support::{
 	dispatch::{DispatchError, DispatchResult},
 	ensure,
@@ -46,7 +46,7 @@ use frame_system::{ensure_none, ensure_root};
 use polkadot_parachain::primitives::RelayChainBlockNumber;
 use relay_state_snapshot::MessagingStateSnapshot;
 use sp_runtime::{
-	traits::{BlakeTwo256, Block as BlockT, BlockNumberProvider, Hash},
+	traits::{Block as BlockT, BlockNumberProvider, Hash},
 	transaction_validity::{
 		InvalidTransaction, TransactionLongevity, TransactionSource, TransactionValidity,
 		ValidTransaction,
@@ -750,7 +750,7 @@ impl<T: Config> Pallet<T> {
 			weight_used += T::DmpMessageHandler::handle_dmp_messages(message_iter, max_weight);
 			<LastDmqMqcHead<T>>::put(&dmq_head);
 
-			Self::deposit_event(Event::DownwardMessagesProcessed(weight_used, dmq_head.0));
+			Self::deposit_event(Event::DownwardMessagesProcessed(weight_used, dmq_head.head()));
 		}
 
 		// After hashing each message in the message queue chain submitted by the collator, we
@@ -758,7 +758,7 @@ impl<T: Config> Pallet<T> {
 		//
 		// A mismatch means that at least some of the submitted messages were altered, omitted or
 		// added improperly.
-		assert_eq!(dmq_head.0, expected_dmq_mqc_head);
+		assert_eq!(dmq_head.head(), expected_dmq_mqc_head);
 
 		ProcessedDownwardMessages::<T>::put(dm_count);
 
@@ -945,43 +945,6 @@ impl<T: Config> frame_system::SetCode<T> for ParachainSetCode<T> {
 	}
 }
 
-/// This struct provides ability to extend a message queue chain (MQC) and compute a new head.
-///
-/// MQC is an instance of a [hash chain] applied to a message queue. Using a hash chain it's
-/// possible to represent a sequence of messages using only a single hash.
-///
-/// A head for an empty chain is agreed to be a zero hash.
-///
-/// [hash chain]: https://en.wikipedia.org/wiki/Hash_chain
-#[derive(Default, Clone, codec::Encode, codec::Decode, scale_info::TypeInfo)]
-struct MessageQueueChain(relay_chain::Hash);
-
-impl MessageQueueChain {
-	fn extend_hrmp(&mut self, horizontal_message: &InboundHrmpMessage) -> &mut Self {
-		let prev_head = self.0;
-		self.0 = BlakeTwo256::hash_of(&(
-			prev_head,
-			horizontal_message.sent_at,
-			BlakeTwo256::hash_of(&horizontal_message.data),
-		));
-		self
-	}
-
-	fn extend_downward(&mut self, downward_message: &InboundDownwardMessage) -> &mut Self {
-		let prev_head = self.0;
-		self.0 = BlakeTwo256::hash_of(&(
-			prev_head,
-			downward_message.sent_at,
-			BlakeTwo256::hash_of(&downward_message.msg),
-		));
-		self
-	}
-
-	fn head(&self) -> relay_chain::Hash {
-		self.0
-	}
-}
-
 impl<T: Config> Pallet<T> {
 	pub fn send_upward_message(message: UpwardMessage) -> Result<u32, MessageSendError> {
 		// Check if the message fits into the relay-chain constraints.
diff --git a/cumulus/pallets/parachain-system/src/tests.rs b/cumulus/pallets/parachain-system/src/tests.rs
index 1351e2d98d2..3b8444878be 100755
--- a/cumulus/pallets/parachain-system/src/tests.rs
+++ b/cumulus/pallets/parachain-system/src/tests.rs
@@ -33,7 +33,10 @@ use frame_system::{InitKind, RawOrigin};
 use hex_literal::hex;
 use relay_chain::v1::HrmpChannelId;
 use sp_core::H256;
-use sp_runtime::{testing::Header, traits::IdentityLookup};
+use sp_runtime::{
+	testing::Header,
+	traits::{BlakeTwo256, IdentityLookup},
+};
 use sp_version::RuntimeVersion;
 use std::cell::RefCell;
 
diff --git a/cumulus/primitives/parachain-inherent/Cargo.toml b/cumulus/primitives/parachain-inherent/Cargo.toml
index 0644bcca420..faca11a9152 100644
--- a/cumulus/primitives/parachain-inherent/Cargo.toml
+++ b/cumulus/primitives/parachain-inherent/Cargo.toml
@@ -14,6 +14,7 @@ sp-std = { git = "https://github.com/paritytech/substrate", default-features = f
 sp-state-machine = { git = "https://github.com/paritytech/substrate", optional = true , branch = "master" }
 sp-trie = { git = "https://github.com/paritytech/substrate", default-features = false, branch = "master" }
 sp-api = { git = "https://github.com/paritytech/substrate", optional = true , branch = "master" }
+sp-storage = { git = "https://github.com/paritytech/substrate", optional = true , branch = "master" }
 
 # Cumulus dependencies
 cumulus-primitives-core = { path = "../core", default-features = false }
@@ -42,6 +43,8 @@ std = [
 	"sp-runtime",
 	"sc-client-api",
 	"sp-api",
+	"sp-storage",
+	"cumulus-test-relay-sproof-builder",
 	"cumulus-relay-chain-interface",
 	"cumulus-test-relay-sproof-builder"
 ]
diff --git a/cumulus/primitives/parachain-inherent/src/lib.rs b/cumulus/primitives/parachain-inherent/src/lib.rs
index e61a794c2f1..4781f5e7081 100644
--- a/cumulus/primitives/parachain-inherent/src/lib.rs
+++ b/cumulus/primitives/parachain-inherent/src/lib.rs
@@ -28,6 +28,7 @@
 #![cfg_attr(not(feature = "std"), no_std)]
 
 use cumulus_primitives_core::{
+	relay_chain::{BlakeTwo256, Hash as RelayHash, HashT as _},
 	InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData,
 };
 
@@ -42,7 +43,7 @@ pub use client_side::*;
 #[cfg(feature = "std")]
 mod mock;
 #[cfg(feature = "std")]
-pub use mock::MockValidationDataInherentDataProvider;
+pub use mock::{MockValidationDataInherentDataProvider, MockXcmConfig};
 
 /// The identifier for the parachain inherent.
 pub const INHERENT_IDENTIFIER: InherentIdentifier = *b"sysi1337";
@@ -68,3 +69,50 @@ pub struct ParachainInherentData {
 	/// this means `sent_at` is **strictly** greater than the previous one (if any).
 	pub horizontal_messages: BTreeMap<ParaId, Vec<InboundHrmpMessage>>,
 }
+
+/// This struct provides ability to extend a message queue chain (MQC) and compute a new head.
+///
+/// MQC is an instance of a [hash chain] applied to a message queue. Using a hash chain it's
+/// possible to represent a sequence of messages using only a single hash.
+///
+/// A head for an empty chain is agreed to be a zero hash.
+///
+/// An instance is used to track either DMP from the relay chain or HRMP across a channel.
+/// But a given instance is never used to track both. Therefore, you should call either
+/// `extend_downward` or `extend_hrmp`, but not both methods on a single instance.
+///
+/// [hash chain]: https://en.wikipedia.org/wiki/Hash_chain
+#[derive(Default, Clone, codec::Encode, codec::Decode, scale_info::TypeInfo)]
+pub struct MessageQueueChain(RelayHash);
+
+impl MessageQueueChain {
+	/// Extend the hash chain with an HRMP message. This method should be used only when
+	/// this chain is tracking HRMP.
+	pub fn extend_hrmp(&mut self, horizontal_message: &InboundHrmpMessage) -> &mut Self {
+		let prev_head = self.0;
+		self.0 = BlakeTwo256::hash_of(&(
+			prev_head,
+			horizontal_message.sent_at,
+			BlakeTwo256::hash_of(&horizontal_message.data),
+		));
+		self
+	}
+
+	/// Extend the hash chain with a downward message. This method should be used only when
+	/// this chain is tracking DMP.
+	pub fn extend_downward(&mut self, downward_message: &InboundDownwardMessage) -> &mut Self {
+		let prev_head = self.0;
+		self.0 = BlakeTwo256::hash_of(&(
+			prev_head,
+			downward_message.sent_at,
+			BlakeTwo256::hash_of(&downward_message.msg),
+		));
+		self
+	}
+
+	/// Return the current mead of the message queue hash chain.
+	/// This is agreed to be the zero hash for an empty chain.
+	pub fn head(&self) -> RelayHash {
+		self.0
+	}
+}
diff --git a/cumulus/primitives/parachain-inherent/src/mock.rs b/cumulus/primitives/parachain-inherent/src/mock.rs
index 4bb341ff0f6..e69b0a80911 100644
--- a/cumulus/primitives/parachain-inherent/src/mock.rs
+++ b/cumulus/primitives/parachain-inherent/src/mock.rs
@@ -15,8 +15,16 @@
 // along with Cumulus.  If not, see <http://www.gnu.org/licenses/>.
 
 use crate::{ParachainInherentData, INHERENT_IDENTIFIER};
-use cumulus_primitives_core::PersistedValidationData;
+use codec::Decode;
+use cumulus_primitives_core::{
+	relay_chain, InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData,
+};
+use sc_client_api::{Backend, StorageProvider};
+use sp_api::BlockId;
+use sp_core::twox_128;
 use sp_inherents::{InherentData, InherentDataProvider};
+use sp_runtime::traits::Block;
+use std::collections::BTreeMap;
 
 use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
 
@@ -29,6 +37,11 @@ use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
 /// relay_block_number = offset + relay_blocks_per_para_block * current_para_block
 /// To simulate a parachain that starts in relay block 1000 and gets a block in every other relay
 /// block, use 1000 and 2
+///
+/// Optionally, mock XCM messages can be injected into the runtime. When mocking XCM,
+/// in addition to the messages themselves, you must provide some information about
+/// your parachain's configuration in order to mock the MQC heads properly.
+/// See [`MockXcmConfig`] for more information
 pub struct MockValidationDataInherentDataProvider {
 	/// The current block number of the local block chain (the parachain)
 	pub current_para_block: u32,
@@ -38,6 +51,82 @@ pub struct MockValidationDataInherentDataProvider {
 	/// The number of relay blocks that elapses between each parablock. Probably set this to 1 or 2
 	/// to simulate optimistic or realistic relay chain behavior.
 	pub relay_blocks_per_para_block: u32,
+	/// XCM messages and associated configuration information.
+	pub xcm_config: MockXcmConfig,
+	/// Inbound downward XCM messages to be injected into the block.
+	pub raw_downward_messages: Vec<Vec<u8>>,
+	// Inbound Horizontal messages sorted by channel
+	pub raw_horizontal_messages: Vec<(ParaId, Vec<u8>)>,
+}
+
+/// Parameters for how the Mock inherent data provider should inject XCM messages.
+/// In addition to the messages themselves, some information about the parachain's
+/// configuration is also required so that the MQC heads can be read out of the
+/// parachain's storage, and the corresponding relay data mocked.
+#[derive(Default)]
+pub struct MockXcmConfig {
+	/// The parachain id of the parachain being mocked.
+	pub para_id: ParaId,
+	/// The starting state of the dmq_mqc_head.
+	pub starting_dmq_mqc_head: relay_chain::Hash,
+	/// The starting state of each parachain's mqc head
+	pub starting_hrmp_mqc_heads: BTreeMap<ParaId, relay_chain::Hash>,
+}
+
+/// The name of the parachain system in the runtime.
+///
+/// This name is used by frame to prefix storage items and will be required to read data from the storage.
+///
+/// The `Default` implementation sets the name to `ParachainSystem`.
+pub struct ParachainSystemName(pub Vec<u8>);
+
+impl Default for ParachainSystemName {
+	fn default() -> Self {
+		Self(b"ParachainSystem".to_vec())
+	}
+}
+
+impl MockXcmConfig {
+	/// Create a MockXcmConfig by reading the mqc_heads directly
+	/// from the storage of a previous block.
+	pub fn new<B: Block, BE: Backend<B>, C: StorageProvider<B, BE>>(
+		client: &C,
+		parent_block: B::Hash,
+		para_id: ParaId,
+		parachain_system_name: ParachainSystemName,
+	) -> Self {
+		let starting_dmq_mqc_head = client
+			.storage(
+				&BlockId::Hash(parent_block),
+				&sp_storage::StorageKey(
+					[twox_128(&parachain_system_name.0), twox_128(b"LastDmqMqcHead")]
+						.concat()
+						.to_vec(),
+				),
+			)
+			.expect("We should be able to read storage from the parent block.")
+			.map(|ref mut raw_data| {
+				Decode::decode(&mut &raw_data.0[..]).expect("Stored data should decode correctly")
+			})
+			.unwrap_or_default();
+
+		let starting_hrmp_mqc_heads = client
+			.storage(
+				&BlockId::Hash(parent_block),
+				&sp_storage::StorageKey(
+					[twox_128(&parachain_system_name.0), twox_128(b"LastHrmpMqcHeads")]
+						.concat()
+						.to_vec(),
+				),
+			)
+			.expect("We should be able to read storage from the parent block.")
+			.map(|ref mut raw_data| {
+				Decode::decode(&mut &raw_data.0[..]).expect("Stored data should decode correctly")
+			})
+			.unwrap_or_default();
+
+		Self { para_id, starting_dmq_mqc_head, starting_hrmp_mqc_heads }
+	}
 }
 
 #[async_trait::async_trait]
@@ -46,27 +135,65 @@ impl InherentDataProvider for MockValidationDataInherentDataProvider {
 		&self,
 		inherent_data: &mut InherentData,
 	) -> Result<(), sp_inherents::Error> {
-		// Use the "sproof" (spoof proof) builder to build valid mock state root and proof.
-		let (relay_storage_root, proof) =
-			RelayStateSproofBuilder::default().into_state_root_and_proof();
-
 		// Calculate the mocked relay block based on the current para block
 		let relay_parent_number =
 			self.relay_offset + self.relay_blocks_per_para_block * self.current_para_block;
 
-		let data = ParachainInherentData {
-			validation_data: PersistedValidationData {
-				parent_head: Default::default(),
-				relay_parent_storage_root: relay_storage_root,
-				relay_parent_number,
-				max_pov_size: Default::default(),
-			},
-			downward_messages: Default::default(),
-			horizontal_messages: Default::default(),
-			relay_chain_state: proof,
-		};
+		// Use the "sproof" (spoof proof) builder to build valid mock state root and proof.
+		let mut sproof_builder = RelayStateSproofBuilder::default();
+		sproof_builder.para_id = self.xcm_config.para_id;
+
+		// Process the downward messages and set up the correct head
+		let mut downward_messages = Vec::new();
+		let mut dmq_mqc = crate::MessageQueueChain(self.xcm_config.starting_dmq_mqc_head);
+		for msg in &self.raw_downward_messages {
+			let wrapped = InboundDownwardMessage { sent_at: relay_parent_number, msg: msg.clone() };
 
-		inherent_data.put_data(INHERENT_IDENTIFIER, &data)
+			dmq_mqc.extend_downward(&wrapped);
+			downward_messages.push(wrapped);
+		}
+		sproof_builder.dmq_mqc_head = Some(dmq_mqc.head());
+
+		// Process the hrmp messages and set up the correct heads
+		// Begin by collecting them into a Map
+		let mut horizontal_messages = BTreeMap::<ParaId, Vec<InboundHrmpMessage>>::new();
+		for (para_id, msg) in &self.raw_horizontal_messages {
+			let wrapped = InboundHrmpMessage { sent_at: relay_parent_number, data: msg.clone() };
+
+			horizontal_messages.entry(*para_id).or_default().push(wrapped);
+		}
+
+		// Now iterate again, updating the heads as we go
+		for (para_id, messages) in &horizontal_messages {
+			let mut channel_mqc = crate::MessageQueueChain(
+				*self
+					.xcm_config
+					.starting_hrmp_mqc_heads
+					.get(para_id)
+					.unwrap_or(&relay_chain::Hash::default()),
+			);
+			for message in messages {
+				channel_mqc.extend_hrmp(message);
+			}
+			sproof_builder.upsert_inbound_channel(*para_id).mqc_head = Some(channel_mqc.head());
+		}
+
+		let (relay_parent_storage_root, proof) = sproof_builder.into_state_root_and_proof();
+
+		inherent_data.put_data(
+			INHERENT_IDENTIFIER,
+			&ParachainInherentData {
+				validation_data: PersistedValidationData {
+					parent_head: Default::default(),
+					relay_parent_storage_root,
+					relay_parent_number,
+					max_pov_size: Default::default(),
+				},
+				downward_messages,
+				horizontal_messages,
+				relay_chain_state: proof,
+			},
+		)
 	}
 
 	// Copied from the real implementation
-- 
GitLab