cumulus_client_parachain_inherent/
mock.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Cumulus.
3
4// Cumulus is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Cumulus is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Cumulus.  If not, see <http://www.gnu.org/licenses/>.
16
17use crate::{ParachainInherentData, INHERENT_IDENTIFIER};
18use codec::Decode;
19use cumulus_primitives_core::{
20	relay_chain, InboundDownwardMessage, InboundHrmpMessage, ParaId, PersistedValidationData,
21};
22use cumulus_primitives_parachain_inherent::MessageQueueChain;
23use sc_client_api::{Backend, StorageProvider};
24use sp_crypto_hashing::twox_128;
25use sp_inherents::{InherentData, InherentDataProvider};
26use sp_runtime::traits::Block;
27use std::collections::BTreeMap;
28
29use cumulus_test_relay_sproof_builder::RelayStateSproofBuilder;
30
31/// Relay chain slot duration, in milliseconds.
32pub const RELAY_CHAIN_SLOT_DURATION_MILLIS: u32 = 6000;
33
34/// Inherent data provider that supplies mocked validation data.
35///
36/// This is useful when running a node that is not actually backed by any relay chain.
37/// For example when running a local node, or running integration tests.
38///
39/// We mock a relay chain block number as follows:
40/// relay_block_number = offset + relay_blocks_per_para_block * current_para_block
41/// To simulate a parachain that starts in relay block 1000 and gets a block in every other relay
42/// block, use 1000 and 2
43///
44/// Optionally, mock XCM messages can be injected into the runtime. When mocking XCM,
45/// in addition to the messages themselves, you must provide some information about
46/// your parachain's configuration in order to mock the MQC heads properly.
47/// See [`MockXcmConfig`] for more information
48pub struct MockValidationDataInherentDataProvider<R = ()> {
49	/// The current block number of the local block chain (the parachain).
50	pub current_para_block: u32,
51	/// The parachain ID of the parachain for that the inherent data is created.
52	pub para_id: ParaId,
53	/// The current block head data of the local block chain (the parachain).
54	pub current_para_block_head: Option<cumulus_primitives_core::relay_chain::HeadData>,
55	/// The relay block in which this parachain appeared to start. This will be the relay block
56	/// number in para block #P1.
57	pub relay_offset: u32,
58	/// The number of relay blocks that elapses between each parablock. Probably set this to 1 or 2
59	/// to simulate optimistic or realistic relay chain behavior.
60	pub relay_blocks_per_para_block: u32,
61	/// Number of parachain blocks per relay chain epoch
62	/// Mock epoch is computed by dividing `current_para_block` by this value.
63	pub para_blocks_per_relay_epoch: u32,
64	/// Function to mock BABE one epoch ago randomness.
65	pub relay_randomness_config: R,
66	/// XCM messages and associated configuration information.
67	pub xcm_config: MockXcmConfig,
68	/// Inbound downward XCM messages to be injected into the block.
69	pub raw_downward_messages: Vec<Vec<u8>>,
70	// Inbound Horizontal messages sorted by channel.
71	pub raw_horizontal_messages: Vec<(ParaId, Vec<u8>)>,
72	// Additional key-value pairs that should be injected.
73	pub additional_key_values: Option<Vec<(Vec<u8>, Vec<u8>)>>,
74}
75
76/// Something that can generate randomness.
77pub trait GenerateRandomness<I> {
78	/// Generate the randomness using the given `input`.
79	fn generate_randomness(&self, input: I) -> relay_chain::Hash;
80}
81
82impl GenerateRandomness<u64> for () {
83	/// Default implementation uses relay epoch as randomness value
84	/// A more seemingly random implementation may hash the relay epoch instead
85	fn generate_randomness(&self, input: u64) -> relay_chain::Hash {
86		let mut mock_randomness: [u8; 32] = [0u8; 32];
87		mock_randomness[..8].copy_from_slice(&input.to_be_bytes());
88		mock_randomness.into()
89	}
90}
91
92/// Parameters for how the Mock inherent data provider should inject XCM messages.
93/// In addition to the messages themselves, some information about the parachain's
94/// configuration is also required so that the MQC heads can be read out of the
95/// parachain's storage, and the corresponding relay data mocked.
96#[derive(Default)]
97pub struct MockXcmConfig {
98	/// The starting state of the dmq_mqc_head.
99	pub starting_dmq_mqc_head: relay_chain::Hash,
100	/// The starting state of each parachain's mqc head
101	pub starting_hrmp_mqc_heads: BTreeMap<ParaId, relay_chain::Hash>,
102}
103
104/// The name of the parachain system in the runtime.
105///
106/// This name is used by frame to prefix storage items and will be required to read data from the
107/// storage.
108///
109/// The `Default` implementation sets the name to `ParachainSystem`.
110pub struct ParachainSystemName(pub Vec<u8>);
111
112impl Default for ParachainSystemName {
113	fn default() -> Self {
114		Self(b"ParachainSystem".to_vec())
115	}
116}
117
118impl MockXcmConfig {
119	/// Create a MockXcmConfig by reading the mqc_heads directly
120	/// from the storage of a previous block.
121	pub fn new<B: Block, BE: Backend<B>, C: StorageProvider<B, BE>>(
122		client: &C,
123		parent_block: B::Hash,
124		parachain_system_name: ParachainSystemName,
125	) -> Self {
126		let starting_dmq_mqc_head = client
127			.storage(
128				parent_block,
129				&sp_storage::StorageKey(
130					[twox_128(&parachain_system_name.0), twox_128(b"LastDmqMqcHead")]
131						.concat()
132						.to_vec(),
133				),
134			)
135			.expect("We should be able to read storage from the parent block.")
136			.map(|ref mut raw_data| {
137				Decode::decode(&mut &raw_data.0[..]).expect("Stored data should decode correctly")
138			})
139			.unwrap_or_default();
140
141		let starting_hrmp_mqc_heads = client
142			.storage(
143				parent_block,
144				&sp_storage::StorageKey(
145					[twox_128(&parachain_system_name.0), twox_128(b"LastHrmpMqcHeads")]
146						.concat()
147						.to_vec(),
148				),
149			)
150			.expect("We should be able to read storage from the parent block.")
151			.map(|ref mut raw_data| {
152				Decode::decode(&mut &raw_data.0[..]).expect("Stored data should decode correctly")
153			})
154			.unwrap_or_default();
155
156		Self { starting_dmq_mqc_head, starting_hrmp_mqc_heads }
157	}
158}
159
160#[async_trait::async_trait]
161impl<R: Send + Sync + GenerateRandomness<u64>> InherentDataProvider
162	for MockValidationDataInherentDataProvider<R>
163{
164	async fn provide_inherent_data(
165		&self,
166		inherent_data: &mut InherentData,
167	) -> Result<(), sp_inherents::Error> {
168		// Use the "sproof" (spoof proof) builder to build valid mock state root and proof.
169		let mut sproof_builder =
170			RelayStateSproofBuilder { para_id: self.para_id, ..Default::default() };
171
172		// Calculate the mocked relay block based on the current para block
173		let relay_parent_number =
174			self.relay_offset + self.relay_blocks_per_para_block * self.current_para_block;
175		sproof_builder.current_slot =
176			((relay_parent_number / RELAY_CHAIN_SLOT_DURATION_MILLIS) as u64).into();
177
178		// Process the downward messages and set up the correct head
179		let mut downward_messages = Vec::new();
180		let mut dmq_mqc = MessageQueueChain::new(self.xcm_config.starting_dmq_mqc_head);
181		for msg in &self.raw_downward_messages {
182			let wrapped = InboundDownwardMessage { sent_at: relay_parent_number, msg: msg.clone() };
183
184			dmq_mqc.extend_downward(&wrapped);
185			downward_messages.push(wrapped);
186		}
187		sproof_builder.dmq_mqc_head = Some(dmq_mqc.head());
188
189		// Process the hrmp messages and set up the correct heads
190		// Begin by collecting them into a Map
191		let mut horizontal_messages = BTreeMap::<ParaId, Vec<InboundHrmpMessage>>::new();
192		for (para_id, msg) in &self.raw_horizontal_messages {
193			let wrapped = InboundHrmpMessage { sent_at: relay_parent_number, data: msg.clone() };
194
195			horizontal_messages.entry(*para_id).or_default().push(wrapped);
196		}
197
198		// Now iterate again, updating the heads as we go
199		for (para_id, messages) in &horizontal_messages {
200			let mut channel_mqc = MessageQueueChain::new(
201				*self
202					.xcm_config
203					.starting_hrmp_mqc_heads
204					.get(para_id)
205					.unwrap_or(&relay_chain::Hash::default()),
206			);
207			for message in messages {
208				channel_mqc.extend_hrmp(message);
209			}
210			sproof_builder.upsert_inbound_channel(*para_id).mqc_head = Some(channel_mqc.head());
211		}
212
213		// Epoch is set equal to current para block / blocks per epoch
214		sproof_builder.current_epoch = if self.para_blocks_per_relay_epoch == 0 {
215			// do not divide by 0 => set epoch to para block number
216			self.current_para_block.into()
217		} else {
218			(self.current_para_block / self.para_blocks_per_relay_epoch).into()
219		};
220		// Randomness is set by randomness generator
221		sproof_builder.randomness =
222			self.relay_randomness_config.generate_randomness(self.current_para_block.into());
223
224		if let Some(key_values) = &self.additional_key_values {
225			sproof_builder.additional_key_values = key_values.clone()
226		}
227
228		// Inject current para block head, if any
229		sproof_builder.included_para_head = self.current_para_block_head.clone();
230
231		let (relay_parent_storage_root, proof) = sproof_builder.into_state_root_and_proof();
232
233		inherent_data.put_data(
234			INHERENT_IDENTIFIER,
235			&ParachainInherentData {
236				validation_data: PersistedValidationData {
237					parent_head: Default::default(),
238					relay_parent_storage_root,
239					relay_parent_number,
240					max_pov_size: Default::default(),
241				},
242				downward_messages,
243				horizontal_messages,
244				relay_chain_state: proof,
245			},
246		)
247	}
248
249	// Copied from the real implementation
250	async fn try_handle_error(
251		&self,
252		_: &sp_inherents::InherentIdentifier,
253		_: &[u8],
254	) -> Option<Result<(), sp_inherents::Error>> {
255		None
256	}
257}