referrerpolicy=no-referrer-when-downgrade

cumulus_client_parachain_inherent/
mock.rs

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