1#![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 polkadot_node_subsystem::{
32 messages::{
33 Ancestors, BackableCandidateRef, CandidateBackingMessage, ChainApiMessage,
34 ProspectiveParachainsMessage, ProvisionableData, ProvisionerInherentData,
35 ProvisionerMessage,
36 },
37 overseer, ActivatedLeaf, ActiveLeavesUpdate, FromOrchestra, OverseerSignal, SpawnedSubsystem,
38 SubsystemError,
39};
40use polkadot_node_subsystem_util::{request_availability_cores, TimeoutExt};
41use polkadot_primitives::{
42 BackedCandidate, CandidateEvent, CoreIndex, CoreState, Hash, Id as ParaId,
43 SignedAvailabilityBitfield, ValidatorIndex,
44};
45use sc_consensus_slots::time_until_next_slot;
46use schnellru::{ByLength, LruMap};
47use std::{
48 collections::{BTreeMap, HashMap},
49 time::Duration,
50};
51mod disputes;
52mod error;
53mod metrics;
54
55pub use self::metrics::*;
56use error::{Error, FatalResult};
57
58#[cfg(test)]
59mod tests;
60
61const PRE_PROPOSE_TIMEOUT: std::time::Duration = core::time::Duration::from_millis(2000);
63const SEND_INHERENT_DATA_TIMEOUT: std::time::Duration = core::time::Duration::from_millis(500);
65
66const LOG_TARGET: &str = "parachain::provisioner";
67
68pub struct ProvisionerSubsystem {
70 metrics: Metrics,
71}
72
73impl ProvisionerSubsystem {
74 pub fn new(metrics: Metrics) -> Self {
76 Self { metrics }
77 }
78}
79
80pub struct PerRelayParent {
82 leaf: ActivatedLeaf,
83 signed_bitfields: Vec<SignedAvailabilityBitfield>,
84 is_inherent_ready: bool,
85 awaiting_inherent: Vec<oneshot::Sender<ProvisionerInherentData>>,
86}
87
88impl PerRelayParent {
89 fn new(leaf: ActivatedLeaf) -> Self {
90 Self {
91 leaf,
92 signed_bitfields: Vec::new(),
93 is_inherent_ready: false,
94 awaiting_inherent: Vec::new(),
95 }
96 }
97}
98
99type InherentDelays = FuturesUnordered<BoxFuture<'static, Hash>>;
100type SlotDelays = FuturesUnordered<BoxFuture<'static, Hash>>;
101type InherentReceivers =
102 FuturesUnordered<BoxFuture<'static, (Hash, Result<ProvisionerInherentData, Canceled>)>>;
103
104#[overseer::subsystem(Provisioner, error=SubsystemError, prefix=self::overseer)]
105impl<Context> ProvisionerSubsystem {
106 fn start(self, ctx: Context) -> SpawnedSubsystem {
107 let future = async move {
108 run(ctx, self.metrics)
109 .await
110 .map_err(|e| SubsystemError::with_origin("provisioner", e))
111 }
112 .boxed();
113
114 SpawnedSubsystem { name: "provisioner-subsystem", future }
115 }
116}
117
118#[overseer::contextbounds(Provisioner, prefix = self::overseer)]
119async fn run<Context>(mut ctx: Context, metrics: Metrics) -> FatalResult<()> {
120 let mut inherent_delays = InherentDelays::new();
121 let mut inherent_receivers = InherentReceivers::new();
122 let mut slot_delays = SlotDelays::new();
123 let mut per_relay_parent = HashMap::new();
124 let mut inherents = LruMap::new(ByLength::new(16));
125
126 loop {
127 let result = run_iteration(
128 &mut ctx,
129 &mut per_relay_parent,
130 &mut inherent_delays,
131 &mut inherent_receivers,
132 &mut inherents,
133 &mut slot_delays,
134 &metrics,
135 )
136 .await;
137
138 match result {
139 Ok(()) => break,
140 err => crate::error::log_error(err)?,
141 }
142 }
143
144 Ok(())
145}
146
147#[overseer::contextbounds(Provisioner, prefix = self::overseer)]
148async fn run_iteration<Context>(
149 ctx: &mut Context,
150 per_relay_parent: &mut HashMap<Hash, PerRelayParent>,
151 inherent_delays: &mut InherentDelays,
152 inherent_receivers: &mut InherentReceivers,
153 inherents: &mut LruMap<Hash, ProvisionerInherentData>,
154 slot_delays: &mut SlotDelays,
155 metrics: &Metrics,
156) -> Result<(), Error> {
157 loop {
158 futures::select! {
159 from_overseer = ctx.recv().fuse() => {
160 match from_overseer.map_err(Error::OverseerExited)? {
162 FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) =>
163 handle_active_leaves_update(ctx, update, per_relay_parent, inherent_delays, slot_delays, inherents, metrics).await?,
164 FromOrchestra::Signal(OverseerSignal::BlockFinalized(..)) => {},
165 FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(()),
166 FromOrchestra::Communication { msg } => {
167 handle_communication(ctx, per_relay_parent, msg, metrics).await?;
168 },
169 }
170 },
171 hash = slot_delays.select_next_some() => {
172 gum::debug!(target: LOG_TARGET, leaf_hash=?hash, "Slot start, preparing debug inherent");
173
174 let Some(state) = per_relay_parent.get_mut(&hash) else {
175 continue
176 };
177
178 let (inherent_tx, inherent_rx) = oneshot::channel();
180 let task = async move {
181 match inherent_rx.await {
182 Ok(res) => (hash, Ok(res)),
183 Err(e) => (hash, Err(e)),
184 }
185 }
186 .boxed();
187
188 inherent_receivers.push(task);
189
190 send_inherent_data_bg(ctx, &state, vec![inherent_tx], metrics.clone()).await?;
191 },
192 (hash, inherent_data) = inherent_receivers.select_next_some() => {
193 let Ok(inherent_data) = inherent_data else {
194 continue
195 };
196
197 gum::trace!(
198 target: LOG_TARGET,
199 relay_parent = ?hash,
200 "Debug Inherent Data became ready"
201 );
202 inherents.insert(hash, inherent_data);
203 }
204 hash = inherent_delays.select_next_some() => {
205 if let Some(state) = per_relay_parent.get_mut(&hash) {
206 state.is_inherent_ready = true;
207
208 gum::trace!(
209 target: LOG_TARGET,
210 relay_parent = ?hash,
211 "Inherent Data became ready"
212 );
213
214 let return_senders = std::mem::take(&mut state.awaiting_inherent);
215 if !return_senders.is_empty() {
216 send_inherent_data_bg(ctx, &state, return_senders, metrics.clone()).await?;
217 }
218 }
219 }
220 }
221 }
222}
223
224#[overseer::contextbounds(Provisioner, prefix = self::overseer)]
225async fn handle_active_leaves_update<Context>(
226 ctx: &mut Context,
227 update: ActiveLeavesUpdate,
228 per_relay_parent: &mut HashMap<Hash, PerRelayParent>,
229 inherent_delays: &mut InherentDelays,
230 slot_delays: &mut SlotDelays,
231 inherents: &mut LruMap<Hash, ProvisionerInherentData>,
232 metrics: &Metrics,
233) -> Result<(), Error> {
234 gum::trace!(target: LOG_TARGET, "Handle ActiveLeavesUpdate");
235 for deactivated in &update.deactivated {
236 per_relay_parent.remove(deactivated);
237 }
238
239 let Some(leaf) = update.activated else { return Ok(()) };
240
241 gum::trace!(target: LOG_TARGET, leaf_hash=?leaf.hash, "Adding delay");
242 let delay_fut = Delay::new(PRE_PROPOSE_TIMEOUT).map(move |_| leaf.hash).boxed();
243 per_relay_parent.insert(leaf.hash, PerRelayParent::new(leaf.clone()));
244 inherent_delays.push(delay_fut);
245
246 let slot_delay = time_until_next_slot(Duration::from_millis(6000));
247 gum::debug!(target: LOG_TARGET, leaf_hash=?leaf.hash, "Expecting next slot in {}ms", slot_delay.as_millis());
248
249 let slot_delay_task =
250 Delay::new(slot_delay + PRE_PROPOSE_TIMEOUT).map(move |_| leaf.hash).boxed();
251 slot_delays.push(slot_delay_task);
252
253 let Ok(Ok(candidate_events)) =
254 polkadot_node_subsystem_util::request_candidate_events(leaf.hash, ctx.sender())
255 .await
256 .await
257 else {
258 gum::warn!(target: LOG_TARGET, leaf_hash=?leaf.hash, "Failed to fetch candidate events");
259
260 return Ok(());
261 };
262
263 let in_block_count = candidate_events
264 .into_iter()
265 .filter(|event| matches!(event, CandidateEvent::CandidateBacked(_, _, _, _)))
266 .count() as isize;
267
268 let (tx, rx) = oneshot::channel();
269 ctx.send_message(ChainApiMessage::BlockHeader(leaf.hash, tx)).await;
270
271 let Ok(Some(header)) = rx.await.unwrap_or_else(|err| {
272 gum::warn!(target: LOG_TARGET, hash = ?leaf.hash, ?err, "Missing header for block");
273 Ok(None)
274 }) else {
275 return Ok(());
276 };
277
278 gum::trace!(target: LOG_TARGET, hash = ?header.parent_hash, "Looking up debug inherent");
279
280 let Some(inherent) = inherents.get(&header.parent_hash) else { return Ok(()) };
283
284 let diff = inherent.backed_candidates.len() as isize - in_block_count;
285 gum::debug!(target: LOG_TARGET,
286 ?diff,
287 ?in_block_count,
288 local_count = ?inherent.backed_candidates.len(),
289 leaf_hash=?leaf.hash, "Offchain vs on-chain backing update");
290
291 metrics.observe_backable_vs_in_block(diff);
292 Ok(())
293}
294
295#[overseer::contextbounds(Provisioner, prefix = self::overseer)]
296async fn handle_communication<Context>(
297 ctx: &mut Context,
298 per_relay_parent: &mut HashMap<Hash, PerRelayParent>,
299 message: ProvisionerMessage,
300 metrics: &Metrics,
301) -> Result<(), Error> {
302 match message {
303 ProvisionerMessage::RequestInherentData(relay_parent, return_sender) => {
304 gum::trace!(target: LOG_TARGET, ?relay_parent, "Inherent data got requested.");
305
306 if let Some(state) = per_relay_parent.get_mut(&relay_parent) {
307 if state.is_inherent_ready {
308 gum::trace!(target: LOG_TARGET, ?relay_parent, "Calling send_inherent_data.");
309 send_inherent_data_bg(ctx, &state, vec![return_sender], metrics.clone())
310 .await?;
311 } else {
312 gum::trace!(
313 target: LOG_TARGET,
314 ?relay_parent,
315 "Queuing inherent data request (inherent data not yet ready)."
316 );
317 state.awaiting_inherent.push(return_sender);
318 }
319 }
320 },
321 ProvisionerMessage::ProvisionableData(relay_parent, data) => {
322 if let Some(state) = per_relay_parent.get_mut(&relay_parent) {
323 let _timer = metrics.time_provisionable_data();
324
325 gum::trace!(target: LOG_TARGET, ?relay_parent, "Received provisionable data: {:?}", &data);
326
327 note_provisionable_data(state, data);
328 }
329 },
330 }
331
332 Ok(())
333}
334
335#[overseer::contextbounds(Provisioner, prefix = self::overseer)]
336async fn send_inherent_data_bg<Context>(
337 ctx: &mut Context,
338 per_relay_parent: &PerRelayParent,
339 return_senders: Vec<oneshot::Sender<ProvisionerInherentData>>,
340 metrics: Metrics,
341) -> Result<(), Error> {
342 let leaf = per_relay_parent.leaf.clone();
343 let signed_bitfields = per_relay_parent.signed_bitfields.clone();
344 let mut sender = ctx.sender().clone();
345
346 let bg = async move {
347 let _timer = metrics.time_request_inherent_data();
348
349 gum::trace!(
350 target: LOG_TARGET,
351 relay_parent = ?leaf.hash,
352 "Sending inherent data in background."
353 );
354
355 let send_result =
356 send_inherent_data(&leaf, &signed_bitfields, return_senders, &mut sender, &metrics) .timeout(SEND_INHERENT_DATA_TIMEOUT)
358 .map(|v| match v {
359 Some(r) => r,
360 None => Err(Error::SendInherentDataTimeout),
361 });
362
363 match send_result.await {
364 Err(err) => {
365 if let Error::CanceledBackedCandidates(_) = err {
366 gum::debug!(
367 target: LOG_TARGET,
368 err = ?err,
369 "Failed to assemble or send inherent data - block got likely obsoleted already."
370 );
371 } else {
372 gum::warn!(target: LOG_TARGET, err = ?err, "failed to assemble or send inherent data");
373 }
374 metrics.on_inherent_data_request(Err(()));
375 },
376 Ok(()) => {
377 metrics.on_inherent_data_request(Ok(()));
378 gum::debug!(
379 target: LOG_TARGET,
380 signed_bitfield_count = signed_bitfields.len(),
381 leaf_hash = ?leaf.hash,
382 "inherent data sent successfully"
383 );
384 metrics.observe_inherent_data_bitfields_count(signed_bitfields.len());
385 },
386 }
387 };
388
389 ctx.spawn("send-inherent-data", bg.boxed())
390 .map_err(|_| Error::FailedToSpawnBackgroundTask)?;
391
392 Ok(())
393}
394
395fn note_provisionable_data(
396 per_relay_parent: &mut PerRelayParent,
397 provisionable_data: ProvisionableData,
398) {
399 match provisionable_data {
400 ProvisionableData::Bitfield(_, signed_bitfield) => {
401 per_relay_parent.signed_bitfields.push(signed_bitfield)
402 },
403 ProvisionableData::MisbehaviorReport(_, _, _) => {},
408 ProvisionableData::Dispute(_, _) => {},
423 }
424}
425
426type CoreAvailability = BitVec<u8, bitvec::order::Lsb0>;
427
428async fn send_inherent_data(
446 leaf: &ActivatedLeaf,
447 bitfields: &[SignedAvailabilityBitfield],
448 return_senders: Vec<oneshot::Sender<ProvisionerInherentData>>,
449 from_job: &mut impl overseer::ProvisionerSenderTrait,
450 metrics: &Metrics,
451) -> Result<(), Error> {
452 gum::trace!(
453 target: LOG_TARGET,
454 relay_parent = ?leaf.hash,
455 "Requesting availability cores"
456 );
457 let availability_cores = request_availability_cores(leaf.hash, from_job)
458 .await
459 .await
460 .map_err(|err| Error::CanceledAvailabilityCores(err))??;
461
462 gum::trace!(
463 target: LOG_TARGET,
464 relay_parent = ?leaf.hash,
465 "Selecting disputes"
466 );
467
468 let disputes = disputes::prioritized_selection::select_disputes(from_job, metrics, leaf).await;
469
470 gum::trace!(
471 target: LOG_TARGET,
472 relay_parent = ?leaf.hash,
473 "Selected disputes"
474 );
475
476 let bitfields = select_availability_bitfields(&availability_cores, bitfields, &leaf.hash);
477
478 gum::trace!(
479 target: LOG_TARGET,
480 relay_parent = ?leaf.hash,
481 "Selected bitfields"
482 );
483
484 let candidates = select_candidates(&availability_cores, &bitfields, leaf, from_job).await?;
485
486 gum::trace!(
487 target: LOG_TARGET,
488 relay_parent = ?leaf.hash,
489 "Selected candidates"
490 );
491
492 gum::debug!(
493 target: LOG_TARGET,
494 availability_cores_len = availability_cores.len(),
495 disputes_count = disputes.len(),
496 bitfields_count = bitfields.len(),
497 candidates_count = candidates.len(),
498 leaf_hash = ?leaf.hash,
499 "inherent data prepared",
500 );
501
502 let inherent_data =
503 ProvisionerInherentData { bitfields, backed_candidates: candidates, disputes };
504
505 gum::trace!(
506 target: LOG_TARGET,
507 relay_parent = ?leaf.hash,
508 "Sending back inherent data to requesters."
509 );
510
511 for return_sender in return_senders {
512 return_sender
513 .send(inherent_data.clone())
514 .map_err(|_data| Error::InherentDataReturnChannel)?;
515 }
516
517 Ok(())
518}
519
520fn select_availability_bitfields(
531 cores: &[CoreState],
532 bitfields: &[SignedAvailabilityBitfield],
533 leaf_hash: &Hash,
534) -> Vec<SignedAvailabilityBitfield> {
535 let mut selected: BTreeMap<ValidatorIndex, SignedAvailabilityBitfield> = BTreeMap::new();
536
537 gum::debug!(
538 target: LOG_TARGET,
539 bitfields_count = bitfields.len(),
540 ?leaf_hash,
541 "bitfields count before selection"
542 );
543
544 'a: for bitfield in bitfields.iter().cloned() {
545 if bitfield.payload().0.len() != cores.len() {
546 gum::debug!(target: LOG_TARGET, ?leaf_hash, "dropping bitfield due to length mismatch");
547 continue;
548 }
549
550 let is_better = selected
551 .get(&bitfield.validator_index())
552 .map_or(true, |b| b.payload().0.count_ones() < bitfield.payload().0.count_ones());
553
554 if !is_better {
555 gum::trace!(
556 target: LOG_TARGET,
557 val_idx = bitfield.validator_index().0,
558 ?leaf_hash,
559 "dropping bitfield due to duplication - the better one is kept"
560 );
561 continue;
562 }
563
564 for (idx, _) in cores.iter().enumerate().filter(|v| !v.1.is_occupied()) {
565 if *bitfield.payload().0.get(idx).as_deref().unwrap_or(&false) {
567 gum::debug!(
568 target: LOG_TARGET,
569 val_idx = bitfield.validator_index().0,
570 ?leaf_hash,
571 "dropping invalid bitfield - bit is set for an unoccupied core"
572 );
573 continue 'a;
574 }
575 }
576
577 let _ = selected.insert(bitfield.validator_index(), bitfield);
578 }
579
580 gum::debug!(
581 target: LOG_TARGET,
582 ?leaf_hash,
583 "selected {} of all {} bitfields (each bitfield is from a unique validator)",
584 selected.len(),
585 bitfields.len()
586 );
587
588 selected.into_values().collect()
589}
590
591async fn request_backable_candidates(
594 availability_cores: &[CoreState],
595 bitfields: &[SignedAvailabilityBitfield],
596 leaf: &ActivatedLeaf,
597 sender: &mut impl overseer::ProvisionerSenderTrait,
598) -> Result<HashMap<ParaId, Vec<BackableCandidateRef>>, Error> {
599 let block_number_under_construction = leaf.number + 1;
600
601 let mut scheduled_cores_per_para: BTreeMap<ParaId, usize> = BTreeMap::new();
604 let mut ancestors: HashMap<ParaId, Ancestors> =
606 HashMap::with_capacity(availability_cores.len());
607
608 for (core_idx, core) in availability_cores.iter().enumerate() {
609 let core_idx = CoreIndex(core_idx as u32);
610 match core {
611 CoreState::Scheduled(scheduled_core) => {
612 *scheduled_cores_per_para.entry(scheduled_core.para_id).or_insert(0) += 1;
613 },
614 CoreState::Occupied(occupied_core) => {
615 let is_available = bitfields_indicate_availability(
616 core_idx.0 as usize,
617 bitfields,
618 &occupied_core.availability,
619 );
620
621 if is_available {
622 ancestors
623 .entry(occupied_core.para_id())
624 .or_default()
625 .insert(occupied_core.candidate_hash);
626
627 if let Some(ref scheduled_core) = occupied_core.next_up_on_available {
628 *scheduled_cores_per_para.entry(scheduled_core.para_id).or_insert(0) += 1;
630 }
631 } else if occupied_core.time_out_at <= block_number_under_construction {
632 if let Some(ref scheduled_core) = occupied_core.next_up_on_time_out {
635 *scheduled_cores_per_para.entry(scheduled_core.para_id).or_insert(0) += 1;
637 }
638 } else {
639 ancestors
641 .entry(occupied_core.para_id())
642 .or_default()
643 .insert(occupied_core.candidate_hash);
644 }
645 },
646 CoreState::Free => continue,
647 };
648 }
649
650 let mut selected_candidates: HashMap<ParaId, Vec<BackableCandidateRef>> =
651 HashMap::with_capacity(scheduled_cores_per_para.len());
652
653 for (para_id, core_count) in scheduled_cores_per_para {
654 let para_ancestors = ancestors.remove(¶_id).unwrap_or_default();
655
656 let response =
657 get_backable_candidates(leaf.hash, para_id, para_ancestors, core_count as u32, sender)
658 .await?;
659
660 if response.is_empty() {
661 gum::debug!(
662 target: LOG_TARGET,
663 leaf_hash = ?leaf.hash,
664 ?para_id,
665 "No backable candidate returned by prospective parachains",
666 );
667 continue;
668 }
669
670 selected_candidates.insert(para_id, response);
671 }
672
673 Ok(selected_candidates)
674}
675
676async fn select_candidates(
679 availability_cores: &[CoreState],
680 bitfields: &[SignedAvailabilityBitfield],
681 leaf: &ActivatedLeaf,
682 sender: &mut impl overseer::ProvisionerSenderTrait,
683) -> Result<Vec<BackedCandidate>, Error> {
684 gum::trace!(
685 target: LOG_TARGET,
686 leaf_hash=?leaf.hash,
687 "before GetBackedCandidates"
688 );
689
690 let selected_candidates =
691 request_backable_candidates(availability_cores, bitfields, leaf, sender).await?;
692 gum::debug!(target: LOG_TARGET, ?selected_candidates, "Got backable candidates");
693
694 let (tx, rx) = oneshot::channel();
696 sender.send_unbounded_message(CandidateBackingMessage::GetBackableCandidates {
697 candidates: selected_candidates.clone(),
698 sender: tx,
699 });
700 let candidates = rx.await.map_err(|err| Error::CanceledBackedCandidates(err))?;
701 gum::trace!(
702 target: LOG_TARGET,
703 leaf_hash=?leaf.hash,
704 "Got {} backed candidates", candidates.len()
705 );
706
707 let mut with_validation_code = false;
709 let mut merged_candidates = Vec::with_capacity(availability_cores.len());
711
712 for para_candidates in candidates.into_values() {
713 for candidate in para_candidates {
714 if candidate.candidate().commitments.new_validation_code.is_some() {
715 if with_validation_code {
716 break;
717 } else {
718 with_validation_code = true;
719 }
720 }
721
722 merged_candidates.push(candidate);
723 }
724 }
725
726 gum::debug!(
727 target: LOG_TARGET,
728 n_candidates = merged_candidates.len(),
729 n_cores = availability_cores.len(),
730 leaf=?leaf.hash,
731 "Selected backed candidates",
732 );
733
734 Ok(merged_candidates)
735}
736
737async fn get_backable_candidates(
740 leaf: Hash,
741 para_id: ParaId,
742 ancestors: Ancestors,
743 count: u32,
744 sender: &mut impl overseer::ProvisionerSenderTrait,
745) -> Result<Vec<BackableCandidateRef>, Error> {
746 let (tx, rx) = oneshot::channel();
747 sender
748 .send_message(ProspectiveParachainsMessage::GetBackableCandidates {
749 leaf,
750 para_id,
751 count,
752 ancestors,
753 sender: tx,
754 })
755 .await;
756
757 rx.await.map_err(Error::CanceledBackableCandidates)
758}
759
760fn bitfields_indicate_availability(
767 core_idx: usize,
768 bitfields: &[SignedAvailabilityBitfield],
769 availability: &CoreAvailability,
770) -> bool {
771 let mut availability = availability.clone();
772 let availability_len = availability.len();
773
774 for bitfield in bitfields {
775 let validator_idx = bitfield.validator_index().0 as usize;
776 match availability.get_mut(validator_idx) {
777 None => {
778 gum::warn!(
783 target: LOG_TARGET,
784 validator_idx = %validator_idx,
785 availability_len = %availability_len,
786 "attempted to set a transverse bit at idx {} which is greater than bitfield size {}",
787 validator_idx,
788 availability_len,
789 );
790
791 return false;
792 },
793 Some(mut bit_mut) => *bit_mut |= bitfield.payload().0[core_idx],
794 }
795 }
796
797 3 * availability.count_ones() >= 2 * availability.len()
798}