1use 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 #[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#[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 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 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 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 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
318pub 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
326pub(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 return Ok(Vec::new())
364 },
365 Ok(None) => {
366 gum::warn!(target: LOG_TARGET, "Missing header for new head {}", head);
367 return Ok(Vec::new())
369 },
370 Ok(Some(h)) => h,
371 }
372 };
373
374 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 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 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 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 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 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 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 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 }
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 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 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 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}