referrerpolicy=no-referrer-when-downgrade

polkadot_subsystem_bench/mock/
runtime_api.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot 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// Polkadot 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 Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! A generic runtime api subsystem mockup suitable to be used in benchmarks.
18
19use crate::configuration::{TestAuthorities, TestConfiguration};
20use bitvec::prelude::BitVec;
21use futures::FutureExt;
22use itertools::Itertools;
23use polkadot_node_subsystem::{
24	messages::{RuntimeApiMessage, RuntimeApiRequest},
25	overseer, SpawnedSubsystem, SubsystemError,
26};
27use polkadot_node_subsystem_types::OverseerSignal;
28use polkadot_primitives::{
29	node_features, ApprovalVotingParams, AsyncBackingParams, CandidateEvent,
30	CandidateReceiptV2 as CandidateReceipt, CoreIndex, CoreState, GroupIndex, GroupRotationInfo,
31	Id as ParaId, IndexedVec, NodeFeatures, OccupiedCore, ScheduledCore, SessionIndex, SessionInfo,
32	ValidationCode, ValidatorIndex,
33};
34use sp_consensus_babe::Epoch as BabeEpoch;
35use sp_core::H256;
36use std::collections::{BTreeMap, HashMap, VecDeque};
37
38const LOG_TARGET: &str = "subsystem-bench::runtime-api-mock";
39
40/// Minimal state to answer requests.
41#[derive(Clone)]
42pub struct RuntimeApiState {
43	// All authorities in the test,
44	authorities: TestAuthorities,
45	// Node features state in the runtime
46	node_features: NodeFeatures,
47	// Candidate hashes per block
48	candidate_hashes: HashMap<H256, Vec<CandidateReceipt>>,
49	// Candidate events per block
50	candidate_events: HashMap<H256, Vec<CandidateEvent>>,
51	babe_epoch: Option<BabeEpoch>,
52	// The session child index,
53	session_index: SessionIndex,
54	// The claim queue
55	claim_queue: BTreeMap<CoreIndex, VecDeque<ParaId>>,
56}
57
58#[derive(Clone)]
59pub enum MockRuntimeApiCoreState {
60	Occupied,
61	Scheduled,
62	#[allow(dead_code)]
63	Free,
64}
65
66/// A mocked `runtime-api` subsystem.
67#[derive(Clone)]
68pub struct MockRuntimeApi {
69	state: RuntimeApiState,
70	config: TestConfiguration,
71	core_state: MockRuntimeApiCoreState,
72}
73
74impl MockRuntimeApi {
75	pub fn new(
76		config: TestConfiguration,
77		authorities: TestAuthorities,
78		candidate_hashes: HashMap<H256, Vec<CandidateReceipt>>,
79		candidate_events: HashMap<H256, Vec<CandidateEvent>>,
80		babe_epoch: Option<BabeEpoch>,
81		session_index: SessionIndex,
82		core_state: MockRuntimeApiCoreState,
83	) -> MockRuntimeApi {
84		// Enable chunk mapping feature to make systematic av-recovery possible.
85		let node_features = default_node_features();
86		let validator_group_count =
87			session_info_for_peers(&config, &authorities).validator_groups.len();
88
89		// Each para gets one core assigned and there is only one candidate per
90		// parachain per relay chain block (no elastic scaling).
91		let claim_queue = candidate_hashes
92			.iter()
93			.next()
94			.expect("Candidates are generated at test start")
95			.1
96			.iter()
97			.enumerate()
98			.map(|(index, candidate_receipt)| {
99				// Ensure test breaks if badly configured.
100				assert!(index < validator_group_count);
101				(CoreIndex(index as u32), vec![candidate_receipt.descriptor.para_id()].into())
102			})
103			.collect();
104
105		Self {
106			state: RuntimeApiState {
107				authorities,
108				candidate_hashes,
109				candidate_events,
110				babe_epoch,
111				session_index,
112				node_features,
113				claim_queue,
114			},
115			config,
116			core_state,
117		}
118	}
119
120	fn session_info(&self) -> SessionInfo {
121		session_info_for_peers(&self.config, &self.state.authorities)
122	}
123}
124
125/// Generates a test session info with all passed authorities as consensus validators.
126pub fn session_info_for_peers(
127	configuration: &TestConfiguration,
128	authorities: &TestAuthorities,
129) -> SessionInfo {
130	let all_validators = (0..configuration.n_validators)
131		.map(|i| ValidatorIndex(i as _))
132		.collect::<Vec<_>>();
133
134	let validator_groups = all_validators
135		.chunks(configuration.max_validators_per_core)
136		.map(Vec::from)
137		.collect::<Vec<_>>();
138
139	SessionInfo {
140		validators: authorities.validator_public.iter().cloned().collect(),
141		discovery_keys: authorities.validator_authority_id.to_vec(),
142		assignment_keys: authorities.validator_assignment_id.to_vec(),
143		validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(validator_groups),
144		n_cores: configuration.n_cores as u32,
145		needed_approvals: configuration.needed_approvals as u32,
146		zeroth_delay_tranche_width: configuration.zeroth_delay_tranche_width as u32,
147		relay_vrf_modulo_samples: configuration.relay_vrf_modulo_samples as u32,
148		n_delay_tranches: configuration.n_delay_tranches as u32,
149		no_show_slots: configuration.no_show_slots as u32,
150		active_validator_indices: (0..authorities.validator_authority_id.len())
151			.map(|index| ValidatorIndex(index as u32))
152			.collect_vec(),
153		dispute_period: 6,
154		random_seed: [0u8; 32],
155	}
156}
157
158#[overseer::subsystem(RuntimeApi, error=SubsystemError, prefix=self::overseer)]
159impl<Context> MockRuntimeApi {
160	fn start(self, ctx: Context) -> SpawnedSubsystem {
161		let future = self.run(ctx).map(|_| Ok(())).boxed();
162
163		SpawnedSubsystem { name: "test-environment", future }
164	}
165}
166
167#[overseer::contextbounds(RuntimeApi, prefix = self::overseer)]
168impl MockRuntimeApi {
169	async fn run<Context>(self, mut ctx: Context) {
170		let validator_group_count = self.session_info().validator_groups.len();
171
172		loop {
173			let msg = ctx.recv().await.expect("Overseer never fails us");
174
175			match msg {
176				orchestra::FromOrchestra::Signal(signal) =>
177					if signal == OverseerSignal::Conclude {
178						return
179					},
180				orchestra::FromOrchestra::Communication { msg } => {
181					gum::debug!(target: LOG_TARGET, msg=?msg, "recv message");
182
183					match msg {
184						RuntimeApiMessage::Request(
185							request,
186							RuntimeApiRequest::CandidateEvents(sender),
187						) => {
188							let candidate_events = self.state.candidate_events.get(&request);
189							let _ = sender.send(Ok(candidate_events.cloned().unwrap_or_default()));
190						},
191						RuntimeApiMessage::Request(
192							_block_hash,
193							RuntimeApiRequest::SessionInfo(_session_index, sender),
194						) => {
195							let _ = sender.send(Ok(Some(self.session_info())));
196						},
197						RuntimeApiMessage::Request(
198							_block_hash,
199							RuntimeApiRequest::NodeFeatures(_session_index, sender),
200						) => {
201							let _ = sender.send(Ok(self.state.node_features.clone()));
202						},
203						RuntimeApiMessage::Request(
204							_block_hash,
205							RuntimeApiRequest::SessionExecutorParams(_session_index, sender),
206						) => {
207							let _ = sender.send(Ok(Some(Default::default())));
208						},
209						RuntimeApiMessage::Request(
210							_block_hash,
211							RuntimeApiRequest::Validators(sender),
212						) => {
213							let _ =
214								sender.send(Ok(self.state.authorities.validator_public.clone()));
215						},
216						RuntimeApiMessage::Request(
217							_block_hash,
218							RuntimeApiRequest::SessionIndexForChild(sender),
219						) => {
220							// Session is always the same.
221							let _ = sender.send(Ok(self.state.session_index));
222						},
223						RuntimeApiMessage::Request(
224							block_hash,
225							RuntimeApiRequest::AvailabilityCores(sender),
226						) => {
227							let candidate_hashes = self
228								.state
229								.candidate_hashes
230								.get(&block_hash)
231								.expect("Relay chain block hashes are generated at test start");
232
233							// All cores are always occupied.
234							let cores = candidate_hashes
235								.iter()
236								.enumerate()
237								.map(|(index, candidate_receipt)| {
238									// Ensure test breaks if badly configured.
239									assert!(index < validator_group_count);
240
241									use MockRuntimeApiCoreState::*;
242									match self.core_state {
243										Occupied => CoreState::Occupied(OccupiedCore {
244											next_up_on_available: None,
245											occupied_since: 0,
246											time_out_at: 0,
247											next_up_on_time_out: None,
248											availability: BitVec::default(),
249											group_responsible: GroupIndex(index as u32),
250											candidate_hash: candidate_receipt.hash(),
251											candidate_descriptor: candidate_receipt
252												.descriptor
253												.clone(),
254										}),
255										Scheduled => CoreState::Scheduled(ScheduledCore {
256											para_id: (index + 1).into(),
257											collator: None,
258										}),
259										Free => todo!(),
260									}
261								})
262								.collect::<Vec<_>>();
263
264							let _ = sender.send(Ok(cores));
265						},
266						RuntimeApiMessage::Request(
267							_request,
268							RuntimeApiRequest::CurrentBabeEpoch(sender),
269						) => {
270							let _ = sender.send(Ok(self
271								.state
272								.babe_epoch
273								.clone()
274								.expect("Babe epoch unpopulated")));
275						},
276						RuntimeApiMessage::Request(
277							_block_hash,
278							RuntimeApiRequest::AsyncBackingParams(sender),
279						) => {
280							let _ = sender.send(Ok(AsyncBackingParams {
281								max_candidate_depth: self.config.max_candidate_depth,
282								allowed_ancestry_len: self.config.allowed_ancestry_len,
283							}));
284						},
285						RuntimeApiMessage::Request(_parent, RuntimeApiRequest::Version(tx)) => {
286							tx.send(Ok(RuntimeApiRequest::DISABLED_VALIDATORS_RUNTIME_REQUIREMENT))
287								.unwrap();
288						},
289						RuntimeApiMessage::Request(
290							_parent,
291							RuntimeApiRequest::DisabledValidators(tx),
292						) => {
293							tx.send(Ok(vec![])).unwrap();
294						},
295						RuntimeApiMessage::Request(
296							_parent,
297							RuntimeApiRequest::MinimumBackingVotes(_session_index, tx),
298						) => {
299							tx.send(Ok(self.config.minimum_backing_votes)).unwrap();
300						},
301						RuntimeApiMessage::Request(
302							_parent,
303							RuntimeApiRequest::ValidatorGroups(tx),
304						) => {
305							let groups = self.session_info().validator_groups.to_vec();
306							let group_rotation_info = GroupRotationInfo {
307								session_start_block: 1,
308								group_rotation_frequency: 12,
309								now: 1,
310							};
311							tx.send(Ok((groups, group_rotation_info))).unwrap();
312						},
313						RuntimeApiMessage::Request(
314							_parent,
315							RuntimeApiRequest::ValidationCodeByHash(hash, tx),
316						) => {
317							gum::debug!(target: LOG_TARGET, "ValidationCodeByHash: {:?}", hash);
318							let validation_code = ValidationCode(Vec::new());
319							if let Err(err) = tx.send(Ok(Some(validation_code))) {
320								gum::error!(target: LOG_TARGET, ?err, "validation code wasn't received");
321							}
322						},
323						RuntimeApiMessage::Request(
324							_parent,
325							RuntimeApiRequest::ApprovalVotingParams(_, tx),
326						) =>
327							if let Err(err) = tx.send(Ok(ApprovalVotingParams::default())) {
328								gum::error!(target: LOG_TARGET, ?err, "Voting params weren't received");
329							},
330						RuntimeApiMessage::Request(_parent, RuntimeApiRequest::ClaimQueue(tx)) => {
331							tx.send(Ok(self.state.claim_queue.clone())).unwrap();
332						},
333						RuntimeApiMessage::Request(
334							_parent,
335							RuntimeApiRequest::FetchOnChainVotes(tx),
336						) => {
337							tx.send(Ok(None)).unwrap();
338						},
339						RuntimeApiMessage::Request(
340							_parent,
341							RuntimeApiRequest::UnappliedSlashes(tx),
342						) => {
343							tx.send(Ok(vec![])).unwrap();
344						},
345						// Long term TODO: implement more as needed.
346						message => {
347							unimplemented!("Unexpected runtime-api message: {:?}", message)
348						},
349					}
350				},
351			}
352		}
353	}
354}
355
356pub fn default_node_features() -> NodeFeatures {
357	let mut node_features = NodeFeatures::new();
358	node_features.resize(node_features::FeatureIndex::FirstUnassigned as usize, false);
359	node_features.set(node_features::FeatureIndex::AvailabilityChunkMapping as u8 as usize, true);
360	node_features.set(node_features::FeatureIndex::ElasticScalingMVP as u8 as usize, true);
361	node_features.set(node_features::FeatureIndex::CandidateReceiptV2 as u8 as usize, true);
362
363	node_features
364}