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 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
60const PRE_PROPOSE_TIMEOUT: std::time::Duration = core::time::Duration::from_millis(2000);
62const SEND_INHERENT_DATA_TIMEOUT: std::time::Duration = core::time::Duration::from_millis(500);
64
65const LOG_TARGET: &str = "teyrchain::provisioner";
66
67pub struct ProvisionerSubsystem {
69 metrics: Metrics,
70}
71
72impl ProvisionerSubsystem {
73 pub fn new(metrics: Metrics) -> Self {
75 Self { metrics }
76 }
77}
78
79pub 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 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 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 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) .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 ProvisionableData::MisbehaviorReport(_, _, _) => {},
407 ProvisionableData::Dispute(_, _) => {},
422 }
423}
424
425type CoreAvailability = BitVec<u8, bitvec::order::Lsb0>;
426
427async 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
519fn 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 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
590async 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 let mut scheduled_cores_per_para: BTreeMap<ParaId, usize> = BTreeMap::new();
603 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 *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 if let Some(ref scheduled_core) = occupied_core.next_up_on_time_out {
634 *scheduled_cores_per_para.entry(scheduled_core.para_id).or_insert(0) += 1;
636 }
637 } else {
638 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(¶_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
680async 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 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 let mut with_validation_code = false;
714 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
742async 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
765fn 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 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}