Skip to main content

pezkuwi_node_core_provisioner/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd. and Dijital Kurdistan Tech Institute
2// This file is part of Pezkuwi.
3
4// Pezkuwi 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// Pezkuwi 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 Pezkuwi.  If not, see <http://www.gnu.org/licenses/>.
16
17//! The provisioner is responsible for assembling a relay chain block
18//! from a set of available teyrchain candidates of its choice.
19
20#![deny(missing_docs, unused_crate_dependencies)]
21
22use bitvec::vec::BitVec;
23use futures::{
24	channel::oneshot::{self, Canceled},
25	future::BoxFuture,
26	prelude::*,
27	stream::FuturesUnordered,
28	FutureExt,
29};
30use futures_timer::Delay;
31use pezkuwi_node_subsystem::{
32	messages::{
33		Ancestors, CandidateBackingMessage, ChainApiMessage, ProspectiveTeyrchainsMessage,
34		ProvisionableData, ProvisionerInherentData, ProvisionerMessage,
35	},
36	overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem,
37	SubsystemError,
38};
39use pezkuwi_node_subsystem_util::{request_availability_cores, TimeoutExt};
40use pezkuwi_primitives::{
41	BackedCandidate, CandidateEvent, CandidateHash, CoreIndex, CoreState, Hash, Id as ParaId,
42	SignedAvailabilityBitfield, ValidatorIndex,
43};
44use pezsc_consensus_slots::time_until_next_slot;
45use schnellru::{ByLength, LruMap};
46use std::{
47	collections::{BTreeMap, HashMap},
48	time::Duration,
49};
50mod disputes;
51mod error;
52mod metrics;
53
54pub use self::metrics::*;
55use error::{Error, FatalResult};
56
57#[cfg(test)]
58mod tests;
59
60/// How long to wait before proposing.
61const PRE_PROPOSE_TIMEOUT: std::time::Duration = core::time::Duration::from_millis(2000);
62/// Some timeout to ensure task won't hang around in the background forever on issues.
63const SEND_INHERENT_DATA_TIMEOUT: std::time::Duration = core::time::Duration::from_millis(500);
64
65const LOG_TARGET: &str = "teyrchain::provisioner";
66
67/// The provisioner subsystem.
68pub struct ProvisionerSubsystem {
69	metrics: Metrics,
70}
71
72impl ProvisionerSubsystem {
73	/// Create a new instance of the `ProvisionerSubsystem`.
74	pub fn new(metrics: Metrics) -> Self {
75		Self { metrics }
76	}
77}
78
79/// A per-relay-parent state for the provisioning subsystem.
80pub struct PerRelayParent {
81	leaf: ActivatedLeaf,
82	signed_bitfields: Vec<SignedAvailabilityBitfield>,
83	is_inherent_ready: bool,
84	awaiting_inherent: Vec<oneshot::Sender<ProvisionerInherentData>>,
85}
86
87impl PerRelayParent {
88	fn new(leaf: ActivatedLeaf) -> Self {
89		Self {
90			leaf,
91			signed_bitfields: Vec::new(),
92			is_inherent_ready: false,
93			awaiting_inherent: Vec::new(),
94		}
95	}
96}
97
98type InherentDelays = FuturesUnordered<BoxFuture<'static, Hash>>;
99type SlotDelays = FuturesUnordered<BoxFuture<'static, Hash>>;
100type InherentReceivers =
101	FuturesUnordered<BoxFuture<'static, (Hash, Result<ProvisionerInherentData, Canceled>)>>;
102
103#[overseer::subsystem(Provisioner, error=SubsystemError, prefix=self::overseer)]
104impl<Context> ProvisionerSubsystem {
105	fn start(self, ctx: Context) -> SpawnedSubsystem {
106		let future = async move {
107			run(ctx, self.metrics)
108				.await
109				.map_err(|e| SubsystemError::with_origin("provisioner", e))
110		}
111		.boxed();
112
113		SpawnedSubsystem { name: "provisioner-subsystem", future }
114	}
115}
116
117#[overseer::contextbounds(Provisioner, prefix = self::overseer)]
118async fn run<Context>(mut ctx: Context, metrics: Metrics) -> FatalResult<()> {
119	let mut inherent_delays = InherentDelays::new();
120	let mut inherent_receivers = InherentReceivers::new();
121	let mut slot_delays = SlotDelays::new();
122	let mut per_relay_parent = HashMap::new();
123	let mut inherents = LruMap::new(ByLength::new(16));
124
125	loop {
126		let result = run_iteration(
127			&mut ctx,
128			&mut per_relay_parent,
129			&mut inherent_delays,
130			&mut inherent_receivers,
131			&mut inherents,
132			&mut slot_delays,
133			&metrics,
134		)
135		.await;
136
137		match result {
138			Ok(()) => break,
139			err => crate::error::log_error(err)?,
140		}
141	}
142
143	Ok(())
144}
145
146#[overseer::contextbounds(Provisioner, prefix = self::overseer)]
147async fn run_iteration<Context>(
148	ctx: &mut Context,
149	per_relay_parent: &mut HashMap<Hash, PerRelayParent>,
150	inherent_delays: &mut InherentDelays,
151	inherent_receivers: &mut InherentReceivers,
152	inherents: &mut LruMap<Hash, ProvisionerInherentData>,
153	slot_delays: &mut SlotDelays,
154	metrics: &Metrics,
155) -> Result<(), Error> {
156	loop {
157		futures::select! {
158			from_overseer = ctx.recv().fuse() => {
159				// Map the error to ensure that the subsystem exits when the overseer is gone.
160				match from_overseer.map_err(Error::OverseerExited)? {
161					FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) =>
162						handle_active_leaves_update(ctx, update, per_relay_parent, inherent_delays, slot_delays, inherents, metrics).await?,
163					FromOrchestra::Signal(OverseerSignal::BlockFinalized(..)) => {},
164					FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()),
165					FromOrchestra::Communication { msg } => {
166						handle_communication(ctx, per_relay_parent, msg, metrics).await?;
167					},
168				}
169			},
170			hash = slot_delays.select_next_some() => {
171				gum::debug!(target: LOG_TARGET, leaf_hash=?hash, "Slot start, preparing debug inherent");
172
173				let Some(state) = per_relay_parent.get_mut(&hash) else {
174					continue
175				};
176
177				// Create the inherent data just to record the backed candidates.
178				let (inherent_tx, inherent_rx) = oneshot::channel();
179				let task = async move {
180					match inherent_rx.await {
181						Ok(res) => (hash, Ok(res)),
182						Err(e) => (hash, Err(e)),
183					}
184				}
185				.boxed();
186
187				inherent_receivers.push(task);
188
189				send_inherent_data_bg(ctx, &state, vec![inherent_tx], metrics.clone()).await?;
190			},
191			(hash, inherent_data) = inherent_receivers.select_next_some() => {
192				let Ok(inherent_data) = inherent_data else {
193					continue
194				};
195
196				gum::trace!(
197					target: LOG_TARGET,
198					relay_parent = ?hash,
199					"Debug Inherent Data became ready"
200				);
201				inherents.insert(hash, inherent_data);
202			}
203			hash = inherent_delays.select_next_some() => {
204				if let Some(state) = per_relay_parent.get_mut(&hash) {
205					state.is_inherent_ready = true;
206
207					gum::trace!(
208						target: LOG_TARGET,
209						relay_parent = ?hash,
210						"Inherent Data became ready"
211					);
212
213					let return_senders = std::mem::take(&mut state.awaiting_inherent);
214					if !return_senders.is_empty() {
215						send_inherent_data_bg(ctx, &state, return_senders, metrics.clone()).await?;
216					}
217				}
218			}
219		}
220	}
221}
222
223#[overseer::contextbounds(Provisioner, prefix = self::overseer)]
224async fn handle_active_leaves_update<Context>(
225	ctx: &mut Context,
226	update: ActiveLeavesUpdate,
227	per_relay_parent: &mut HashMap<Hash, PerRelayParent>,
228	inherent_delays: &mut InherentDelays,
229	slot_delays: &mut SlotDelays,
230	inherents: &mut LruMap<Hash, ProvisionerInherentData>,
231	metrics: &Metrics,
232) -> Result<(), Error> {
233	gum::trace!(target: LOG_TARGET, "Handle ActiveLeavesUpdate");
234	for deactivated in &update.deactivated {
235		per_relay_parent.remove(deactivated);
236	}
237
238	let Some(leaf) = update.activated else { return Ok(()) };
239
240	gum::trace!(target: LOG_TARGET, leaf_hash=?leaf.hash, "Adding delay");
241	let delay_fut = Delay::new(PRE_PROPOSE_TIMEOUT).map(move |_| leaf.hash).boxed();
242	per_relay_parent.insert(leaf.hash, PerRelayParent::new(leaf.clone()));
243	inherent_delays.push(delay_fut);
244
245	let slot_delay = time_until_next_slot(Duration::from_millis(6000));
246	gum::debug!(target: LOG_TARGET, leaf_hash=?leaf.hash, "Expecting next slot in {}ms", slot_delay.as_millis());
247
248	let slot_delay_task =
249		Delay::new(slot_delay + PRE_PROPOSE_TIMEOUT).map(move |_| leaf.hash).boxed();
250	slot_delays.push(slot_delay_task);
251
252	let Ok(Ok(candidate_events)) =
253		pezkuwi_node_subsystem_util::request_candidate_events(leaf.hash, ctx.sender())
254			.await
255			.await
256	else {
257		gum::warn!(target: LOG_TARGET, leaf_hash=?leaf.hash, "Failed to fetch candidate events");
258
259		return Ok(());
260	};
261
262	let in_block_count = candidate_events
263		.into_iter()
264		.filter(|event| matches!(event, CandidateEvent::CandidateBacked(_, _, _, _)))
265		.count() as isize;
266
267	let (tx, rx) = oneshot::channel();
268	ctx.send_message(ChainApiMessage::BlockHeader(leaf.hash, tx)).await;
269
270	let Ok(Some(header)) = rx.await.unwrap_or_else(|err| {
271		gum::warn!(target: LOG_TARGET, hash = ?leaf.hash, ?err, "Missing header for block");
272		Ok(None)
273	}) else {
274		return Ok(());
275	};
276
277	gum::trace!(target: LOG_TARGET, hash = ?header.parent_hash, "Looking up debug inherent");
278
279	// Now, let's get the candidate count from our own inherent built earlier.
280	// The inherent is stored under the parent hash.
281	let Some(inherent) = inherents.get(&header.parent_hash) else { return Ok(()) };
282
283	let diff = inherent.backed_candidates.len() as isize - in_block_count;
284	gum::debug!(target: LOG_TARGET, 
285		 ?diff,
286		 ?in_block_count,
287		 local_count = ?inherent.backed_candidates.len(),
288		 leaf_hash=?leaf.hash, "Offchain vs on-chain backing update");
289
290	metrics.observe_backable_vs_in_block(diff);
291	Ok(())
292}
293
294#[overseer::contextbounds(Provisioner, prefix = self::overseer)]
295async fn handle_communication<Context>(
296	ctx: &mut Context,
297	per_relay_parent: &mut HashMap<Hash, PerRelayParent>,
298	message: ProvisionerMessage,
299	metrics: &Metrics,
300) -> Result<(), Error> {
301	match message {
302		ProvisionerMessage::RequestInherentData(relay_parent, return_sender) => {
303			gum::trace!(target: LOG_TARGET, ?relay_parent, "Inherent data got requested.");
304
305			if let Some(state) = per_relay_parent.get_mut(&relay_parent) {
306				if state.is_inherent_ready {
307					gum::trace!(target: LOG_TARGET, ?relay_parent, "Calling send_inherent_data.");
308					send_inherent_data_bg(ctx, &state, vec![return_sender], metrics.clone())
309						.await?;
310				} else {
311					gum::trace!(
312						target: LOG_TARGET,
313						?relay_parent,
314						"Queuing inherent data request (inherent data not yet ready)."
315					);
316					state.awaiting_inherent.push(return_sender);
317				}
318			}
319		},
320		ProvisionerMessage::ProvisionableData(relay_parent, data) => {
321			if let Some(state) = per_relay_parent.get_mut(&relay_parent) {
322				let _timer = metrics.time_provisionable_data();
323
324				gum::trace!(target: LOG_TARGET, ?relay_parent, "Received provisionable data: {:?}", &data);
325
326				note_provisionable_data(state, data);
327			}
328		},
329	}
330
331	Ok(())
332}
333
334#[overseer::contextbounds(Provisioner, prefix = self::overseer)]
335async fn send_inherent_data_bg<Context>(
336	ctx: &mut Context,
337	per_relay_parent: &PerRelayParent,
338	return_senders: Vec<oneshot::Sender<ProvisionerInherentData>>,
339	metrics: Metrics,
340) -> Result<(), Error> {
341	let leaf = per_relay_parent.leaf.clone();
342	let signed_bitfields = per_relay_parent.signed_bitfields.clone();
343	let mut sender = ctx.sender().clone();
344
345	let bg = async move {
346		let _timer = metrics.time_request_inherent_data();
347
348		gum::trace!(
349			target: LOG_TARGET,
350			relay_parent = ?leaf.hash,
351			"Sending inherent data in background."
352		);
353
354		let send_result =
355			send_inherent_data(&leaf, &signed_bitfields, return_senders, &mut sender, &metrics) // Make sure call is not taking forever:
356				.timeout(SEND_INHERENT_DATA_TIMEOUT)
357				.map(|v| match v {
358					Some(r) => r,
359					None => Err(Error::SendInherentDataTimeout),
360				});
361
362		match send_result.await {
363			Err(err) => {
364				if let Error::CanceledBackedCandidates(_) = err {
365					gum::debug!(
366						target: LOG_TARGET,
367						err = ?err,
368						"Failed to assemble or send inherent data - block got likely obsoleted already."
369					);
370				} else {
371					gum::warn!(target: LOG_TARGET, err = ?err, "failed to assemble or send inherent data");
372				}
373				metrics.on_inherent_data_request(Err(()));
374			},
375			Ok(()) => {
376				metrics.on_inherent_data_request(Ok(()));
377				gum::debug!(
378					target: LOG_TARGET,
379					signed_bitfield_count = signed_bitfields.len(),
380					leaf_hash = ?leaf.hash,
381					"inherent data sent successfully"
382				);
383				metrics.observe_inherent_data_bitfields_count(signed_bitfields.len());
384			},
385		}
386	};
387
388	ctx.spawn("send-inherent-data", bg.boxed())
389		.map_err(|_| Error::FailedToSpawnBackgroundTask)?;
390
391	Ok(())
392}
393
394fn note_provisionable_data(
395	per_relay_parent: &mut PerRelayParent,
396	provisionable_data: ProvisionableData,
397) {
398	match provisionable_data {
399		ProvisionableData::Bitfield(_, signed_bitfield) => {
400			per_relay_parent.signed_bitfields.push(signed_bitfield)
401		},
402		// We choose not to punish these forms of misbehavior for the time being.
403		// Risks from misbehavior are sufficiently mitigated at the protocol level
404		// via reputation changes. Punitive actions here may become desirable
405		// enough to dedicate time to in the future.
406		ProvisionableData::MisbehaviorReport(_, _, _) => {},
407		// We wait and do nothing here, preferring to initiate a dispute after the
408		// parablock candidate is included for the following reasons:
409		//
410		// 1. A dispute for a candidate triggered at any point before the candidate
411		// has been made available, including the backing stage, can't be
412		// guaranteed to conclude. Non-concluding disputes are unacceptable.
413		// 2. Candidates which haven't been made available don't pose a security
414		// risk as they can not be included, approved, or finalized.
415		//
416		// Currently we rely on approval checkers to trigger disputes for bad
417		// parablocks once they are included. But we can do slightly better by
418		// allowing disagreeing backers to record their disagreement and initiate a
419		// dispute once the parablock in question has been included. This potential
420		// change is tracked by: https://github.com/pezkuwichain/pezkuwi-sdk/issues/140
421		ProvisionableData::Dispute(_, _) => {},
422	}
423}
424
425type CoreAvailability = BitVec<u8, bitvec::order::Lsb0>;
426
427/// The provisioner is the subsystem best suited to choosing which specific
428/// backed candidates and availability bitfields should be assembled into the
429/// block. To engage this functionality, a
430/// `ProvisionerMessage::RequestInherentData` is sent; the response is a set of
431/// non-conflicting candidates and the appropriate bitfields. Non-conflicting
432/// means that there are never two distinct teyrchain candidates included for
433/// the same teyrchain and that new teyrchain candidates cannot be included
434/// until the previous one either gets declared available or expired.
435///
436/// The main complication here is going to be around handling
437/// occupied-core-assumptions. We might have candidates that are only
438/// includable when some bitfields are included. And we might have candidates
439/// that are not includable when certain bitfields are included.
440///
441/// When we're choosing bitfields to include, the rule should be simple:
442/// maximize availability. So basically, include all bitfields. And then
443/// choose a coherent set of candidates along with that.
444async fn send_inherent_data(
445	leaf: &ActivatedLeaf,
446	bitfields: &[SignedAvailabilityBitfield],
447	return_senders: Vec<oneshot::Sender<ProvisionerInherentData>>,
448	from_job: &mut impl overseer::ProvisionerSenderTrait,
449	metrics: &Metrics,
450) -> Result<(), Error> {
451	gum::trace!(
452		target: LOG_TARGET,
453		relay_parent = ?leaf.hash,
454		"Requesting availability cores"
455	);
456	let availability_cores = request_availability_cores(leaf.hash, from_job)
457		.await
458		.await
459		.map_err(|err| Error::CanceledAvailabilityCores(err))??;
460
461	gum::trace!(
462		target: LOG_TARGET,
463		relay_parent = ?leaf.hash,
464		"Selecting disputes"
465	);
466
467	let disputes = disputes::prioritized_selection::select_disputes(from_job, metrics, leaf).await;
468
469	gum::trace!(
470		target: LOG_TARGET,
471		relay_parent = ?leaf.hash,
472		"Selected disputes"
473	);
474
475	let bitfields = select_availability_bitfields(&availability_cores, bitfields, &leaf.hash);
476
477	gum::trace!(
478		target: LOG_TARGET,
479		relay_parent = ?leaf.hash,
480		"Selected bitfields"
481	);
482
483	let candidates = select_candidates(&availability_cores, &bitfields, leaf, from_job).await?;
484
485	gum::trace!(
486		target: LOG_TARGET,
487		relay_parent = ?leaf.hash,
488		"Selected candidates"
489	);
490
491	gum::debug!(
492		target: LOG_TARGET,
493		availability_cores_len = availability_cores.len(),
494		disputes_count = disputes.len(),
495		bitfields_count = bitfields.len(),
496		candidates_count = candidates.len(),
497		leaf_hash = ?leaf.hash,
498		"inherent data prepared",
499	);
500
501	let inherent_data =
502		ProvisionerInherentData { bitfields, backed_candidates: candidates, disputes };
503
504	gum::trace!(
505		target: LOG_TARGET,
506		relay_parent = ?leaf.hash,
507		"Sending back inherent data to requesters."
508	);
509
510	for return_sender in return_senders {
511		return_sender
512			.send(inherent_data.clone())
513			.map_err(|_data| Error::InherentDataReturnChannel)?;
514	}
515
516	Ok(())
517}
518
519/// In general, we want to pick all the bitfields. However, we have the following constraints:
520///
521/// - not more than one per validator
522/// - each 1 bit must correspond to an occupied core
523///
524/// If we have too many, an arbitrary selection policy is fine. For purposes of maximizing
525/// availability, we pick the one with the greatest number of 1 bits.
526///
527/// Note: This does not enforce any sorting precondition on the output; the ordering there will be
528/// unrelated to the sorting of the input.
529fn select_availability_bitfields(
530	cores: &[CoreState],
531	bitfields: &[SignedAvailabilityBitfield],
532	leaf_hash: &Hash,
533) -> Vec<SignedAvailabilityBitfield> {
534	let mut selected: BTreeMap<ValidatorIndex, SignedAvailabilityBitfield> = BTreeMap::new();
535
536	gum::debug!(
537		target: LOG_TARGET,
538		bitfields_count = bitfields.len(),
539		?leaf_hash,
540		"bitfields count before selection"
541	);
542
543	'a: for bitfield in bitfields.iter().cloned() {
544		if bitfield.payload().0.len() != cores.len() {
545			gum::debug!(target: LOG_TARGET, ?leaf_hash, "dropping bitfield due to length mismatch");
546			continue;
547		}
548
549		let is_better = selected
550			.get(&bitfield.validator_index())
551			.map_or(true, |b| b.payload().0.count_ones() < bitfield.payload().0.count_ones());
552
553		if !is_better {
554			gum::trace!(
555				target: LOG_TARGET,
556				val_idx = bitfield.validator_index().0,
557				?leaf_hash,
558				"dropping bitfield due to duplication - the better one is kept"
559			);
560			continue;
561		}
562
563		for (idx, _) in cores.iter().enumerate().filter(|v| !v.1.is_occupied()) {
564			// Bit is set for an unoccupied core - invalid
565			if *bitfield.payload().0.get(idx).as_deref().unwrap_or(&false) {
566				gum::debug!(
567					target: LOG_TARGET,
568					val_idx = bitfield.validator_index().0,
569					?leaf_hash,
570					"dropping invalid bitfield - bit is set for an unoccupied core"
571				);
572				continue 'a;
573			}
574		}
575
576		let _ = selected.insert(bitfield.validator_index(), bitfield);
577	}
578
579	gum::debug!(
580		target: LOG_TARGET,
581		?leaf_hash,
582		"selected {} of all {} bitfields (each bitfield is from a unique validator)",
583		selected.len(),
584		bitfields.len()
585	);
586
587	selected.into_values().collect()
588}
589
590/// Requests backable candidates from Prospective Teyrchains subsystem
591/// based on core states.
592async fn request_backable_candidates(
593	availability_cores: &[CoreState],
594	bitfields: &[SignedAvailabilityBitfield],
595	relay_parent: &ActivatedLeaf,
596	sender: &mut impl overseer::ProvisionerSenderTrait,
597) -> Result<HashMap<ParaId, Vec<(CandidateHash, Hash)>>, Error> {
598	let block_number_under_construction = relay_parent.number + 1;
599
600	// Record how many cores are scheduled for each paraid. Use a BTreeMap because
601	// we'll need to iterate through them.
602	let mut scheduled_cores_per_para: BTreeMap<ParaId, usize> = BTreeMap::new();
603	// The on-chain ancestors of a para present in availability-cores.
604	let mut ancestors: HashMap<ParaId, Ancestors> =
605		HashMap::with_capacity(availability_cores.len());
606
607	for (core_idx, core) in availability_cores.iter().enumerate() {
608		let core_idx = CoreIndex(core_idx as u32);
609		match core {
610			CoreState::Scheduled(scheduled_core) => {
611				*scheduled_cores_per_para.entry(scheduled_core.para_id).or_insert(0) += 1;
612			},
613			CoreState::Occupied(occupied_core) => {
614				let is_available = bitfields_indicate_availability(
615					core_idx.0 as usize,
616					bitfields,
617					&occupied_core.availability,
618				);
619
620				if is_available {
621					ancestors
622						.entry(occupied_core.para_id())
623						.or_default()
624						.insert(occupied_core.candidate_hash);
625
626					if let Some(ref scheduled_core) = occupied_core.next_up_on_available {
627						// Request a new backable candidate for the newly scheduled para id.
628						*scheduled_cores_per_para.entry(scheduled_core.para_id).or_insert(0) += 1;
629					}
630				} else if occupied_core.time_out_at <= block_number_under_construction {
631					// Timed out before being available.
632
633					if let Some(ref scheduled_core) = occupied_core.next_up_on_time_out {
634						// Candidate's availability timed out, practically same as scheduled.
635						*scheduled_cores_per_para.entry(scheduled_core.para_id).or_insert(0) += 1;
636					}
637				} else {
638					// Not timed out and not available.
639					ancestors
640						.entry(occupied_core.para_id())
641						.or_default()
642						.insert(occupied_core.candidate_hash);
643				}
644			},
645			CoreState::Free => continue,
646		};
647	}
648
649	let mut selected_candidates: HashMap<ParaId, Vec<(CandidateHash, Hash)>> =
650		HashMap::with_capacity(scheduled_cores_per_para.len());
651
652	for (para_id, core_count) in scheduled_cores_per_para {
653		let para_ancestors = ancestors.remove(&para_id).unwrap_or_default();
654
655		let response = get_backable_candidates(
656			relay_parent.hash,
657			para_id,
658			para_ancestors,
659			core_count as u32,
660			sender,
661		)
662		.await?;
663
664		if response.is_empty() {
665			gum::debug!(
666				target: LOG_TARGET,
667				leaf_hash = ?relay_parent.hash,
668				?para_id,
669				"No backable candidate returned by prospective teyrchains",
670			);
671			continue;
672		}
673
674		selected_candidates.insert(para_id, response);
675	}
676
677	Ok(selected_candidates)
678}
679
680/// Determine which cores are free, and then to the degree possible, pick a candidate appropriate to
681/// each free core.
682async fn select_candidates(
683	availability_cores: &[CoreState],
684	bitfields: &[SignedAvailabilityBitfield],
685	leaf: &ActivatedLeaf,
686	sender: &mut impl overseer::ProvisionerSenderTrait,
687) -> Result<Vec<BackedCandidate>, Error> {
688	let relay_parent = leaf.hash;
689	gum::trace!(
690		target: LOG_TARGET,
691		leaf_hash=?relay_parent,
692		"before GetBackedCandidates"
693	);
694
695	let selected_candidates =
696		request_backable_candidates(availability_cores, bitfields, leaf, sender).await?;
697	gum::debug!(target: LOG_TARGET, ?selected_candidates, "Got backable candidates");
698
699	// now get the backed candidates corresponding to these candidate receipts
700	let (tx, rx) = oneshot::channel();
701	sender.send_unbounded_message(CandidateBackingMessage::GetBackableCandidates(
702		selected_candidates.clone(),
703		tx,
704	));
705	let candidates = rx.await.map_err(|err| Error::CanceledBackedCandidates(err))?;
706	gum::trace!(
707		target: LOG_TARGET,
708		leaf_hash=?relay_parent,
709		"Got {} backed candidates", candidates.len()
710	);
711
712	// keep only one candidate with validation code.
713	let mut with_validation_code = false;
714	// merge the candidates into a common collection, preserving the order
715	let mut merged_candidates = Vec::with_capacity(availability_cores.len());
716
717	for para_candidates in candidates.into_values() {
718		for candidate in para_candidates {
719			if candidate.candidate().commitments.new_validation_code.is_some() {
720				if with_validation_code {
721					break;
722				} else {
723					with_validation_code = true;
724				}
725			}
726
727			merged_candidates.push(candidate);
728		}
729	}
730
731	gum::debug!(
732		target: LOG_TARGET,
733		n_candidates = merged_candidates.len(),
734		n_cores = availability_cores.len(),
735		?relay_parent,
736		"Selected backed candidates",
737	);
738
739	Ok(merged_candidates)
740}
741
742/// Requests backable candidates from Prospective Teyrchains based on
743/// the given ancestors in the fragment chain. The ancestors may not be ordered.
744async fn get_backable_candidates(
745	relay_parent: Hash,
746	para_id: ParaId,
747	ancestors: Ancestors,
748	count: u32,
749	sender: &mut impl overseer::ProvisionerSenderTrait,
750) -> Result<Vec<(CandidateHash, Hash)>, Error> {
751	let (tx, rx) = oneshot::channel();
752	sender
753		.send_message(ProspectiveTeyrchainsMessage::GetBackableCandidates(
754			relay_parent,
755			para_id,
756			count,
757			ancestors,
758			tx,
759		))
760		.await;
761
762	rx.await.map_err(Error::CanceledBackableCandidates)
763}
764
765/// The availability bitfield for a given core is the transpose
766/// of a set of signed availability bitfields. It goes like this:
767///
768/// - construct a transverse slice along `core_idx`
769/// - bitwise-or it with the availability slice
770/// - count the 1 bits, compare to the total length; true on 2/3+
771fn bitfields_indicate_availability(
772	core_idx: usize,
773	bitfields: &[SignedAvailabilityBitfield],
774	availability: &CoreAvailability,
775) -> bool {
776	let mut availability = availability.clone();
777	let availability_len = availability.len();
778
779	for bitfield in bitfields {
780		let validator_idx = bitfield.validator_index().0 as usize;
781		match availability.get_mut(validator_idx) {
782			None => {
783				// in principle, this function might return a `Result<bool, Error>` so that we can
784				// more clearly express this error condition however, in practice, that would just
785				// push off an error-handling routine which would look a whole lot like this one.
786				// simpler to just handle the error internally here.
787				gum::warn!(
788					target: LOG_TARGET,
789					validator_idx = %validator_idx,
790					availability_len = %availability_len,
791					"attempted to set a transverse bit at idx {} which is greater than bitfield size {}",
792					validator_idx,
793					availability_len,
794				);
795
796				return false;
797			},
798			Some(mut bit_mut) => *bit_mut |= bitfield.payload().0[core_idx],
799		}
800	}
801
802	3 * availability.count_ones() >= 2 * availability.len()
803}