referrerpolicy=no-referrer-when-downgrade

polkadot_node_core_approval_voting/
import.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//! Block import logic for the approval voting subsystem.
18//!
19//! There are two major concerns when handling block import notifications.
20//!   * Determining all new blocks.
21//!   * Handling session changes
22//!
23//! When receiving a block import notification from the overseer, the
24//! approval voting subsystem needs to account for the fact that there
25//! may have been blocks missed by the notification. It needs to iterate
26//! the ancestry of the block notification back to either the last finalized
27//! block or a block that is already accounted for within the DB.
28//!
29//! We maintain a rolling window of session indices. This starts as empty
30
31use polkadot_node_primitives::{
32	approval::{
33		self as approval_types,
34		v1::{BlockApprovalMeta, RelayVRFStory},
35	},
36	MAX_FINALITY_LAG,
37};
38use polkadot_node_subsystem::{
39	messages::{
40		ApprovalDistributionMessage, ChainApiMessage, ChainSelectionMessage, RuntimeApiMessage,
41		RuntimeApiRequest,
42	},
43	overseer, RuntimeApiError, SubsystemError, SubsystemResult,
44};
45use polkadot_node_subsystem_util::{determine_new_blocks, runtime::RuntimeInfo};
46use polkadot_overseer::SubsystemSender;
47use polkadot_primitives::{
48	node_features, BlockNumber, CandidateEvent, CandidateHash,
49	CandidateReceiptV2 as CandidateReceipt, ConsensusLog, CoreIndex, GroupIndex, Hash, Header,
50	SessionIndex,
51};
52use sc_keystore::LocalKeystore;
53use sp_consensus_slots::Slot;
54
55use bitvec::order::Lsb0 as BitOrderLsb0;
56use futures::{channel::oneshot, prelude::*};
57
58use std::collections::HashMap;
59
60use super::approval_db::v3;
61use crate::{
62	backend::{Backend, OverlayedBackend},
63	criteria::{AssignmentCriteria, OurAssignment},
64	get_extended_session_info, get_session_info,
65	persisted_entries::CandidateEntry,
66};
67
68use polkadot_node_primitives::approval::time::{slot_number_to_tick, Tick};
69
70use super::{State, LOG_TARGET};
71
72#[derive(Debug)]
73struct ImportedBlockInfo {
74	included_candidates: Vec<(CandidateHash, CandidateReceipt, CoreIndex, GroupIndex)>,
75	session_index: SessionIndex,
76	assignments: HashMap<CoreIndex, OurAssignment>,
77	n_validators: usize,
78	relay_vrf_story: RelayVRFStory,
79	slot: Slot,
80	force_approve: Option<BlockNumber>,
81}
82
83struct ImportedBlockInfoEnv<'a> {
84	runtime_info: &'a mut RuntimeInfo,
85	assignment_criteria: &'a (dyn AssignmentCriteria + Send + Sync),
86	keystore: &'a LocalKeystore,
87}
88
89#[derive(Debug, thiserror::Error)]
90enum ImportedBlockInfoError {
91	// NOTE: The `RuntimeApiError` already prints out which request it was,
92	//       so it's not necessary to include that here.
93	#[error(transparent)]
94	RuntimeError(RuntimeApiError),
95
96	#[error("future cancelled while requesting {0}")]
97	FutureCancelled(&'static str, futures::channel::oneshot::Canceled),
98
99	#[error(transparent)]
100	ApprovalError(approval_types::v1::ApprovalError),
101
102	#[error("block is already finalized")]
103	BlockAlreadyFinalized,
104
105	#[error("session info unavailable")]
106	SessionInfoUnavailable,
107
108	#[error("VRF info unavailable")]
109	VrfInfoUnavailable,
110}
111
112/// Computes information about the imported block. Returns an error if the info couldn't be
113/// extracted.
114#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
115async fn imported_block_info<Sender: SubsystemSender<RuntimeApiMessage>>(
116	sender: &mut Sender,
117	env: ImportedBlockInfoEnv<'_>,
118	block_hash: Hash,
119	block_header: &Header,
120	last_finalized_height: &Option<BlockNumber>,
121) -> Result<ImportedBlockInfo, ImportedBlockInfoError> {
122	// Ignore any runtime API errors - that means these blocks are old and finalized.
123	// Only unfinalized blocks factor into the approval voting process.
124
125	// fetch candidates
126	let included_candidates: Vec<_> = {
127		let (c_tx, c_rx) = oneshot::channel();
128		sender
129			.send_message(RuntimeApiMessage::Request(
130				block_hash,
131				RuntimeApiRequest::CandidateEvents(c_tx),
132			))
133			.await;
134
135		let events: Vec<CandidateEvent> = match c_rx.await {
136			Ok(Ok(events)) => events,
137			Ok(Err(error)) => return Err(ImportedBlockInfoError::RuntimeError(error)),
138			Err(error) =>
139				return Err(ImportedBlockInfoError::FutureCancelled("CandidateEvents", error)),
140		};
141
142		events
143			.into_iter()
144			.filter_map(|e| match e {
145				CandidateEvent::CandidateIncluded(receipt, _, core, group) =>
146					Some((receipt.hash(), receipt, core, group)),
147				_ => None,
148			})
149			.collect()
150	};
151
152	// fetch session. ignore blocks that are too old, but unless sessions are really
153	// short, that shouldn't happen.
154	let session_index = {
155		let (s_tx, s_rx) = oneshot::channel();
156		sender
157			.send_message(RuntimeApiMessage::Request(
158				block_header.parent_hash,
159				RuntimeApiRequest::SessionIndexForChild(s_tx),
160			))
161			.await;
162
163		let session_index = match s_rx.await {
164			Ok(Ok(s)) => s,
165			Ok(Err(error)) => return Err(ImportedBlockInfoError::RuntimeError(error)),
166			Err(error) =>
167				return Err(ImportedBlockInfoError::FutureCancelled("SessionIndexForChild", error)),
168		};
169
170		// We can't determine if the block is finalized or not - try processing it
171		if last_finalized_height.map_or(false, |finalized| block_header.number < finalized) {
172			gum::debug!(
173				target: LOG_TARGET,
174				session = session_index,
175				finalized = ?last_finalized_height,
176				"Block {} is either finalized or last finalized height is unknown. Skipping",
177				block_hash,
178			);
179
180			return Err(ImportedBlockInfoError::BlockAlreadyFinalized)
181		}
182
183		session_index
184	};
185
186	let babe_epoch = {
187		let (s_tx, s_rx) = oneshot::channel();
188
189		// It's not obvious whether to use the hash or the parent hash for this, intuitively. We
190		// want to use the block hash itself, and here's why:
191		//
192		// First off, 'epoch' in BABE means 'session' in other places. 'epoch' is the terminology
193		// from the paper, which we fulfill using 'session's, which are a Substrate consensus
194		// concept.
195		//
196		// In BABE, the on-chain and off-chain view of the current epoch can differ at epoch
197		// boundaries because epochs change precisely at a slot. When a block triggers a new epoch,
198		// the state of its parent will still have the old epoch. Conversely, we have the invariant
199		// that every block in BABE has the epoch _it was authored in_ within its post-state. So we
200		// use the block, and not its parent.
201		//
202		// It's worth nothing that Polkadot session changes, at least for the purposes of
203		// parachains, would function the same way, except for the fact that they're always delayed
204		// by one block. This gives us the opposite invariant for sessions - the parent block's
205		// post-state gives us the canonical information about the session index for any of its
206		// children, regardless of which slot number they might be produced at.
207		sender
208			.send_message(RuntimeApiMessage::Request(
209				block_hash,
210				RuntimeApiRequest::CurrentBabeEpoch(s_tx),
211			))
212			.await;
213
214		match s_rx.await {
215			Ok(Ok(s)) => s,
216			Ok(Err(error)) => return Err(ImportedBlockInfoError::RuntimeError(error)),
217			Err(error) =>
218				return Err(ImportedBlockInfoError::FutureCancelled("CurrentBabeEpoch", error)),
219		}
220	};
221
222	let extended_session_info =
223		get_extended_session_info(env.runtime_info, sender, block_hash, session_index).await;
224	let enable_v2_assignments = extended_session_info.map_or(false, |extended_session_info| {
225		*extended_session_info
226			.node_features
227			.get(node_features::FeatureIndex::EnableAssignmentsV2 as usize)
228			.as_deref()
229			.unwrap_or(&false)
230	});
231
232	let session_info = get_session_info(env.runtime_info, sender, block_hash, session_index)
233		.await
234		.ok_or(ImportedBlockInfoError::SessionInfoUnavailable)?;
235
236	gum::debug!(target: LOG_TARGET, ?enable_v2_assignments, "V2 assignments");
237	let (assignments, slot, relay_vrf_story) = {
238		let unsafe_vrf = approval_types::v1::babe_unsafe_vrf_info(&block_header);
239
240		match unsafe_vrf {
241			Some(unsafe_vrf) => {
242				let slot = unsafe_vrf.slot();
243
244				match unsafe_vrf.compute_randomness(
245					&babe_epoch.authorities,
246					&babe_epoch.randomness,
247					babe_epoch.epoch_index,
248				) {
249					Ok(relay_vrf) => {
250						let assignments = env.assignment_criteria.compute_assignments(
251							&env.keystore,
252							relay_vrf.clone(),
253							&crate::criteria::Config::from(session_info),
254							included_candidates
255								.iter()
256								.map(|(c_hash, _, core, group)| (*c_hash, *core, *group))
257								.collect(),
258							enable_v2_assignments,
259						);
260
261						(assignments, slot, relay_vrf)
262					},
263					Err(error) => return Err(ImportedBlockInfoError::ApprovalError(error)),
264				}
265			},
266			None => {
267				gum::debug!(
268					target: LOG_TARGET,
269					"BABE VRF info unavailable for block {}",
270					block_hash,
271				);
272
273				return Err(ImportedBlockInfoError::VrfInfoUnavailable)
274			},
275		}
276	};
277
278	gum::trace!(target: LOG_TARGET, n_assignments = assignments.len(), "Produced assignments");
279
280	let force_approve =
281		block_header.digest.convert_first(|l| match ConsensusLog::from_digest_item(l) {
282			Ok(Some(ConsensusLog::ForceApprove(num))) if num < block_header.number => {
283				gum::trace!(
284					target: LOG_TARGET,
285					?block_hash,
286					current_number = block_header.number,
287					approved_number = num,
288					"Force-approving based on header digest"
289				);
290
291				Some(num)
292			},
293			Ok(Some(_)) => None,
294			Ok(None) => None,
295			Err(err) => {
296				gum::warn!(
297					target: LOG_TARGET,
298					?err,
299					?block_hash,
300					"Malformed consensus digest in header",
301				);
302
303				None
304			},
305		});
306
307	Ok(ImportedBlockInfo {
308		included_candidates,
309		session_index,
310		assignments,
311		n_validators: session_info.validators.len(),
312		relay_vrf_story,
313		slot,
314		force_approve,
315	})
316}
317
318/// Information about a block and imported candidates.
319pub struct BlockImportedCandidates {
320	pub block_hash: Hash,
321	pub block_number: BlockNumber,
322	pub block_tick: Tick,
323	pub imported_candidates: Vec<(CandidateHash, CandidateEntry)>,
324}
325
326/// Handle a new notification of a header. This will
327///   * determine all blocks to import,
328///   * extract candidate information from them
329///   * update the rolling session window
330///   * compute our assignments
331///   * import the block and candidates to the approval DB
332///   * and return information about all candidates imported under each block.
333///
334/// It is the responsibility of the caller to schedule wakeups for each block.
335pub(crate) async fn handle_new_head<
336	Sender: SubsystemSender<ChainApiMessage>
337		+ SubsystemSender<RuntimeApiMessage>
338		+ SubsystemSender<ChainSelectionMessage>,
339	AVSender: SubsystemSender<ApprovalDistributionMessage>,
340	B: Backend,
341>(
342	sender: &mut Sender,
343	approval_voting_sender: &mut AVSender,
344	state: &State,
345	db: &mut OverlayedBackend<'_, B>,
346	session_info_provider: &mut RuntimeInfo,
347	head: Hash,
348	finalized_number: &Option<BlockNumber>,
349) -> SubsystemResult<Vec<BlockImportedCandidates>> {
350	const MAX_HEADS_LOOK_BACK: BlockNumber = MAX_FINALITY_LAG;
351
352	let header = {
353		let (h_tx, h_rx) = oneshot::channel();
354		sender.send_message(ChainApiMessage::BlockHeader(head, h_tx)).await;
355		match h_rx.await? {
356			Err(e) => {
357				gum::debug!(
358					target: LOG_TARGET,
359					"Chain API subsystem temporarily unreachable {}",
360					e,
361				);
362				// May be a better way of handling errors here.
363				return Ok(Vec::new())
364			},
365			Ok(None) => {
366				gum::warn!(target: LOG_TARGET, "Missing header for new head {}", head);
367				// May be a better way of handling warnings here.
368				return Ok(Vec::new())
369			},
370			Ok(Some(h)) => h,
371		}
372	};
373
374	// If we've just started the node and are far behind,
375	// import at most `MAX_HEADS_LOOK_BACK` blocks.
376	let lower_bound_number = header.number.saturating_sub(MAX_HEADS_LOOK_BACK);
377	let lower_bound_number = finalized_number.unwrap_or(lower_bound_number).max(lower_bound_number);
378
379	let new_blocks = determine_new_blocks(
380		sender,
381		|h| db.load_block_entry(h).map(|e| e.is_some()),
382		head,
383		&header,
384		lower_bound_number,
385	)
386	.map_err(|e| SubsystemError::with_origin("approval-voting", e))
387	.await?;
388
389	if new_blocks.is_empty() {
390		return Ok(Vec::new())
391	}
392
393	let mut approval_meta: Vec<BlockApprovalMeta> = Vec::with_capacity(new_blocks.len());
394	let mut imported_candidates = Vec::with_capacity(new_blocks.len());
395
396	// `determine_new_blocks` gives us a vec in backwards order. we want to move forwards.
397	let imported_blocks_and_info = {
398		let mut imported_blocks_and_info = Vec::with_capacity(new_blocks.len());
399		for (block_hash, block_header) in new_blocks.into_iter().rev() {
400			let env = ImportedBlockInfoEnv {
401				runtime_info: session_info_provider,
402				assignment_criteria: &*state.assignment_criteria,
403				keystore: &state.keystore,
404			};
405
406			match imported_block_info(sender, env, block_hash, &block_header, finalized_number)
407				.await
408			{
409				Ok(i) => imported_blocks_and_info.push((block_hash, block_header, i)),
410				Err(error) => {
411					// It's possible that we've lost a race with finality.
412					let (tx, rx) = oneshot::channel();
413					sender
414						.send_message(ChainApiMessage::FinalizedBlockHash(block_header.number, tx))
415						.await;
416
417					let lost_to_finality = match rx.await {
418						Ok(Ok(Some(h))) if h != block_hash => true,
419						_ => false,
420					};
421
422					if !lost_to_finality {
423						// Such errors are likely spurious, but this prevents us from getting gaps
424						// in the approval-db.
425						gum::warn!(
426							target: LOG_TARGET,
427							"Skipping chain: unable to gather info about imported block {:?}: {}",
428							(block_hash, block_header.number),
429							error,
430						);
431					}
432
433					return Ok(Vec::new())
434				},
435			};
436		}
437
438		imported_blocks_and_info
439	};
440
441	gum::trace!(
442		target: LOG_TARGET,
443		imported_blocks = imported_blocks_and_info.len(),
444		"Inserting imported blocks into database"
445	);
446
447	for (block_hash, block_header, imported_block_info) in imported_blocks_and_info {
448		let ImportedBlockInfo {
449			included_candidates,
450			session_index,
451			assignments,
452			n_validators,
453			relay_vrf_story,
454			slot,
455			force_approve,
456		} = imported_block_info;
457
458		let session_info =
459			match get_session_info(session_info_provider, sender, head, session_index).await {
460				Some(session_info) => session_info,
461				None => return Ok(Vec::new()),
462			};
463
464		let block_tick = slot_number_to_tick(state.slot_duration_millis, slot);
465
466		let needed_approvals = session_info.needed_approvals;
467		let validator_group_lens: Vec<usize> =
468			session_info.validator_groups.iter().map(|v| v.len()).collect();
469		// insta-approve candidates on low-node testnets:
470		// cf. https://github.com/paritytech/polkadot/issues/2411
471		let num_candidates = included_candidates.len();
472		let approved_bitfield = {
473			if needed_approvals == 0 {
474				gum::debug!(
475					target: LOG_TARGET,
476					block_hash = ?block_hash,
477					"Insta-approving all candidates",
478				);
479				bitvec::bitvec![u8, BitOrderLsb0; 1; num_candidates]
480			} else {
481				let mut result = bitvec::bitvec![u8, BitOrderLsb0; 0; num_candidates];
482				for (i, &(_, _, _, backing_group)) in included_candidates.iter().enumerate() {
483					let backing_group_size =
484						validator_group_lens.get(backing_group.0 as usize).copied().unwrap_or(0);
485					let needed_approvals =
486						usize::try_from(needed_approvals).expect("usize is at least u32; qed");
487					if n_validators.saturating_sub(backing_group_size) < needed_approvals {
488						result.set(i, true);
489					}
490				}
491				if result.any() {
492					gum::debug!(
493						target: LOG_TARGET,
494						block_hash = ?block_hash,
495						"Insta-approving {}/{} candidates as the number of validators is too low",
496						result.count_ones(),
497						result.len(),
498					);
499				}
500				result
501			}
502		};
503		// If all bits are already set, then send an approve message.
504		if approved_bitfield.count_ones() == approved_bitfield.len() {
505			sender.send_message(ChainSelectionMessage::Approved(block_hash)).await;
506		}
507
508		let block_entry = v3::BlockEntry {
509			block_hash,
510			parent_hash: block_header.parent_hash,
511			block_number: block_header.number,
512			session: session_index,
513			slot,
514			relay_vrf_story: relay_vrf_story.0,
515			candidates: included_candidates
516				.iter()
517				.map(|(hash, _, core, _)| (*core, *hash))
518				.collect(),
519			approved_bitfield,
520			children: Vec::new(),
521			candidates_pending_signature: Default::default(),
522			distributed_assignments: Default::default(),
523		};
524
525		gum::trace!(
526			target: LOG_TARGET,
527			?block_hash,
528			block_number = block_header.number,
529			"Writing BlockEntry",
530		);
531
532		let candidate_entries =
533			crate::ops::add_block_entry(db, block_entry.into(), n_validators, |candidate_hash| {
534				included_candidates.iter().find(|(hash, _, _, _)| candidate_hash == hash).map(
535					|(_, receipt, core, backing_group)| {
536						super::ops::NewCandidateInfo::new(
537							receipt.clone(),
538							*backing_group,
539							assignments.get(core).map(|a| a.clone().into()),
540						)
541					},
542				)
543			})
544			.map_err(|e| SubsystemError::with_origin("approval-voting", e))?;
545
546		// force-approve needs to load the current block entry as well as all
547		// ancestors. this can only be done after writing the block entry above.
548		if let Some(up_to) = force_approve {
549			gum::debug!(target: LOG_TARGET, ?block_hash, up_to, "Enacting force-approve");
550			let approved_hashes = crate::ops::force_approve(db, block_hash, up_to)
551				.map_err(|e| SubsystemError::with_origin("approval-voting", e))?;
552			gum::debug!(
553				target: LOG_TARGET,
554				?block_hash,
555				up_to,
556				"Force-approving {} blocks",
557				approved_hashes.len()
558			);
559
560			// Notify chain-selection of all approved hashes.
561			for hash in approved_hashes {
562				sender.send_message(ChainSelectionMessage::Approved(hash)).await;
563			}
564		}
565
566		approval_meta.push(BlockApprovalMeta {
567			hash: block_hash,
568			number: block_header.number,
569			parent_hash: block_header.parent_hash,
570			candidates: included_candidates
571				.iter()
572				.map(|(hash, _, core_index, group_index)| (*hash, *core_index, *group_index))
573				.collect(),
574			slot,
575			session: session_index,
576			vrf_story: relay_vrf_story,
577		});
578
579		imported_candidates.push(BlockImportedCandidates {
580			block_hash,
581			block_number: block_header.number,
582			block_tick,
583			imported_candidates: candidate_entries
584				.into_iter()
585				.map(|(h, e)| (h, e.into()))
586				.collect(),
587		});
588	}
589
590	gum::trace!(
591		target: LOG_TARGET,
592		head = ?head,
593		chain_length = approval_meta.len(),
594		"Informing distribution of newly imported chain",
595	);
596
597	approval_voting_sender
598		.send_unbounded_message(ApprovalDistributionMessage::NewBlocks(approval_meta));
599	Ok(imported_candidates)
600}
601
602#[cfg(test)]
603pub(crate) mod tests {
604	use super::*;
605	use crate::{
606		approval_db::common::{load_block_entry, DbBackend},
607		RuntimeInfo, RuntimeInfoConfig, MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS,
608	};
609	use approval_types::time::Clock;
610	use assert_matches::assert_matches;
611	use polkadot_node_primitives::{
612		approval::v1::{VrfSignature, VrfTranscript},
613		DISPUTE_WINDOW,
614	};
615	use polkadot_node_subsystem::{
616		messages::{AllMessages, ApprovalVotingMessage},
617		SubsystemContext,
618	};
619	use polkadot_node_subsystem_test_helpers::make_subsystem_context;
620	use polkadot_node_subsystem_util::database::Database;
621	use polkadot_primitives::{
622		node_features::FeatureIndex, ExecutorParams, Id as ParaId, IndexedVec, MutateDescriptorV2,
623		NodeFeatures, SessionInfo, ValidatorId, ValidatorIndex,
624	};
625	use polkadot_primitives_test_helpers::{dummy_candidate_receipt_v2, dummy_hash};
626	use schnellru::{ByLength, LruMap};
627	pub(crate) use sp_consensus_babe::{
628		digests::{CompatibleDigestItem, PreDigest, SecondaryVRFPreDigest},
629		AllowedSlots, BabeEpochConfiguration, Epoch as BabeEpoch,
630	};
631	use sp_core::{crypto::VrfSecret, testing::TaskExecutor};
632	use sp_keyring::sr25519::Keyring as Sr25519Keyring;
633	pub(crate) use sp_runtime::{Digest, DigestItem};
634	use std::{pin::Pin, sync::Arc};
635
636	use crate::{approval_db::common::Config as DatabaseConfig, criteria, BlockEntry};
637
638	const DATA_COL: u32 = 0;
639
640	const NUM_COLUMNS: u32 = 1;
641
642	const TEST_CONFIG: DatabaseConfig = DatabaseConfig { col_approval_data: DATA_COL };
643	#[derive(Default)]
644	struct MockClock;
645
646	impl Clock for MockClock {
647		fn tick_now(&self) -> Tick {
648			42 // chosen by fair dice roll
649		}
650
651		fn wait(&self, _tick: Tick) -> Pin<Box<dyn Future<Output = ()> + Send + 'static>> {
652			Box::pin(async move { () })
653		}
654	}
655
656	fn blank_state() -> State {
657		State {
658			keystore: Arc::new(LocalKeystore::in_memory()),
659			slot_duration_millis: 6_000,
660			clock: Arc::new(MockClock::default()),
661			assignment_criteria: Box::new(MockAssignmentCriteria::default()),
662			per_block_assignments_gathering_times: LruMap::new(ByLength::new(
663				MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS,
664			)),
665			no_show_stats: Default::default(),
666		}
667	}
668
669	fn single_session_state() -> (State, RuntimeInfo) {
670		(
671			blank_state(),
672			RuntimeInfo::new_with_config(RuntimeInfoConfig {
673				keystore: None,
674				session_cache_lru_size: DISPUTE_WINDOW.get(),
675			}),
676		)
677	}
678
679	#[derive(Default)]
680	struct MockAssignmentCriteria {
681		enable_v2: bool,
682	}
683
684	impl AssignmentCriteria for MockAssignmentCriteria {
685		fn compute_assignments(
686			&self,
687			_keystore: &LocalKeystore,
688			_relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory,
689			_config: &criteria::Config,
690			_leaving_cores: Vec<(
691				CandidateHash,
692				polkadot_primitives::CoreIndex,
693				polkadot_primitives::GroupIndex,
694			)>,
695			enable_assignments_v2: bool,
696		) -> HashMap<polkadot_primitives::CoreIndex, criteria::OurAssignment> {
697			assert_eq!(enable_assignments_v2, self.enable_v2);
698			HashMap::new()
699		}
700
701		fn check_assignment_cert(
702			&self,
703			_claimed_core_bitfield: polkadot_node_primitives::approval::v2::CoreBitfield,
704			_validator_index: polkadot_primitives::ValidatorIndex,
705			_config: &criteria::Config,
706			_relay_vrf_story: polkadot_node_primitives::approval::v1::RelayVRFStory,
707			_assignment: &polkadot_node_primitives::approval::v2::AssignmentCertV2,
708			_backing_groups: Vec<polkadot_primitives::GroupIndex>,
709		) -> Result<polkadot_node_primitives::approval::v1::DelayTranche, criteria::InvalidAssignment>
710		{
711			Ok(0)
712		}
713	}
714
715	// used for generating assignments where the validity of the VRF doesn't matter.
716	pub(crate) fn garbage_vrf_signature() -> VrfSignature {
717		let transcript = VrfTranscript::new(b"test-garbage", &[]);
718		Sr25519Keyring::Alice.pair().vrf_sign(&transcript.into())
719	}
720
721	fn dummy_session_info(index: SessionIndex) -> SessionInfo {
722		SessionInfo {
723			validators: Default::default(),
724			discovery_keys: Vec::new(),
725			assignment_keys: Vec::new(),
726			validator_groups: Default::default(),
727			n_cores: index as _,
728			zeroth_delay_tranche_width: index as _,
729			relay_vrf_modulo_samples: index as _,
730			n_delay_tranches: index as _,
731			no_show_slots: index as _,
732			needed_approvals: index as _,
733			active_validator_indices: Vec::new(),
734			dispute_period: 6,
735			random_seed: [0u8; 32],
736		}
737	}
738
739	#[test]
740	fn imported_block_info_is_good() {
741		for enable_v2 in [false, true] {
742			let pool = TaskExecutor::new();
743			let (mut ctx, mut handle) =
744				make_subsystem_context::<ApprovalVotingMessage, _>(pool.clone());
745
746			let session = 5;
747			let session_info = dummy_session_info(session);
748
749			let slot = Slot::from(10);
750			let header = Header {
751				digest: {
752					let mut d = Digest::default();
753					let vrf_signature = garbage_vrf_signature();
754					d.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(
755						SecondaryVRFPreDigest { authority_index: 0, slot, vrf_signature },
756					)));
757
758					d
759				},
760				extrinsics_root: Default::default(),
761				number: 5,
762				state_root: Default::default(),
763				parent_hash: Default::default(),
764			};
765
766			let hash = header.hash();
767			let make_candidate = |para_id| {
768				let mut r = dummy_candidate_receipt_v2(dummy_hash());
769				r.descriptor.set_para_id(para_id);
770				r.descriptor.set_relay_parent(hash);
771				r
772			};
773			let candidates = vec![
774				(make_candidate(1.into()), CoreIndex(0), GroupIndex(2)),
775				(make_candidate(2.into()), CoreIndex(1), GroupIndex(3)),
776			];
777
778			let inclusion_events = candidates
779				.iter()
780				.cloned()
781				.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
782				.collect::<Vec<_>>();
783
784			let test_fut = {
785				let included_candidates = candidates
786					.iter()
787					.map(|(r, c, g)| (r.hash(), r.clone(), *c, *g))
788					.collect::<Vec<_>>();
789
790				let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig {
791					keystore: None,
792					session_cache_lru_size: DISPUTE_WINDOW.get(),
793				});
794
795				let header = header.clone();
796				Box::pin(async move {
797					let env = ImportedBlockInfoEnv {
798						runtime_info: &mut runtime_info,
799						assignment_criteria: &MockAssignmentCriteria { enable_v2 },
800						keystore: &LocalKeystore::in_memory(),
801					};
802
803					let info = imported_block_info(ctx.sender(), env, hash, &header, &Some(4))
804						.await
805						.unwrap();
806
807					assert_eq!(info.included_candidates, included_candidates);
808					assert_eq!(info.session_index, session);
809					assert!(info.assignments.is_empty());
810					assert_eq!(info.n_validators, 0);
811					assert_eq!(info.slot, slot);
812					assert!(info.force_approve.is_none());
813				})
814			};
815
816			let aux_fut = Box::pin(async move {
817				assert_matches!(
818					handle.recv().await,
819					AllMessages::RuntimeApi(RuntimeApiMessage::Request(
820						h,
821						RuntimeApiRequest::CandidateEvents(c_tx),
822					)) => {
823						assert_eq!(h, hash);
824						let _ = c_tx.send(Ok(inclusion_events));
825					}
826				);
827
828				assert_matches!(
829					handle.recv().await,
830					AllMessages::RuntimeApi(RuntimeApiMessage::Request(
831						h,
832						RuntimeApiRequest::SessionIndexForChild(c_tx),
833					)) => {
834						assert_eq!(h, header.parent_hash);
835						let _ = c_tx.send(Ok(session));
836					}
837				);
838
839				assert_matches!(
840					handle.recv().await,
841					AllMessages::RuntimeApi(RuntimeApiMessage::Request(
842						h,
843						RuntimeApiRequest::CurrentBabeEpoch(c_tx),
844					)) => {
845						assert_eq!(h, hash);
846						let _ = c_tx.send(Ok(BabeEpoch {
847							epoch_index: session as _,
848							start_slot: Slot::from(0),
849							duration: 200,
850							authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)],
851							randomness: [0u8; 32],
852							config: BabeEpochConfiguration {
853								c: (1, 4),
854								allowed_slots: AllowedSlots::PrimarySlots,
855							},
856						}));
857					}
858				);
859
860				assert_matches!(
861					handle.recv().await,
862					AllMessages::RuntimeApi(
863						RuntimeApiMessage::Request(
864							req_block_hash,
865							RuntimeApiRequest::SessionInfo(idx, si_tx),
866						)
867					) => {
868						assert_eq!(session, idx);
869						assert_eq!(req_block_hash, hash);
870						si_tx.send(Ok(Some(session_info.clone()))).unwrap();
871					}
872				);
873
874				assert_matches!(
875					handle.recv().await,
876					AllMessages::RuntimeApi(
877						RuntimeApiMessage::Request(
878							req_block_hash,
879							RuntimeApiRequest::SessionExecutorParams(idx, si_tx),
880						)
881					) => {
882						assert_eq!(session, idx);
883						assert_eq!(req_block_hash, hash);
884						si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap();
885					}
886				);
887
888				assert_matches!(
889					handle.recv().await,
890					AllMessages::RuntimeApi(
891						RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), )
892					) => {
893						si_tx.send(Ok(NodeFeatures::repeat(enable_v2, FeatureIndex::EnableAssignmentsV2 as usize + 1))).unwrap();
894					}
895				);
896			});
897
898			futures::executor::block_on(futures::future::join(test_fut, aux_fut));
899		}
900	}
901
902	#[test]
903	fn imported_block_info_fails_if_no_babe_vrf() {
904		let pool = TaskExecutor::new();
905		let (mut ctx, mut handle) =
906			make_subsystem_context::<ApprovalVotingMessage, _>(pool.clone());
907
908		let session = 5;
909		let session_info = dummy_session_info(session);
910
911		let header = Header {
912			digest: Digest::default(),
913			extrinsics_root: Default::default(),
914			number: 5,
915			state_root: Default::default(),
916			parent_hash: Default::default(),
917		};
918
919		let hash = header.hash();
920		let make_candidate = |para_id| {
921			let mut r = dummy_candidate_receipt_v2(dummy_hash());
922			r.descriptor.set_para_id(para_id);
923			r.descriptor.set_relay_parent(hash);
924			r
925		};
926		let candidates = vec![
927			(make_candidate(1.into()), CoreIndex(0), GroupIndex(2)),
928			(make_candidate(2.into()), CoreIndex(1), GroupIndex(3)),
929		];
930
931		let inclusion_events = candidates
932			.iter()
933			.cloned()
934			.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
935			.collect::<Vec<_>>();
936
937		let test_fut = {
938			let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig {
939				keystore: None,
940				session_cache_lru_size: DISPUTE_WINDOW.get(),
941			});
942
943			let header = header.clone();
944			Box::pin(async move {
945				let env = ImportedBlockInfoEnv {
946					runtime_info: &mut runtime_info,
947					assignment_criteria: &MockAssignmentCriteria::default(),
948					keystore: &LocalKeystore::in_memory(),
949				};
950
951				let info = imported_block_info(ctx.sender(), env, hash, &header, &Some(4)).await;
952
953				assert_matches!(info, Err(ImportedBlockInfoError::VrfInfoUnavailable));
954			})
955		};
956
957		let aux_fut = Box::pin(async move {
958			assert_matches!(
959				handle.recv().await,
960				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
961					h,
962					RuntimeApiRequest::CandidateEvents(c_tx),
963				)) => {
964					assert_eq!(h, hash);
965					let _ = c_tx.send(Ok(inclusion_events));
966				}
967			);
968
969			assert_matches!(
970				handle.recv().await,
971				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
972					h,
973					RuntimeApiRequest::SessionIndexForChild(c_tx),
974				)) => {
975					assert_eq!(h, header.parent_hash);
976					let _ = c_tx.send(Ok(session));
977				}
978			);
979
980			assert_matches!(
981				handle.recv().await,
982				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
983					h,
984					RuntimeApiRequest::CurrentBabeEpoch(c_tx),
985				)) => {
986					assert_eq!(h, hash);
987					let _ = c_tx.send(Ok(BabeEpoch {
988						epoch_index: session as _,
989						start_slot: Slot::from(0),
990						duration: 200,
991						authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)],
992						randomness: [0u8; 32],
993						config: BabeEpochConfiguration {
994							c: (1, 4),
995							allowed_slots: AllowedSlots::PrimarySlots,
996						},
997					}));
998				}
999			);
1000
1001			assert_matches!(
1002				handle.recv().await,
1003				AllMessages::RuntimeApi(
1004					RuntimeApiMessage::Request(
1005						req_block_hash,
1006						RuntimeApiRequest::SessionInfo(idx, si_tx),
1007					)
1008				) => {
1009					assert_eq!(session, idx);
1010					assert_eq!(req_block_hash, hash);
1011					si_tx.send(Ok(Some(session_info.clone()))).unwrap();
1012				}
1013			);
1014
1015			assert_matches!(
1016				handle.recv().await,
1017				AllMessages::RuntimeApi(
1018					RuntimeApiMessage::Request(
1019						req_block_hash,
1020						RuntimeApiRequest::SessionExecutorParams(idx, si_tx),
1021					)
1022				) => {
1023					assert_eq!(session, idx);
1024					assert_eq!(req_block_hash, hash);
1025					si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap();
1026				}
1027			);
1028
1029			assert_matches!(
1030				handle.recv().await,
1031				AllMessages::RuntimeApi(
1032					RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), )
1033				) => {
1034					si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap();
1035				}
1036			);
1037		});
1038
1039		futures::executor::block_on(futures::future::join(test_fut, aux_fut));
1040	}
1041
1042	#[test]
1043	fn imported_block_info_fails_if_ancient_session() {
1044		let pool = TaskExecutor::new();
1045		let (mut ctx, mut handle) =
1046			make_subsystem_context::<ApprovalVotingMessage, _>(pool.clone());
1047
1048		let session = 5;
1049
1050		let header = Header {
1051			digest: Digest::default(),
1052			extrinsics_root: Default::default(),
1053			number: 5,
1054			state_root: Default::default(),
1055			parent_hash: Default::default(),
1056		};
1057
1058		let hash = header.hash();
1059		let make_candidate = |para_id| {
1060			let mut r = dummy_candidate_receipt_v2(dummy_hash());
1061			r.descriptor.set_para_id(para_id);
1062			r.descriptor.set_relay_parent(hash);
1063			r
1064		};
1065		let candidates = vec![
1066			(make_candidate(1.into()), CoreIndex(0), GroupIndex(2)),
1067			(make_candidate(2.into()), CoreIndex(1), GroupIndex(3)),
1068		];
1069
1070		let inclusion_events = candidates
1071			.iter()
1072			.cloned()
1073			.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
1074			.collect::<Vec<_>>();
1075
1076		let test_fut = {
1077			let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig {
1078				keystore: None,
1079				session_cache_lru_size: DISPUTE_WINDOW.get(),
1080			});
1081
1082			let header = header.clone();
1083			Box::pin(async move {
1084				let env = ImportedBlockInfoEnv {
1085					runtime_info: &mut runtime_info,
1086					assignment_criteria: &MockAssignmentCriteria::default(),
1087					keystore: &LocalKeystore::in_memory(),
1088				};
1089
1090				let info = imported_block_info(ctx.sender(), env, hash, &header, &Some(6)).await;
1091
1092				assert_matches!(info, Err(ImportedBlockInfoError::BlockAlreadyFinalized));
1093			})
1094		};
1095
1096		let aux_fut = Box::pin(async move {
1097			assert_matches!(
1098				handle.recv().await,
1099				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
1100					h,
1101					RuntimeApiRequest::CandidateEvents(c_tx),
1102				)) => {
1103					assert_eq!(h, hash);
1104					let _ = c_tx.send(Ok(inclusion_events));
1105				}
1106			);
1107
1108			assert_matches!(
1109				handle.recv().await,
1110				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
1111					h,
1112					RuntimeApiRequest::SessionIndexForChild(c_tx),
1113				)) => {
1114					assert_eq!(h, header.parent_hash);
1115					let _ = c_tx.send(Ok(session));
1116				}
1117			);
1118		});
1119
1120		futures::executor::block_on(futures::future::join(test_fut, aux_fut));
1121	}
1122
1123	#[test]
1124	fn imported_block_info_extracts_force_approve() {
1125		let pool = TaskExecutor::new();
1126		let (mut ctx, mut handle) =
1127			make_subsystem_context::<ApprovalVotingMessage, _>(pool.clone());
1128
1129		let session = 5;
1130		let session_info = dummy_session_info(session);
1131
1132		let slot = Slot::from(10);
1133
1134		let header = Header {
1135			digest: {
1136				let mut d = Digest::default();
1137				let vrf_signature = garbage_vrf_signature();
1138				d.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(
1139					SecondaryVRFPreDigest { authority_index: 0, slot, vrf_signature },
1140				)));
1141
1142				d.push(ConsensusLog::ForceApprove(3).into());
1143
1144				d
1145			},
1146			extrinsics_root: Default::default(),
1147			number: 5,
1148			state_root: Default::default(),
1149			parent_hash: Default::default(),
1150		};
1151
1152		let hash = header.hash();
1153		let make_candidate = |para_id| {
1154			let mut r = dummy_candidate_receipt_v2(dummy_hash());
1155			r.descriptor.set_para_id(para_id);
1156			r.descriptor.set_relay_parent(hash);
1157			r
1158		};
1159		let candidates = vec![
1160			(make_candidate(1.into()), CoreIndex(0), GroupIndex(2)),
1161			(make_candidate(2.into()), CoreIndex(1), GroupIndex(3)),
1162		];
1163
1164		let inclusion_events = candidates
1165			.iter()
1166			.cloned()
1167			.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
1168			.collect::<Vec<_>>();
1169
1170		let test_fut = {
1171			let included_candidates = candidates
1172				.iter()
1173				.map(|(r, c, g)| (r.hash(), r.clone(), *c, *g))
1174				.collect::<Vec<_>>();
1175
1176			let mut runtime_info = RuntimeInfo::new_with_config(RuntimeInfoConfig {
1177				keystore: None,
1178				session_cache_lru_size: DISPUTE_WINDOW.get(),
1179			});
1180
1181			let header = header.clone();
1182			Box::pin(async move {
1183				let env = ImportedBlockInfoEnv {
1184					runtime_info: &mut runtime_info,
1185					assignment_criteria: &MockAssignmentCriteria::default(),
1186					keystore: &LocalKeystore::in_memory(),
1187				};
1188
1189				let info =
1190					imported_block_info(ctx.sender(), env, hash, &header, &Some(4)).await.unwrap();
1191
1192				assert_eq!(info.included_candidates, included_candidates);
1193				assert_eq!(info.session_index, session);
1194				assert!(info.assignments.is_empty());
1195				assert_eq!(info.n_validators, 0);
1196				assert_eq!(info.slot, slot);
1197				assert_eq!(info.force_approve, Some(3));
1198			})
1199		};
1200
1201		let aux_fut = Box::pin(async move {
1202			assert_matches!(
1203				handle.recv().await,
1204				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
1205					h,
1206					RuntimeApiRequest::CandidateEvents(c_tx),
1207				)) => {
1208					assert_eq!(h, hash);
1209					let _ = c_tx.send(Ok(inclusion_events));
1210				}
1211			);
1212
1213			assert_matches!(
1214				handle.recv().await,
1215				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
1216					h,
1217					RuntimeApiRequest::SessionIndexForChild(c_tx),
1218				)) => {
1219					assert_eq!(h, header.parent_hash);
1220					let _ = c_tx.send(Ok(session));
1221				}
1222			);
1223
1224			assert_matches!(
1225				handle.recv().await,
1226				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
1227					h,
1228					RuntimeApiRequest::CurrentBabeEpoch(c_tx),
1229				)) => {
1230					assert_eq!(h, hash);
1231					let _ = c_tx.send(Ok(BabeEpoch {
1232						epoch_index: session as _,
1233						start_slot: Slot::from(0),
1234						duration: 200,
1235						authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)],
1236						randomness: [0u8; 32],
1237						config: BabeEpochConfiguration {
1238							c: (1, 4),
1239							allowed_slots: AllowedSlots::PrimarySlots,
1240						},
1241					}));
1242				}
1243			);
1244
1245			assert_matches!(
1246				handle.recv().await,
1247				AllMessages::RuntimeApi(
1248					RuntimeApiMessage::Request(
1249						req_block_hash,
1250						RuntimeApiRequest::SessionInfo(idx, si_tx),
1251					)
1252				) => {
1253					assert_eq!(session, idx);
1254					assert_eq!(req_block_hash, hash);
1255					si_tx.send(Ok(Some(session_info.clone()))).unwrap();
1256				}
1257			);
1258
1259			assert_matches!(
1260				handle.recv().await,
1261				AllMessages::RuntimeApi(
1262					RuntimeApiMessage::Request(
1263						req_block_hash,
1264						RuntimeApiRequest::SessionExecutorParams(idx, si_tx),
1265					)
1266				) => {
1267					assert_eq!(session, idx);
1268					assert_eq!(req_block_hash, hash);
1269					si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap();
1270				}
1271			);
1272
1273			assert_matches!(
1274				handle.recv().await,
1275				AllMessages::RuntimeApi(
1276					RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), )
1277				) => {
1278					si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap();
1279				}
1280			);
1281		});
1282
1283		futures::executor::block_on(futures::future::join(test_fut, aux_fut));
1284	}
1285
1286	#[test]
1287	fn insta_approval_works() {
1288		let db = kvdb_memorydb::create(NUM_COLUMNS);
1289		let db = polkadot_node_subsystem_util::database::kvdb_impl::DbAdapter::new(db, &[]);
1290		let db_writer: Arc<dyn Database> = Arc::new(db);
1291		let mut db = DbBackend::new(db_writer.clone(), TEST_CONFIG);
1292		let mut overlay_db = OverlayedBackend::new(&db);
1293
1294		let pool = TaskExecutor::new();
1295		let (mut ctx, mut handle) =
1296			make_subsystem_context::<ApprovalVotingMessage, _>(pool.clone());
1297
1298		let session = 5;
1299		let irrelevant = 666;
1300		let session_info =
1301			SessionInfo {
1302				validators: IndexedVec::<ValidatorIndex, ValidatorId>::from(
1303					vec![Sr25519Keyring::Alice.public().into(); 6],
1304				),
1305				discovery_keys: Vec::new(),
1306				assignment_keys: Vec::new(),
1307				validator_groups: IndexedVec::<GroupIndex, Vec<ValidatorIndex>>::from(vec![
1308					vec![ValidatorIndex(0); 5],
1309					vec![ValidatorIndex(0); 2],
1310				]),
1311				n_cores: 6,
1312				needed_approvals: 2,
1313				zeroth_delay_tranche_width: irrelevant,
1314				relay_vrf_modulo_samples: irrelevant,
1315				n_delay_tranches: irrelevant,
1316				no_show_slots: irrelevant,
1317				active_validator_indices: Vec::new(),
1318				dispute_period: 6,
1319				random_seed: [0u8; 32],
1320			};
1321
1322		let slot = Slot::from(10);
1323
1324		let parent_hash = Hash::repeat_byte(0x01);
1325
1326		let header = Header {
1327			digest: {
1328				let mut d = Digest::default();
1329				let vrf_signature = garbage_vrf_signature();
1330				d.push(DigestItem::babe_pre_digest(PreDigest::SecondaryVRF(
1331					SecondaryVRFPreDigest { authority_index: 0, slot, vrf_signature },
1332				)));
1333
1334				d
1335			},
1336			extrinsics_root: Default::default(),
1337			number: 5,
1338			state_root: Default::default(),
1339			parent_hash,
1340		};
1341
1342		let hash = header.hash();
1343		let make_candidate = |para_id| {
1344			let mut r = dummy_candidate_receipt_v2(dummy_hash());
1345			r.descriptor.set_para_id(para_id);
1346			r.descriptor.set_relay_parent(hash);
1347			r
1348		};
1349		let candidates = vec![
1350			(make_candidate(ParaId::from(1)), CoreIndex(0), GroupIndex(0)),
1351			(make_candidate(ParaId::from(2)), CoreIndex(1), GroupIndex(1)),
1352		];
1353		let inclusion_events = candidates
1354			.iter()
1355			.cloned()
1356			.map(|(r, c, g)| CandidateEvent::CandidateIncluded(r, Vec::new().into(), c, g))
1357			.collect::<Vec<_>>();
1358
1359		let (state, mut session_info_provider) = single_session_state();
1360		overlay_db.write_block_entry(
1361			v3::BlockEntry {
1362				block_hash: parent_hash,
1363				parent_hash: Default::default(),
1364				block_number: 4,
1365				session,
1366				slot,
1367				relay_vrf_story: Default::default(),
1368				candidates: Vec::new(),
1369				approved_bitfield: Default::default(),
1370				children: Vec::new(),
1371				candidates_pending_signature: Default::default(),
1372				distributed_assignments: Default::default(),
1373			}
1374			.into(),
1375		);
1376
1377		let write_ops = overlay_db.into_write_ops();
1378		db.write(write_ops).unwrap();
1379
1380		let test_fut = {
1381			Box::pin(async move {
1382				let mut overlay_db = OverlayedBackend::new(&db);
1383
1384				let mut approval_voting_sender = ctx.sender().clone();
1385				let result = handle_new_head(
1386					ctx.sender(),
1387					&mut approval_voting_sender,
1388					&state,
1389					&mut overlay_db,
1390					&mut session_info_provider,
1391					hash,
1392					&Some(1),
1393				)
1394				.await
1395				.unwrap();
1396
1397				let write_ops = overlay_db.into_write_ops();
1398				db.write(write_ops).unwrap();
1399
1400				assert_eq!(result.len(), 1);
1401				let candidates = &result[0].imported_candidates;
1402				assert_eq!(candidates.len(), 2);
1403				assert_eq!(candidates[0].1.approvals().len(), 6);
1404				assert_eq!(candidates[1].1.approvals().len(), 6);
1405				// the first candidate should be insta-approved
1406				// the second should not
1407				let entry: BlockEntry = load_block_entry(db_writer.as_ref(), &TEST_CONFIG, &hash)
1408					.unwrap()
1409					.unwrap()
1410					.into();
1411				assert!(entry.is_candidate_approved(&candidates[0].0));
1412				assert!(!entry.is_candidate_approved(&candidates[1].0));
1413			})
1414		};
1415
1416		let aux_fut = Box::pin(async move {
1417			assert_matches!(
1418				handle.recv().await,
1419				AllMessages::ChainApi(ChainApiMessage::BlockHeader(
1420					h,
1421					tx,
1422				)) => {
1423					assert_eq!(h, hash);
1424					let _ = tx.send(Ok(Some(header.clone())));
1425				}
1426			);
1427
1428			// determine_new_blocks exits early as the parent_hash is in the DB
1429
1430			assert_matches!(
1431				handle.recv().await,
1432				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
1433					h,
1434					RuntimeApiRequest::CandidateEvents(c_tx),
1435				)) => {
1436					assert_eq!(h, hash.clone());
1437					let _ = c_tx.send(Ok(inclusion_events));
1438				}
1439			);
1440
1441			assert_matches!(
1442				handle.recv().await,
1443				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
1444					h,
1445					RuntimeApiRequest::SessionIndexForChild(c_tx),
1446				)) => {
1447					assert_eq!(h, parent_hash.clone());
1448					let _ = c_tx.send(Ok(session));
1449				}
1450			);
1451
1452			assert_matches!(
1453				handle.recv().await,
1454				AllMessages::RuntimeApi(RuntimeApiMessage::Request(
1455					h,
1456					RuntimeApiRequest::CurrentBabeEpoch(c_tx),
1457				)) => {
1458					assert_eq!(h, hash);
1459					let _ = c_tx.send(Ok(BabeEpoch {
1460						epoch_index: session as _,
1461						start_slot: Slot::from(0),
1462						duration: 200,
1463						authorities: vec![(Sr25519Keyring::Alice.public().into(), 1)],
1464						randomness: [0u8; 32],
1465						config: BabeEpochConfiguration {
1466							c: (1, 4),
1467							allowed_slots: AllowedSlots::PrimarySlots,
1468						},
1469					}));
1470				}
1471			);
1472
1473			assert_matches!(
1474				handle.recv().await,
1475				AllMessages::RuntimeApi(
1476					RuntimeApiMessage::Request(
1477						req_block_hash,
1478						RuntimeApiRequest::SessionInfo(idx, si_tx),
1479					)
1480				) => {
1481					assert_eq!(session, idx);
1482					assert_eq!(req_block_hash, hash);
1483					si_tx.send(Ok(Some(session_info.clone()))).unwrap();
1484				}
1485			);
1486
1487			assert_matches!(
1488				handle.recv().await,
1489				AllMessages::RuntimeApi(
1490					RuntimeApiMessage::Request(
1491						req_block_hash,
1492						RuntimeApiRequest::SessionExecutorParams(idx, si_tx),
1493					)
1494				) => {
1495					assert_eq!(session, idx);
1496					assert_eq!(req_block_hash, hash);
1497					si_tx.send(Ok(Some(ExecutorParams::default()))).unwrap();
1498				}
1499			);
1500
1501			assert_matches!(
1502				handle.recv().await,
1503				AllMessages::RuntimeApi(
1504					RuntimeApiMessage::Request(_, RuntimeApiRequest::NodeFeatures(_, si_tx), )
1505				) => {
1506					si_tx.send(Ok(NodeFeatures::EMPTY)).unwrap();
1507				}
1508			);
1509
1510			assert_matches!(
1511				handle.recv().await,
1512				AllMessages::ApprovalDistribution(ApprovalDistributionMessage::NewBlocks(
1513					approval_meta
1514				)) => {
1515					assert_eq!(approval_meta.len(), 1);
1516				}
1517			);
1518		});
1519
1520		futures::executor::block_on(futures::future::join(test_fut, aux_fut));
1521	}
1522}