1use futures_timer::Delay;
25use polkadot_node_primitives::{
26 approval::{
27 v1::{BlockApprovalMeta, DelayTranche},
28 v2::{
29 AssignmentCertKindV2, BitfieldError, CandidateBitfield, CoreBitfield,
30 IndirectAssignmentCertV2, IndirectSignedApprovalVoteV2,
31 },
32 },
33 ValidationResult, DISPUTE_WINDOW,
34};
35use polkadot_node_subsystem::{
36 errors::RecoveryError,
37 messages::{
38 ApprovalCheckError, ApprovalCheckResult, ApprovalDistributionMessage,
39 ApprovalVotingMessage, AssignmentCheckError, AssignmentCheckResult,
40 AvailabilityRecoveryMessage, BlockDescription, CandidateValidationMessage, ChainApiMessage,
41 ChainSelectionMessage, CheckedIndirectAssignment, CheckedIndirectSignedApprovalVote,
42 DisputeCoordinatorMessage, HighestApprovedAncestorBlock, PvfExecKind, RuntimeApiMessage,
43 RuntimeApiRequest,
44 },
45 overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError, SubsystemResult,
46 SubsystemSender,
47};
48use polkadot_node_subsystem_util::{
49 self,
50 database::Database,
51 metrics::{self, prometheus},
52 runtime::{Config as RuntimeInfoConfig, ExtendedSessionInfo, RuntimeInfo},
53 TimeoutExt,
54};
55use polkadot_primitives::{
56 ApprovalVoteMultipleCandidates, BlockNumber, CandidateHash, CandidateIndex,
57 CandidateReceiptV2 as CandidateReceipt, CoalescedApprovalCandidateHashes, CoreIndex,
58 GroupIndex, Hash, SessionIndex, SessionInfo, ValidatorId, ValidatorIndex, ValidatorPair,
59 ValidatorSignature,
60};
61use sc_keystore::LocalKeystore;
62use sp_application_crypto::Pair;
63use sp_consensus::SyncOracle;
64use sp_consensus_slots::Slot;
65use std::time::Instant;
66
67const MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS: u32 = 100;
71
72use futures::{
73 channel::oneshot,
74 future::{BoxFuture, RemoteHandle},
75 prelude::*,
76 stream::FuturesUnordered,
77 StreamExt,
78};
79
80use std::{
81 cmp::min,
82 collections::{
83 btree_map::Entry as BTMEntry, hash_map::Entry as HMEntry, BTreeMap, HashMap, HashSet,
84 },
85 sync::Arc,
86 time::Duration,
87};
88
89use schnellru::{ByLength, LruMap};
90
91use approval_checking::RequiredTranches;
92use bitvec::{order::Lsb0, vec::BitVec};
93pub use criteria::{AssignmentCriteria, Config as AssignmentConfig, RealAssignmentCriteria};
94use persisted_entries::{ApprovalEntry, BlockEntry, CandidateEntry};
95use polkadot_node_primitives::approval::time::{
96 slot_number_to_tick, Clock, ClockExt, DelayedApprovalTimer, SystemClock, Tick,
97};
98
99mod approval_checking;
100pub mod approval_db;
101mod backend;
102pub mod criteria;
103mod import;
104mod ops;
105mod persisted_entries;
106
107use crate::{
108 approval_checking::{Check, TranchesToApproveResult},
109 approval_db::common::{Config as DatabaseConfig, DbBackend},
110 backend::{Backend, OverlayedBackend},
111 criteria::InvalidAssignmentReason,
112 persisted_entries::OurApproval,
113};
114
115#[cfg(test)]
116mod tests;
117
118const APPROVAL_CHECKING_TIMEOUT: Duration = Duration::from_secs(120);
119const WAIT_FOR_SIGS_TIMEOUT: Duration = Duration::from_millis(500);
124const APPROVAL_CACHE_SIZE: u32 = 1024;
125
126const MAX_APPROVAL_RETRIES: u32 = 16;
128
129const APPROVAL_DELAY: Tick = 2;
130pub(crate) const LOG_TARGET: &str = "parachain::approval-voting";
131
132const MAX_APPROVAL_COALESCE_WAIT_TICKS: Tick = 12;
134
135const RESTART_WAKEUP_DELAY: Tick = 12;
144
145#[derive(Debug, Clone)]
147pub struct Config {
148 pub col_approval_data: u32,
150 pub slot_duration_millis: u64,
153}
154
155enum Mode {
165 Active,
166 Syncing(Box<dyn SyncOracle + Send>),
167}
168
169pub struct ApprovalVotingSubsystem {
171 keystore: Arc<LocalKeystore>,
175 db_config: DatabaseConfig,
176 slot_duration_millis: u64,
177 db: Arc<dyn Database>,
178 mode: Mode,
179 metrics: Metrics,
180 clock: Arc<dyn Clock + Send + Sync>,
181 spawner: Arc<dyn overseer::gen::Spawner + 'static>,
182 max_approval_retries: u32,
184 retry_backoff: Duration,
186}
187
188#[derive(Clone)]
189struct MetricsInner {
190 imported_candidates_total: prometheus::Counter<prometheus::U64>,
191 assignments_produced: prometheus::Histogram,
192 approvals_produced_total: prometheus::CounterVec<prometheus::U64>,
193 no_shows_total: prometheus::Counter<prometheus::U64>,
194 observed_no_shows: prometheus::Counter<prometheus::U64>,
198 approved_by_one_third: prometheus::Counter<prometheus::U64>,
199 wakeups_triggered_total: prometheus::Counter<prometheus::U64>,
200 coalesced_approvals_buckets: prometheus::Histogram,
201 coalesced_approvals_delay: prometheus::Histogram,
202 candidate_approval_time_ticks: prometheus::Histogram,
203 block_approval_time_ticks: prometheus::Histogram,
204 time_db_transaction: prometheus::Histogram,
205 time_recover_and_approve: prometheus::Histogram,
206 candidate_signatures_requests_total: prometheus::Counter<prometheus::U64>,
207 unapproved_candidates_in_unfinalized_chain: prometheus::Gauge<prometheus::U64>,
208 assignments_gathering_time_by_stage: prometheus::HistogramVec,
216}
217
218#[derive(Default, Clone)]
220pub struct Metrics(Option<MetricsInner>);
221
222impl Metrics {
223 fn on_candidate_imported(&self) {
224 if let Some(metrics) = &self.0 {
225 metrics.imported_candidates_total.inc();
226 }
227 }
228
229 fn on_assignment_produced(&self, tranche: DelayTranche) {
230 if let Some(metrics) = &self.0 {
231 metrics.assignments_produced.observe(tranche as f64);
232 }
233 }
234
235 fn on_approval_coalesce(&self, num_coalesced: u32) {
236 if let Some(metrics) = &self.0 {
237 for _ in 0..num_coalesced {
240 metrics.coalesced_approvals_buckets.observe(num_coalesced as f64)
241 }
242 }
243 }
244
245 fn on_delayed_approval(&self, delayed_ticks: u64) {
246 if let Some(metrics) = &self.0 {
247 metrics.coalesced_approvals_delay.observe(delayed_ticks as f64)
248 }
249 }
250
251 fn on_approval_stale(&self) {
252 if let Some(metrics) = &self.0 {
253 metrics.approvals_produced_total.with_label_values(&["stale"]).inc()
254 }
255 }
256
257 fn on_approval_invalid(&self) {
258 if let Some(metrics) = &self.0 {
259 metrics.approvals_produced_total.with_label_values(&["invalid"]).inc()
260 }
261 }
262
263 fn on_approval_unavailable(&self) {
264 if let Some(metrics) = &self.0 {
265 metrics.approvals_produced_total.with_label_values(&["unavailable"]).inc()
266 }
267 }
268
269 fn on_approval_error(&self) {
270 if let Some(metrics) = &self.0 {
271 metrics.approvals_produced_total.with_label_values(&["internal error"]).inc()
272 }
273 }
274
275 fn on_approval_produced(&self) {
276 if let Some(metrics) = &self.0 {
277 metrics.approvals_produced_total.with_label_values(&["success"]).inc()
278 }
279 }
280
281 fn on_no_shows(&self, n: usize) {
282 if let Some(metrics) = &self.0 {
283 metrics.no_shows_total.inc_by(n as u64);
284 }
285 }
286
287 fn on_observed_no_shows(&self, n: usize) {
288 if let Some(metrics) = &self.0 {
289 metrics.observed_no_shows.inc_by(n as u64);
290 }
291 }
292
293 fn on_approved_by_one_third(&self) {
294 if let Some(metrics) = &self.0 {
295 metrics.approved_by_one_third.inc();
296 }
297 }
298
299 fn on_wakeup(&self) {
300 if let Some(metrics) = &self.0 {
301 metrics.wakeups_triggered_total.inc();
302 }
303 }
304
305 fn on_candidate_approved(&self, ticks: Tick) {
306 if let Some(metrics) = &self.0 {
307 metrics.candidate_approval_time_ticks.observe(ticks as f64);
308 }
309 }
310
311 fn on_block_approved(&self, ticks: Tick) {
312 if let Some(metrics) = &self.0 {
313 metrics.block_approval_time_ticks.observe(ticks as f64);
314 }
315 }
316
317 fn on_candidate_signatures_request(&self) {
318 if let Some(metrics) = &self.0 {
319 metrics.candidate_signatures_requests_total.inc();
320 }
321 }
322
323 fn time_db_transaction(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
324 self.0.as_ref().map(|metrics| metrics.time_db_transaction.start_timer())
325 }
326
327 fn time_recover_and_approve(&self) -> Option<metrics::prometheus::prometheus::HistogramTimer> {
328 self.0.as_ref().map(|metrics| metrics.time_recover_and_approve.start_timer())
329 }
330
331 fn on_unapproved_candidates_in_unfinalized_chain(&self, count: usize) {
332 if let Some(metrics) = &self.0 {
333 metrics.unapproved_candidates_in_unfinalized_chain.set(count as u64);
334 }
335 }
336
337 pub fn observe_assignment_gathering_time(&self, stage: usize, elapsed_as_millis: usize) {
338 if let Some(metrics) = &self.0 {
339 let stage_string = stage.to_string();
340 metrics
345 .assignments_gathering_time_by_stage
346 .with_label_values(&[if stage < 10 { stage_string.as_str() } else { "inf" }])
347 .observe(elapsed_as_millis as f64);
348 }
349 }
350}
351
352impl metrics::Metrics for Metrics {
353 fn try_register(
354 registry: &prometheus::Registry,
355 ) -> std::result::Result<Self, prometheus::PrometheusError> {
356 let metrics = MetricsInner {
357 imported_candidates_total: prometheus::register(
358 prometheus::Counter::new(
359 "polkadot_parachain_imported_candidates_total",
360 "Number of candidates imported by the approval voting subsystem",
361 )?,
362 registry,
363 )?,
364 assignments_produced: prometheus::register(
365 prometheus::Histogram::with_opts(
366 prometheus::HistogramOpts::new(
367 "polkadot_parachain_assignments_produced",
368 "Assignments and tranches produced by the approval voting subsystem",
369 ).buckets(vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 10.0, 15.0, 25.0, 40.0, 70.0]),
370 )?,
371 registry,
372 )?,
373 approvals_produced_total: prometheus::register(
374 prometheus::CounterVec::new(
375 prometheus::Opts::new(
376 "polkadot_parachain_approvals_produced_total",
377 "Number of approvals produced by the approval voting subsystem",
378 ),
379 &["status"]
380 )?,
381 registry,
382 )?,
383 no_shows_total: prometheus::register(
384 prometheus::Counter::new(
385 "polkadot_parachain_approvals_no_shows_total",
386 "Number of assignments which became no-shows in the approval voting subsystem",
387 )?,
388 registry,
389 )?,
390 observed_no_shows: prometheus::register(
391 prometheus::Counter::new(
392 "polkadot_parachain_approvals_observed_no_shows_total",
393 "Number of observed no shows at any moment in time",
394 )?,
395 registry,
396 )?,
397 wakeups_triggered_total: prometheus::register(
398 prometheus::Counter::new(
399 "polkadot_parachain_approvals_wakeups_total",
400 "Number of times we woke up to process a candidate in the approval voting subsystem",
401 )?,
402 registry,
403 )?,
404 candidate_approval_time_ticks: prometheus::register(
405 prometheus::Histogram::with_opts(
406 prometheus::HistogramOpts::new(
407 "polkadot_parachain_approvals_candidate_approval_time_ticks",
408 "Number of ticks (500ms) to approve candidates.",
409 ).buckets(vec![6.0, 12.0, 18.0, 24.0, 30.0, 36.0, 72.0, 100.0, 144.0]),
410 )?,
411 registry,
412 )?,
413 coalesced_approvals_buckets: prometheus::register(
414 prometheus::Histogram::with_opts(
415 prometheus::HistogramOpts::new(
416 "polkadot_parachain_approvals_coalesced_approvals_buckets",
417 "Number of coalesced approvals.",
418 ).buckets(vec![1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 7.5, 8.5, 9.5]),
419 )?,
420 registry,
421 )?,
422 coalesced_approvals_delay: prometheus::register(
423 prometheus::Histogram::with_opts(
424 prometheus::HistogramOpts::new(
425 "polkadot_parachain_approvals_coalescing_delay",
426 "Number of ticks we delay the sending of a candidate approval",
427 ).buckets(vec![1.1, 2.1, 3.1, 4.1, 6.1, 8.1, 12.1, 20.1, 32.1]),
428 )?,
429 registry,
430 )?,
431 approved_by_one_third: prometheus::register(
432 prometheus::Counter::new(
433 "polkadot_parachain_approved_by_one_third",
434 "Number of candidates where more than one third had to vote ",
435 )?,
436 registry,
437 )?,
438 block_approval_time_ticks: prometheus::register(
439 prometheus::Histogram::with_opts(
440 prometheus::HistogramOpts::new(
441 "polkadot_parachain_approvals_blockapproval_time_ticks",
442 "Number of ticks (500ms) to approve blocks.",
443 ).buckets(vec![6.0, 12.0, 18.0, 24.0, 30.0, 36.0, 72.0, 100.0, 144.0]),
444 )?,
445 registry,
446 )?,
447 time_db_transaction: prometheus::register(
448 prometheus::Histogram::with_opts(
449 prometheus::HistogramOpts::new(
450 "polkadot_parachain_time_approval_db_transaction",
451 "Time spent writing an approval db transaction.",
452 )
453 )?,
454 registry,
455 )?,
456 time_recover_and_approve: prometheus::register(
457 prometheus::Histogram::with_opts(
458 prometheus::HistogramOpts::new(
459 "polkadot_parachain_time_recover_and_approve",
460 "Time spent recovering and approving data in approval voting",
461 )
462 )?,
463 registry,
464 )?,
465 candidate_signatures_requests_total: prometheus::register(
466 prometheus::Counter::new(
467 "polkadot_parachain_approval_candidate_signatures_requests_total",
468 "Number of times signatures got requested by other subsystems",
469 )?,
470 registry,
471 )?,
472 unapproved_candidates_in_unfinalized_chain: prometheus::register(
473 prometheus::Gauge::new(
474 "polkadot_parachain_approval_unapproved_candidates_in_unfinalized_chain",
475 "Number of unapproved candidates in unfinalized chain",
476 )?,
477 registry,
478 )?,
479 assignments_gathering_time_by_stage: prometheus::register(
480 prometheus::HistogramVec::new(
481 prometheus::HistogramOpts::new(
482 "polkadot_parachain_assignments_gather_time_by_stage_ms",
483 "The time in ms it takes for each stage to gather enough assignments needed for approval",
484 )
485 .buckets(vec![0.0, 250.0, 500.0, 1000.0, 2000.0, 4000.0, 8000.0, 16000.0, 32000.0]),
486 &["stage"],
487 )?,
488 registry,
489 )?,
490 };
491
492 Ok(Metrics(Some(metrics)))
493 }
494}
495
496impl ApprovalVotingSubsystem {
497 pub fn with_config(
499 config: Config,
500 db: Arc<dyn Database>,
501 keystore: Arc<LocalKeystore>,
502 sync_oracle: Box<dyn SyncOracle + Send>,
503 metrics: Metrics,
504 spawner: Arc<dyn overseer::gen::Spawner + 'static>,
505 ) -> Self {
506 ApprovalVotingSubsystem::with_config_and_clock(
507 config,
508 db,
509 keystore,
510 sync_oracle,
511 metrics,
512 Arc::new(SystemClock {}),
513 spawner,
514 MAX_APPROVAL_RETRIES,
515 APPROVAL_CHECKING_TIMEOUT / 2,
516 )
517 }
518
519 pub fn with_config_and_clock(
521 config: Config,
522 db: Arc<dyn Database>,
523 keystore: Arc<LocalKeystore>,
524 sync_oracle: Box<dyn SyncOracle + Send>,
525 metrics: Metrics,
526 clock: Arc<dyn Clock + Send + Sync>,
527 spawner: Arc<dyn overseer::gen::Spawner + 'static>,
528 max_approval_retries: u32,
529 retry_backoff: Duration,
530 ) -> Self {
531 ApprovalVotingSubsystem {
532 keystore,
533 slot_duration_millis: config.slot_duration_millis,
534 db,
535 db_config: DatabaseConfig { col_approval_data: config.col_approval_data },
536 mode: Mode::Syncing(sync_oracle),
537 metrics,
538 clock,
539 spawner,
540 max_approval_retries,
541 retry_backoff,
542 }
543 }
544
545 pub fn revert_to(&self, hash: Hash) -> Result<(), SubsystemError> {
548 let config =
549 approval_db::common::Config { col_approval_data: self.db_config.col_approval_data };
550 let mut backend = approval_db::common::DbBackend::new(self.db.clone(), config);
551 let mut overlay = OverlayedBackend::new(&backend);
552
553 ops::revert_to(&mut overlay, hash)?;
554
555 let ops = overlay.into_write_ops();
556 backend.write(ops)
557 }
558}
559
560fn db_sanity_check(db: Arc<dyn Database>, config: DatabaseConfig) -> SubsystemResult<()> {
563 let backend = DbBackend::new(db, config);
564 let all_blocks = backend.load_all_blocks()?;
565
566 if all_blocks.is_empty() {
567 gum::info!(target: LOG_TARGET, "Starting with an empty approval vote DB.",);
568 } else {
569 gum::debug!(
570 target: LOG_TARGET,
571 "Starting with {} blocks in approval vote DB.",
572 all_blocks.len()
573 );
574 }
575
576 Ok(())
577}
578
579#[overseer::subsystem(ApprovalVoting, error = SubsystemError, prefix = self::overseer)]
580impl<Context: Send> ApprovalVotingSubsystem {
581 fn start(self, mut ctx: Context) -> SpawnedSubsystem {
582 let backend = DbBackend::new(self.db.clone(), self.db_config);
583 let to_other_subsystems = ctx.sender().clone();
584 let to_approval_distr = ctx.sender().clone();
585
586 let future = run::<DbBackend, _, _, _>(
587 ctx,
588 to_other_subsystems,
589 to_approval_distr,
590 self,
591 Box::new(RealAssignmentCriteria),
592 backend,
593 )
594 .map_err(|e| SubsystemError::with_origin("approval-voting", e))
595 .boxed();
596
597 SpawnedSubsystem { name: "approval-voting-subsystem", future }
598 }
599}
600
601#[derive(Debug, Clone)]
602struct ApprovalVoteRequest {
603 validator_index: ValidatorIndex,
604 block_hash: Hash,
605}
606
607#[derive(Default)]
608struct Wakeups {
609 wakeups: BTreeMap<Tick, Vec<(Hash, CandidateHash)>>,
611 reverse_wakeups: HashMap<(Hash, CandidateHash), Tick>,
612 block_numbers: BTreeMap<BlockNumber, HashSet<Hash>>,
613}
614
615impl Wakeups {
616 fn first(&self) -> Option<Tick> {
618 self.wakeups.keys().next().map(|t| *t)
619 }
620
621 fn note_block(&mut self, block_hash: Hash, block_number: BlockNumber) {
622 self.block_numbers.entry(block_number).or_default().insert(block_hash);
623 }
624
625 fn schedule(
628 &mut self,
629 block_hash: Hash,
630 block_number: BlockNumber,
631 candidate_hash: CandidateHash,
632 tick: Tick,
633 ) {
634 if let Some(prev) = self.reverse_wakeups.get(&(block_hash, candidate_hash)) {
635 if prev <= &tick {
636 return;
637 }
638
639 if let BTMEntry::Occupied(mut entry) = self.wakeups.entry(*prev) {
641 if let Some(pos) =
642 entry.get().iter().position(|x| x == &(block_hash, candidate_hash))
643 {
644 entry.get_mut().remove(pos);
645 }
646
647 if entry.get().is_empty() {
648 let _ = entry.remove_entry();
649 }
650 }
651 } else {
652 self.note_block(block_hash, block_number);
653 }
654
655 self.reverse_wakeups.insert((block_hash, candidate_hash), tick);
656 self.wakeups.entry(tick).or_default().push((block_hash, candidate_hash));
657 }
658
659 fn prune_finalized_wakeups(&mut self, finalized_number: BlockNumber) {
660 let after = self.block_numbers.split_off(&(finalized_number + 1));
661 let pruned_blocks: HashSet<_> = std::mem::replace(&mut self.block_numbers, after)
662 .into_iter()
663 .flat_map(|(_number, hashes)| hashes)
664 .collect();
665
666 let mut pruned_wakeups = BTreeMap::new();
667 self.reverse_wakeups.retain(|(h, c_h), tick| {
668 let live = !pruned_blocks.contains(h);
669 if !live {
670 pruned_wakeups.entry(*tick).or_insert_with(HashSet::new).insert((*h, *c_h));
671 }
672 live
673 });
674
675 for (tick, pruned) in pruned_wakeups {
676 if let BTMEntry::Occupied(mut entry) = self.wakeups.entry(tick) {
677 entry.get_mut().retain(|wakeup| !pruned.contains(wakeup));
678 if entry.get().is_empty() {
679 let _ = entry.remove();
680 }
681 }
682 }
683 }
684
685 fn wakeup_for(&self, block_hash: Hash, candidate_hash: CandidateHash) -> Option<Tick> {
687 self.reverse_wakeups.get(&(block_hash, candidate_hash)).map(|t| *t)
688 }
689
690 async fn next(&mut self, clock: &(dyn Clock + Sync)) -> (Tick, Hash, CandidateHash) {
692 match self.first() {
693 None => future::pending().await,
694 Some(tick) => {
695 clock.wait(tick).await;
696 match self.wakeups.entry(tick) {
697 BTMEntry::Vacant(_) => {
698 panic!("entry is known to exist since `first` was `Some`; qed")
699 },
700 BTMEntry::Occupied(mut entry) => {
701 let (hash, candidate_hash) = entry.get_mut().pop()
702 .expect("empty entries are removed here and in `schedule`; no other mutation of this map; qed");
703
704 if entry.get().is_empty() {
705 let _ = entry.remove();
706 }
707
708 self.reverse_wakeups.remove(&(hash, candidate_hash));
709
710 (tick, hash, candidate_hash)
711 },
712 }
713 },
714 }
715 }
716}
717
718struct ApprovalStatus {
719 required_tranches: RequiredTranches,
720 tranche_now: DelayTranche,
721 block_tick: Tick,
722 last_no_shows: usize,
723 no_show_validators: Vec<ValidatorIndex>,
724}
725
726#[derive(Copy, Clone)]
727enum ApprovalOutcome {
728 Approved,
729 Failed,
730 TimedOut,
731}
732
733#[derive(Clone)]
734struct RetryApprovalInfo {
735 candidate: CandidateReceipt,
736 backing_group: GroupIndex,
737 core_index: Option<CoreIndex>,
738 session_index: SessionIndex,
739 attempts_remaining: u32,
740 backoff: Duration,
741}
742
743struct ApprovalState {
744 validator_index: ValidatorIndex,
745 candidate_hash: CandidateHash,
746 approval_outcome: ApprovalOutcome,
747 retry_info: Option<RetryApprovalInfo>,
748}
749
750impl ApprovalState {
751 fn approved(validator_index: ValidatorIndex, candidate_hash: CandidateHash) -> Self {
752 Self {
753 validator_index,
754 candidate_hash,
755 approval_outcome: ApprovalOutcome::Approved,
756 retry_info: None,
757 }
758 }
759 fn failed(validator_index: ValidatorIndex, candidate_hash: CandidateHash) -> Self {
760 Self {
761 validator_index,
762 candidate_hash,
763 approval_outcome: ApprovalOutcome::Failed,
764 retry_info: None,
765 }
766 }
767
768 fn failed_with_retry(
769 validator_index: ValidatorIndex,
770 candidate_hash: CandidateHash,
771 retry_info: Option<RetryApprovalInfo>,
772 ) -> Self {
773 Self {
774 validator_index,
775 candidate_hash,
776 approval_outcome: ApprovalOutcome::Failed,
777 retry_info,
778 }
779 }
780}
781
782struct CurrentlyCheckingSet {
783 candidate_hash_map: HashMap<CandidateHash, HashSet<Hash>>,
784 currently_checking: FuturesUnordered<BoxFuture<'static, ApprovalState>>,
785}
786
787impl Default for CurrentlyCheckingSet {
788 fn default() -> Self {
789 Self { candidate_hash_map: HashMap::new(), currently_checking: FuturesUnordered::new() }
790 }
791}
792
793impl CurrentlyCheckingSet {
794 pub async fn insert_relay_block_hash(
797 &mut self,
798 candidate_hash: CandidateHash,
799 validator_index: ValidatorIndex,
800 relay_block: Hash,
801 launch_work: impl Future<Output = SubsystemResult<RemoteHandle<ApprovalState>>>,
802 ) -> SubsystemResult<()> {
803 match self.candidate_hash_map.entry(candidate_hash) {
804 HMEntry::Occupied(mut entry) => {
805 entry.get_mut().insert(relay_block);
807 },
808 HMEntry::Vacant(entry) => {
809 entry.insert(HashSet::new()).insert(relay_block);
811 let work = launch_work.await?;
812 self.currently_checking.push(Box::pin(async move {
813 match work.timeout(APPROVAL_CHECKING_TIMEOUT).await {
814 None => ApprovalState {
815 candidate_hash,
816 validator_index,
817 approval_outcome: ApprovalOutcome::TimedOut,
818 retry_info: None,
819 },
820 Some(approval_state) => approval_state,
821 }
822 }));
823 },
824 }
825
826 Ok(())
827 }
828
829 pub async fn next(
830 &mut self,
831 approvals_cache: &mut LruMap<CandidateHash, ApprovalOutcome>,
832 ) -> (HashSet<Hash>, ApprovalState) {
833 if !self.currently_checking.is_empty() {
834 if let Some(approval_state) = self.currently_checking.next().await {
835 let out = self
836 .candidate_hash_map
837 .remove(&approval_state.candidate_hash)
838 .unwrap_or_default();
839 approvals_cache
840 .insert(approval_state.candidate_hash, approval_state.approval_outcome);
841 return (out, approval_state);
842 }
843 }
844
845 future::pending().await
846 }
847}
848
849async fn get_extended_session_info_by_index<'a, Sender>(
850 runtime_info: &'a mut RuntimeInfo,
851 sender: &mut Sender,
852 block_hash: Hash,
853 session_index: SessionIndex,
854) -> Option<&'a ExtendedSessionInfo>
855where
856 Sender: SubsystemSender<RuntimeApiMessage>,
857{
858 match runtime_info.get_session_info_by_index(sender, block_hash, session_index).await {
859 Ok(extended_info) => Some(&extended_info),
860 Err(_) => {
861 gum::debug!(
862 target: LOG_TARGET,
863 session = session_index,
864 ?block_hash,
865 "Can't obtain SessionInfo or ExecutorParams"
866 );
867 None
868 },
869 }
870}
871
872async fn get_session_info_by_index<'a, Sender>(
873 runtime_info: &'a mut RuntimeInfo,
874 sender: &mut Sender,
875 block_hash: Hash,
876 session_index: SessionIndex,
877) -> Option<&'a SessionInfo>
878where
879 Sender: SubsystemSender<RuntimeApiMessage>,
880{
881 get_extended_session_info_by_index(runtime_info, sender, block_hash, session_index)
882 .await
883 .map(|extended_info| &extended_info.session_info)
884}
885
886struct State {
887 keystore: Arc<LocalKeystore>,
888 slot_duration_millis: u64,
889 clock: Arc<dyn Clock + Send + Sync>,
890 assignment_criteria: Box<dyn AssignmentCriteria + Send + Sync>,
891 per_block_assignments_gathering_times:
895 LruMap<BlockNumber, HashMap<(Hash, CandidateHash), AssignmentGatheringRecord>>,
896 no_show_stats: NoShowStats,
897}
898
899const NO_SHOW_DUMP_FREQUENCY: BlockNumber = 50;
901pub(crate) const MAX_RECORDED_NO_SHOW_VALIDATORS_PER_CANDIDATE: usize = 20;
903
904#[derive(Debug, Clone, PartialEq, Eq, Default)]
909struct NoShowStats {
910 per_validator_no_show: HashMap<SessionIndex, HashMap<ValidatorIndex, usize>>,
911 per_parachain_no_show: HashMap<u32, usize>,
912 last_dumped_block_number: BlockNumber,
913}
914
915impl NoShowStats {
916 fn maybe_print(&mut self, current_block_number: BlockNumber) {
919 if self.last_dumped_block_number > current_block_number ||
920 current_block_number - self.last_dumped_block_number < NO_SHOW_DUMP_FREQUENCY
921 {
922 return;
923 }
924 if self.per_parachain_no_show.is_empty() && self.per_validator_no_show.is_empty() {
925 return;
926 }
927
928 gum::debug!(
929 target: LOG_TARGET,
930 "Validators with no_show {:?} and parachains with no_shows {:?} since {:}",
931 self.per_validator_no_show,
932 self.per_parachain_no_show,
933 self.last_dumped_block_number
934 );
935
936 self.last_dumped_block_number = current_block_number;
937
938 self.per_validator_no_show.clear();
939 self.per_parachain_no_show.clear();
940 }
941}
942
943#[derive(Debug, Clone, PartialEq, Eq)]
944struct AssignmentGatheringRecord {
945 stage: usize,
950 stage_start: Option<Instant>,
952}
953
954impl Default for AssignmentGatheringRecord {
955 fn default() -> Self {
956 AssignmentGatheringRecord { stage: 0, stage_start: Some(Instant::now()) }
957 }
958}
959
960#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
961impl State {
962 async fn approval_status<Sender, 'a, 'b>(
966 &'a self,
967 sender: &mut Sender,
968 session_info_provider: &'a mut RuntimeInfo,
969 block_entry: &'a BlockEntry,
970 candidate_entry: &'b CandidateEntry,
971 ) -> Option<(&'b ApprovalEntry, ApprovalStatus)>
972 where
973 Sender: SubsystemSender<RuntimeApiMessage>,
974 {
975 let session_info = match get_session_info_by_index(
976 session_info_provider,
977 sender,
978 block_entry.parent_hash(),
979 block_entry.session(),
980 )
981 .await
982 {
983 Some(s) => s,
984 None => return None,
985 };
986 let block_hash = block_entry.block_hash();
987
988 let tranche_now = self.clock.tranche_now(self.slot_duration_millis, block_entry.slot());
989 let block_tick = slot_number_to_tick(self.slot_duration_millis, block_entry.slot());
990 let no_show_duration = slot_number_to_tick(
991 self.slot_duration_millis,
992 Slot::from(u64::from(session_info.no_show_slots)),
993 );
994
995 if let Some(approval_entry) = candidate_entry.approval_entry(&block_hash) {
996 let TranchesToApproveResult {
997 required_tranches,
998 total_observed_no_shows,
999 no_show_validators,
1000 } = approval_checking::tranches_to_approve(
1001 approval_entry,
1002 candidate_entry.approvals(),
1003 tranche_now,
1004 block_tick,
1005 no_show_duration,
1006 session_info.needed_approvals as _,
1007 );
1008
1009 let status = ApprovalStatus {
1010 required_tranches,
1011 block_tick,
1012 tranche_now,
1013 last_no_shows: total_observed_no_shows,
1014 no_show_validators,
1015 };
1016
1017 Some((approval_entry, status))
1018 } else {
1019 None
1020 }
1021 }
1022
1023 fn mark_begining_of_gathering_assignments(
1024 &mut self,
1025 block_number: BlockNumber,
1026 block_hash: Hash,
1027 candidate: CandidateHash,
1028 ) {
1029 if let Some(record) = self
1030 .per_block_assignments_gathering_times
1031 .get_or_insert(block_number, HashMap::new)
1032 .and_then(|records| Some(records.entry((block_hash, candidate)).or_default()))
1033 {
1034 if record.stage_start.is_none() {
1035 record.stage += 1;
1036 gum::debug!(
1037 target: LOG_TARGET,
1038 stage = ?record.stage,
1039 ?block_hash,
1040 ?candidate,
1041 "Started a new assignment gathering stage",
1042 );
1043 record.stage_start = Some(Instant::now());
1044 }
1045 }
1046 }
1047
1048 fn mark_gathered_enough_assignments(
1049 &mut self,
1050 block_number: BlockNumber,
1051 block_hash: Hash,
1052 candidate: CandidateHash,
1053 ) -> AssignmentGatheringRecord {
1054 let record = self
1055 .per_block_assignments_gathering_times
1056 .get(&block_number)
1057 .and_then(|entry| entry.get_mut(&(block_hash, candidate)));
1058 let stage = record.as_ref().map(|record| record.stage).unwrap_or_default();
1059 AssignmentGatheringRecord {
1060 stage,
1061 stage_start: record.and_then(|record| record.stage_start.take()),
1062 }
1063 }
1064
1065 fn cleanup_assignments_gathering_timestamp(&mut self, remove_lower_than: BlockNumber) {
1066 while let Some((block_number, _)) = self.per_block_assignments_gathering_times.peek_oldest()
1067 {
1068 if *block_number < remove_lower_than {
1069 self.per_block_assignments_gathering_times.pop_oldest();
1070 } else {
1071 break;
1072 }
1073 }
1074 }
1075
1076 fn observe_assignment_gathering_status(
1077 &mut self,
1078 metrics: &Metrics,
1079 required_tranches: &RequiredTranches,
1080 block_hash: Hash,
1081 block_number: BlockNumber,
1082 candidate_hash: CandidateHash,
1083 ) {
1084 match required_tranches {
1085 RequiredTranches::All | RequiredTranches::Pending { .. } => {
1086 self.mark_begining_of_gathering_assignments(
1087 block_number,
1088 block_hash,
1089 candidate_hash,
1090 );
1091 },
1092 RequiredTranches::Exact { .. } => {
1093 let time_to_gather =
1094 self.mark_gathered_enough_assignments(block_number, block_hash, candidate_hash);
1095 if let Some(gathering_started) = time_to_gather.stage_start {
1096 if gathering_started.elapsed().as_millis() > 6000 {
1097 gum::trace!(
1098 target: LOG_TARGET,
1099 ?block_hash,
1100 ?candidate_hash,
1101 "Long assignment gathering time",
1102 );
1103 }
1104 metrics.observe_assignment_gathering_time(
1105 time_to_gather.stage,
1106 gathering_started.elapsed().as_millis() as usize,
1107 )
1108 }
1109 },
1110 }
1111 }
1112
1113 fn record_no_shows(
1114 &mut self,
1115 session_index: SessionIndex,
1116 para_id: u32,
1117 no_show_validators: &Vec<ValidatorIndex>,
1118 ) {
1119 if !no_show_validators.is_empty() {
1120 *self.no_show_stats.per_parachain_no_show.entry(para_id.into()).or_default() += 1;
1121 }
1122 for validator_index in no_show_validators {
1123 *self
1124 .no_show_stats
1125 .per_validator_no_show
1126 .entry(session_index)
1127 .or_default()
1128 .entry(*validator_index)
1129 .or_default() += 1;
1130 }
1131 }
1132}
1133
1134#[derive(Debug, Clone)]
1135enum Action {
1136 ScheduleWakeup {
1137 block_hash: Hash,
1138 block_number: BlockNumber,
1139 candidate_hash: CandidateHash,
1140 tick: Tick,
1141 },
1142 LaunchApproval {
1143 claimed_candidate_indices: CandidateBitfield,
1144 candidate_hash: CandidateHash,
1145 indirect_cert: IndirectAssignmentCertV2,
1146 assignment_tranche: DelayTranche,
1147 relay_block_hash: Hash,
1148 session: SessionIndex,
1149 candidate: CandidateReceipt,
1150 backing_group: GroupIndex,
1151 distribute_assignment: bool,
1152 core_index: Option<CoreIndex>,
1153 },
1154 NoteApprovedInChainSelection(Hash),
1155 IssueApproval(CandidateHash, ApprovalVoteRequest),
1156 BecomeActive,
1157 Conclude,
1158}
1159
1160#[async_trait::async_trait]
1162pub trait ApprovalVotingWorkProvider {
1163 async fn recv(&mut self) -> SubsystemResult<FromOrchestra<ApprovalVotingMessage>>;
1164}
1165
1166#[async_trait::async_trait]
1167#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
1168impl<Context> ApprovalVotingWorkProvider for Context {
1169 async fn recv(&mut self) -> SubsystemResult<FromOrchestra<ApprovalVotingMessage>> {
1170 self.recv().await
1171 }
1172}
1173
1174#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
1175async fn run<
1176 B,
1177 WorkProvider: ApprovalVotingWorkProvider,
1178 Sender: SubsystemSender<ChainApiMessage>
1179 + SubsystemSender<RuntimeApiMessage>
1180 + SubsystemSender<ChainSelectionMessage>
1181 + SubsystemSender<AvailabilityRecoveryMessage>
1182 + SubsystemSender<DisputeCoordinatorMessage>
1183 + SubsystemSender<CandidateValidationMessage>
1184 + Clone,
1185 ADSender: SubsystemSender<ApprovalDistributionMessage>,
1186>(
1187 mut work_provider: WorkProvider,
1188 mut to_other_subsystems: Sender,
1189 mut to_approval_distr: ADSender,
1190 mut subsystem: ApprovalVotingSubsystem,
1191 assignment_criteria: Box<dyn AssignmentCriteria + Send + Sync>,
1192 mut backend: B,
1193) -> SubsystemResult<()>
1194where
1195 B: Backend,
1196{
1197 if let Err(err) = db_sanity_check(subsystem.db.clone(), subsystem.db_config) {
1198 gum::warn!(target: LOG_TARGET, ?err, "Could not run approval vote DB sanity check");
1199 }
1200
1201 let mut state = State {
1202 keystore: subsystem.keystore,
1203 slot_duration_millis: subsystem.slot_duration_millis,
1204 clock: subsystem.clock,
1205 assignment_criteria,
1206 per_block_assignments_gathering_times: LruMap::new(ByLength::new(
1207 MAX_BLOCKS_WITH_ASSIGNMENT_TIMESTAMPS,
1208 )),
1209 no_show_stats: NoShowStats::default(),
1210 };
1211
1212 let mut last_finalized_height: Option<BlockNumber> = {
1213 let (tx, rx) = oneshot::channel();
1214 to_other_subsystems
1215 .send_message(ChainApiMessage::FinalizedBlockNumber(tx))
1216 .await;
1217 match rx.await? {
1218 Ok(number) => Some(number),
1219 Err(err) => {
1220 gum::warn!(target: LOG_TARGET, ?err, "Failed fetching finalized number");
1221 None
1222 },
1223 }
1224 };
1225
1226 let mut session_info_provider = RuntimeInfo::new_with_config(RuntimeInfoConfig {
1228 keystore: None,
1229 session_cache_lru_size: DISPUTE_WINDOW.get(),
1230 });
1231
1232 let mut wakeups = Wakeups::default();
1233 let mut currently_checking_set = CurrentlyCheckingSet::default();
1234 let mut delayed_approvals_timers = DelayedApprovalTimer::default();
1235 let mut approvals_cache = LruMap::new(ByLength::new(APPROVAL_CACHE_SIZE));
1236
1237 loop {
1238 let mut overlayed_db = OverlayedBackend::new(&backend);
1239 let actions = futures::select! {
1240 (_tick, woken_block, woken_candidate) = wakeups.next(&*state.clock).fuse() => {
1241 subsystem.metrics.on_wakeup();
1242 process_wakeup(
1243 &mut to_other_subsystems,
1244 &mut state,
1245 &mut overlayed_db,
1246 &mut session_info_provider,
1247 woken_block,
1248 woken_candidate,
1249 &subsystem.metrics,
1250 &wakeups,
1251 ).await?
1252 }
1253 next_msg = work_provider.recv().fuse() => {
1254 let mut actions = handle_from_overseer(
1255 &mut to_other_subsystems,
1256 &mut to_approval_distr,
1257 &subsystem.spawner,
1258 &mut state,
1259 &mut overlayed_db,
1260 &mut session_info_provider,
1261 &subsystem.metrics,
1262 next_msg?,
1263 &mut last_finalized_height,
1264 &mut wakeups,
1265 ).await?;
1266
1267 if let Mode::Syncing(ref mut oracle) = subsystem.mode {
1268 if !oracle.is_major_syncing() {
1269 actions.insert(0, Action::BecomeActive)
1271 }
1272 }
1273
1274 actions
1275 }
1276 approval_state = currently_checking_set.next(&mut approvals_cache).fuse() => {
1277 let mut actions = Vec::new();
1278 let (
1279 relay_block_hashes,
1280 ApprovalState {
1281 validator_index,
1282 candidate_hash,
1283 approval_outcome,
1284 retry_info,
1285 }
1286 ) = approval_state;
1287
1288 if matches!(approval_outcome, ApprovalOutcome::Approved) {
1289 let mut approvals: Vec<Action> = relay_block_hashes
1290 .iter()
1291 .map(|block_hash|
1292 Action::IssueApproval(
1293 candidate_hash,
1294 ApprovalVoteRequest {
1295 validator_index,
1296 block_hash: *block_hash,
1297 },
1298 )
1299 )
1300 .collect();
1301 actions.append(&mut approvals);
1302 }
1303
1304 if let Some(retry_info) = retry_info {
1305 for block_hash in relay_block_hashes {
1306 if overlayed_db.load_block_entry(&block_hash).map(|block_info| block_info.is_some()).unwrap_or(false) {
1307 let sender = to_other_subsystems.clone();
1308 let spawn_handle = subsystem.spawner.clone();
1309 let metrics = subsystem.metrics.clone();
1310 let retry_info = retry_info.clone();
1311 let candidate = retry_info.candidate.clone();
1312
1313 currently_checking_set
1314 .insert_relay_block_hash(
1315 candidate_hash,
1316 validator_index,
1317 block_hash,
1318 async move {
1319 launch_approval(
1320 sender,
1321 spawn_handle,
1322 metrics,
1323 retry_info.session_index,
1324 candidate,
1325 validator_index,
1326 block_hash,
1327 retry_info.backing_group,
1328 retry_info.core_index,
1329 retry_info,
1330 )
1331 .await
1332 },
1333 )
1334 .await?;
1335 }
1336 }
1337 }
1338
1339 actions
1340 },
1341 (block_hash, validator_index) = delayed_approvals_timers.select_next_some() => {
1342 gum::debug!(
1343 target: LOG_TARGET,
1344 ?block_hash,
1345 ?validator_index,
1346 "Sign approval for multiple candidates",
1347 );
1348
1349 match maybe_create_signature(
1350 &mut overlayed_db,
1351 &mut session_info_provider,
1352 &state,
1353 &mut to_other_subsystems,
1354 &mut to_approval_distr,
1355 block_hash,
1356 validator_index,
1357 &subsystem.metrics,
1358 ).await {
1359 Ok(Some(next_wakeup)) => {
1360 delayed_approvals_timers.maybe_arm_timer(next_wakeup, state.clock.as_ref(), block_hash, validator_index);
1361 },
1362 Ok(None) => {}
1363 Err(err) => {
1364 gum::error!(
1365 target: LOG_TARGET,
1366 ?err,
1367 "Failed to create signature",
1368 );
1369 }
1370 }
1371 vec![]
1372 }
1373 };
1374
1375 if handle_actions(
1376 &mut to_other_subsystems,
1377 &mut to_approval_distr,
1378 &subsystem.spawner,
1379 &mut state,
1380 &mut overlayed_db,
1381 &mut session_info_provider,
1382 &subsystem.metrics,
1383 &mut wakeups,
1384 &mut currently_checking_set,
1385 &mut delayed_approvals_timers,
1386 &mut approvals_cache,
1387 &mut subsystem.mode,
1388 actions,
1389 subsystem.max_approval_retries,
1390 subsystem.retry_backoff,
1391 )
1392 .await?
1393 {
1394 break;
1395 }
1396
1397 if !overlayed_db.is_empty() {
1398 let _timer = subsystem.metrics.time_db_transaction();
1399 let ops = overlayed_db.into_write_ops();
1400 backend.write(ops)?;
1401 }
1402 }
1403
1404 Ok(())
1405}
1406
1407pub async fn start_approval_worker<
1409 WorkProvider: ApprovalVotingWorkProvider + Send + 'static,
1410 Sender: SubsystemSender<ChainApiMessage>
1411 + SubsystemSender<RuntimeApiMessage>
1412 + SubsystemSender<ChainSelectionMessage>
1413 + SubsystemSender<AvailabilityRecoveryMessage>
1414 + SubsystemSender<DisputeCoordinatorMessage>
1415 + SubsystemSender<CandidateValidationMessage>
1416 + Clone,
1417 ADSender: SubsystemSender<ApprovalDistributionMessage>,
1418>(
1419 work_provider: WorkProvider,
1420 to_other_subsystems: Sender,
1421 to_approval_distr: ADSender,
1422 config: Config,
1423 db: Arc<dyn Database>,
1424 keystore: Arc<LocalKeystore>,
1425 sync_oracle: Box<dyn SyncOracle + Send>,
1426 metrics: Metrics,
1427 spawner: Arc<dyn overseer::gen::Spawner + 'static>,
1428 task_name: &'static str,
1429 group_name: &'static str,
1430 clock: Arc<dyn Clock + Send + Sync>,
1431) -> SubsystemResult<()> {
1432 let approval_voting = ApprovalVotingSubsystem::with_config_and_clock(
1433 config,
1434 db.clone(),
1435 keystore,
1436 sync_oracle,
1437 metrics,
1438 clock,
1439 spawner,
1440 MAX_APPROVAL_RETRIES,
1441 APPROVAL_CHECKING_TIMEOUT / 2,
1442 );
1443 let backend = DbBackend::new(db.clone(), approval_voting.db_config);
1444 let spawner = approval_voting.spawner.clone();
1445 spawner.spawn_blocking(
1446 task_name,
1447 Some(group_name),
1448 Box::pin(async move {
1449 if let Err(err) = run(
1450 work_provider,
1451 to_other_subsystems,
1452 to_approval_distr,
1453 approval_voting,
1454 Box::new(RealAssignmentCriteria),
1455 backend,
1456 )
1457 .await
1458 {
1459 gum::error!(target: LOG_TARGET, ?err, "Approval voting worker stopped processing messages");
1460 };
1461 }),
1462 );
1463 Ok(())
1464}
1465
1466#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
1486async fn handle_actions<
1487 Sender: SubsystemSender<ChainApiMessage>
1488 + SubsystemSender<RuntimeApiMessage>
1489 + SubsystemSender<ChainSelectionMessage>
1490 + SubsystemSender<AvailabilityRecoveryMessage>
1491 + SubsystemSender<DisputeCoordinatorMessage>
1492 + SubsystemSender<CandidateValidationMessage>
1493 + Clone,
1494 ADSender: SubsystemSender<ApprovalDistributionMessage>,
1495>(
1496 sender: &mut Sender,
1497 approval_voting_sender: &mut ADSender,
1498 spawn_handle: &Arc<dyn overseer::gen::Spawner + 'static>,
1499 state: &mut State,
1500 overlayed_db: &mut OverlayedBackend<'_, impl Backend>,
1501 session_info_provider: &mut RuntimeInfo,
1502 metrics: &Metrics,
1503 wakeups: &mut Wakeups,
1504 currently_checking_set: &mut CurrentlyCheckingSet,
1505 delayed_approvals_timers: &mut DelayedApprovalTimer,
1506 approvals_cache: &mut LruMap<CandidateHash, ApprovalOutcome>,
1507 mode: &mut Mode,
1508 actions: Vec<Action>,
1509 max_approval_retries: u32,
1510 retry_backoff: Duration,
1511) -> SubsystemResult<bool> {
1512 let mut conclude = false;
1513 let mut actions_iter = actions.into_iter();
1514 while let Some(action) = actions_iter.next() {
1515 match action {
1516 Action::ScheduleWakeup { block_hash, block_number, candidate_hash, tick } => {
1517 wakeups.schedule(block_hash, block_number, candidate_hash, tick);
1518 },
1519 Action::IssueApproval(candidate_hash, approval_request) => {
1520 let next_actions: Vec<Action> = issue_approval(
1532 sender,
1533 approval_voting_sender,
1534 state,
1535 overlayed_db,
1536 session_info_provider,
1537 metrics,
1538 candidate_hash,
1539 delayed_approvals_timers,
1540 approval_request,
1541 &wakeups,
1542 )
1543 .await?
1544 .into_iter()
1545 .map(|v| v.clone())
1546 .chain(actions_iter)
1547 .collect();
1548
1549 actions_iter = next_actions.into_iter();
1550 },
1551 Action::LaunchApproval {
1552 claimed_candidate_indices,
1553 candidate_hash,
1554 indirect_cert,
1555 assignment_tranche,
1556 relay_block_hash,
1557 session,
1558 candidate,
1559 backing_group,
1560 distribute_assignment,
1561 core_index,
1562 } => {
1563 if let Mode::Syncing(_) = *mode {
1565 continue;
1566 }
1567
1568 metrics.on_assignment_produced(assignment_tranche);
1569 let block_hash = indirect_cert.block_hash;
1570 let validator_index = indirect_cert.validator;
1571
1572 if distribute_assignment {
1573 approval_voting_sender.send_unbounded_message(
1574 ApprovalDistributionMessage::DistributeAssignment(
1575 indirect_cert,
1576 claimed_candidate_indices,
1577 ),
1578 );
1579 }
1580
1581 match approvals_cache.get(&candidate_hash) {
1582 Some(ApprovalOutcome::Approved) => {
1583 let new_actions: Vec<Action> = std::iter::once(Action::IssueApproval(
1584 candidate_hash,
1585 ApprovalVoteRequest { validator_index, block_hash },
1586 ))
1587 .map(|v| v.clone())
1588 .chain(actions_iter)
1589 .collect();
1590 actions_iter = new_actions.into_iter();
1591 },
1592 None => {
1593 let sender = sender.clone();
1594 let spawn_handle = spawn_handle.clone();
1595
1596 let retry = RetryApprovalInfo {
1597 candidate: candidate.clone(),
1598 backing_group,
1599 core_index,
1600 session_index: session,
1601 attempts_remaining: max_approval_retries,
1602 backoff: retry_backoff,
1603 };
1604
1605 currently_checking_set
1606 .insert_relay_block_hash(
1607 candidate_hash,
1608 validator_index,
1609 relay_block_hash,
1610 async move {
1611 launch_approval(
1612 sender,
1613 spawn_handle,
1614 metrics.clone(),
1615 session,
1616 candidate,
1617 validator_index,
1618 block_hash,
1619 backing_group,
1620 core_index,
1621 retry,
1622 )
1623 .await
1624 },
1625 )
1626 .await?;
1627 },
1628 Some(_) => {},
1629 }
1630 },
1631 Action::NoteApprovedInChainSelection(block_hash) => {
1632 sender.send_message(ChainSelectionMessage::Approved(block_hash)).await;
1633 },
1634 Action::BecomeActive => {
1635 *mode = Mode::Active;
1636
1637 let (messages, next_actions) = distribution_messages_for_activation(
1638 overlayed_db,
1639 state,
1640 delayed_approvals_timers,
1641 )
1642 .await?;
1643 for message in messages.into_iter() {
1644 approval_voting_sender.send_unbounded_message(message);
1645 }
1646 let next_actions: Vec<Action> =
1647 next_actions.into_iter().map(|v| v.clone()).chain(actions_iter).collect();
1648
1649 actions_iter = next_actions.into_iter();
1650 },
1651 Action::Conclude => {
1652 conclude = true;
1653 },
1654 }
1655 }
1656
1657 Ok(conclude)
1658}
1659
1660fn cores_to_candidate_indices(
1661 core_indices: &CoreBitfield,
1662 block_entry: &BlockEntry,
1663) -> Result<CandidateBitfield, BitfieldError> {
1664 let mut candidate_indices = Vec::new();
1665
1666 for claimed_core_index in core_indices.iter_ones() {
1668 if let Some(candidate_index) = block_entry
1669 .candidates()
1670 .iter()
1671 .position(|(core_index, _)| core_index.0 == claimed_core_index as u32)
1672 {
1673 candidate_indices.push(candidate_index as _);
1674 }
1675 }
1676
1677 CandidateBitfield::try_from(candidate_indices)
1678}
1679
1680fn get_assignment_core_indices(assignment: &AssignmentCertKindV2) -> CoreBitfield {
1682 match &assignment {
1683 AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => core_bitfield.clone(),
1684 AssignmentCertKindV2::RelayVRFDelay { core_index } => {
1685 CoreBitfield::try_from(vec![*core_index]).expect("Not an empty vec; qed")
1686 },
1687 }
1688}
1689
1690async fn distribution_messages_for_activation(
1691 db: &OverlayedBackend<'_, impl Backend>,
1692 state: &State,
1693 delayed_approvals_timers: &mut DelayedApprovalTimer,
1694) -> SubsystemResult<(Vec<ApprovalDistributionMessage>, Vec<Action>)> {
1695 let all_blocks: Vec<Hash> = db.load_all_blocks()?;
1696
1697 let mut approval_meta = Vec::with_capacity(all_blocks.len());
1698 let mut messages = Vec::new();
1699 let mut approvals = Vec::new();
1700 let mut actions = Vec::new();
1701
1702 messages.push(ApprovalDistributionMessage::NewBlocks(Vec::new())); for block_hash in all_blocks {
1705 let block_entry = match db.load_block_entry(&block_hash)? {
1706 Some(b) => b,
1707 None => {
1708 gum::warn!(target: LOG_TARGET, ?block_hash, "Missing block entry");
1709
1710 continue;
1711 },
1712 };
1713
1714 approval_meta.push(BlockApprovalMeta {
1715 hash: block_hash,
1716 number: block_entry.block_number(),
1717 parent_hash: block_entry.parent_hash(),
1718 candidates: block_entry
1719 .candidates()
1720 .iter()
1721 .map(|(core_index, c_hash)| {
1722 let candidate = db.load_candidate_entry(c_hash).ok().flatten();
1723 let group_index = candidate
1724 .and_then(|entry| {
1725 entry.approval_entry(&block_hash).map(|entry| entry.backing_group())
1726 })
1727 .unwrap_or_else(|| {
1728 gum::warn!(
1729 target: LOG_TARGET,
1730 ?block_hash,
1731 ?c_hash,
1732 "Missing candidate entry or approval entry",
1733 );
1734 GroupIndex::default()
1735 });
1736 (*c_hash, *core_index, group_index)
1737 })
1738 .collect(),
1739 slot: block_entry.slot(),
1740 session: block_entry.session(),
1741 vrf_story: block_entry.relay_vrf_story(),
1742 });
1743 let mut signatures_queued = HashSet::new();
1744 for (core_index, candidate_hash) in block_entry.candidates() {
1745 let candidate_entry = match db.load_candidate_entry(&candidate_hash)? {
1746 Some(c) => c,
1747 None => {
1748 gum::warn!(
1749 target: LOG_TARGET,
1750 ?block_hash,
1751 ?candidate_hash,
1752 "Missing candidate entry",
1753 );
1754
1755 continue;
1756 },
1757 };
1758
1759 match candidate_entry.approval_entry(&block_hash) {
1760 Some(approval_entry) => {
1761 match approval_entry.local_statements() {
1762 (None, None) => {
1763 if approval_entry
1764 .our_assignment()
1765 .map(|assignment| !assignment.triggered())
1766 .unwrap_or(false)
1767 {
1768 actions.push(Action::ScheduleWakeup {
1769 block_hash,
1770 block_number: block_entry.block_number(),
1771 candidate_hash: *candidate_hash,
1772 tick: state.clock.tick_now() + RESTART_WAKEUP_DELAY,
1773 })
1774 }
1775 },
1776 (None, Some(_)) => {}, (Some(assignment), None) => {
1778 let claimed_core_indices =
1779 get_assignment_core_indices(&assignment.cert().kind);
1780
1781 if block_entry.has_candidates_pending_signature() {
1782 delayed_approvals_timers.maybe_arm_timer(
1783 state.clock.tick_now(),
1784 state.clock.as_ref(),
1785 block_entry.block_hash(),
1786 assignment.validator_index(),
1787 )
1788 }
1789
1790 match cores_to_candidate_indices(&claimed_core_indices, &block_entry) {
1791 Ok(bitfield) => {
1792 gum::debug!(
1793 target: LOG_TARGET,
1794 candidate_hash = ?candidate_entry.candidate_receipt().hash(),
1795 ?block_hash,
1796 "Discovered, triggered assignment, not approved yet",
1797 );
1798
1799 let indirect_cert = IndirectAssignmentCertV2 {
1800 block_hash,
1801 validator: assignment.validator_index(),
1802 cert: assignment.cert().clone(),
1803 };
1804 messages.push(
1805 ApprovalDistributionMessage::DistributeAssignment(
1806 indirect_cert.clone(),
1807 bitfield.clone(),
1808 ),
1809 );
1810
1811 if !block_entry.candidate_is_pending_signature(*candidate_hash)
1812 {
1813 actions.push(Action::LaunchApproval {
1814 claimed_candidate_indices: bitfield,
1815 candidate_hash: candidate_entry
1816 .candidate_receipt()
1817 .hash(),
1818 indirect_cert,
1819 assignment_tranche: assignment.tranche(),
1820 relay_block_hash: block_hash,
1821 session: block_entry.session(),
1822 candidate: candidate_entry.candidate_receipt().clone(),
1823 backing_group: approval_entry.backing_group(),
1824 distribute_assignment: false,
1825 core_index: Some(*core_index),
1826 });
1827 }
1828 },
1829 Err(err) => {
1830 gum::warn!(
1833 target: LOG_TARGET,
1834 ?block_hash,
1835 ?candidate_hash,
1836 ?err,
1837 "Failed to create assignment bitfield",
1838 );
1839 },
1840 }
1841 },
1842 (Some(assignment), Some(approval_sig)) => {
1843 let claimed_core_indices =
1844 get_assignment_core_indices(&assignment.cert().kind);
1845 match cores_to_candidate_indices(&claimed_core_indices, &block_entry) {
1846 Ok(bitfield) => messages.push(
1847 ApprovalDistributionMessage::DistributeAssignment(
1848 IndirectAssignmentCertV2 {
1849 block_hash,
1850 validator: assignment.validator_index(),
1851 cert: assignment.cert().clone(),
1852 },
1853 bitfield,
1854 ),
1855 ),
1856 Err(err) => {
1857 gum::warn!(
1858 target: LOG_TARGET,
1859 ?block_hash,
1860 ?candidate_hash,
1861 ?err,
1862 "Failed to create assignment bitfield",
1863 );
1864 continue;
1866 },
1867 }
1868 if signatures_queued
1869 .insert(approval_sig.signed_candidates_indices.clone())
1870 {
1871 approvals.push(ApprovalDistributionMessage::DistributeApproval(
1872 IndirectSignedApprovalVoteV2 {
1873 block_hash,
1874 candidate_indices: approval_sig.signed_candidates_indices,
1875 validator: assignment.validator_index(),
1876 signature: approval_sig.signature,
1877 },
1878 ))
1879 };
1880 },
1881 }
1882 },
1883 None => {
1884 gum::warn!(
1885 target: LOG_TARGET,
1886 ?block_hash,
1887 ?candidate_hash,
1888 "Missing approval entry",
1889 );
1890 },
1891 }
1892 }
1893 }
1894
1895 messages[0] = ApprovalDistributionMessage::NewBlocks(approval_meta);
1896 messages.extend(approvals.into_iter());
1900 Ok((messages, actions))
1901}
1902
1903async fn handle_from_overseer<
1905 Sender: SubsystemSender<ChainApiMessage>
1906 + SubsystemSender<RuntimeApiMessage>
1907 + SubsystemSender<ChainSelectionMessage>
1908 + Clone,
1909 ADSender: SubsystemSender<ApprovalDistributionMessage>,
1910>(
1911 sender: &mut Sender,
1912 approval_voting_sender: &mut ADSender,
1913 spawn_handle: &Arc<dyn overseer::gen::Spawner + 'static>,
1914 state: &mut State,
1915 db: &mut OverlayedBackend<'_, impl Backend>,
1916 session_info_provider: &mut RuntimeInfo,
1917 metrics: &Metrics,
1918 x: FromOrchestra<ApprovalVotingMessage>,
1919 last_finalized_height: &mut Option<BlockNumber>,
1920 wakeups: &mut Wakeups,
1921) -> SubsystemResult<Vec<Action>> {
1922 let actions = match x {
1923 FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) => {
1924 let mut actions = Vec::new();
1925 if let Some(activated) = update.activated {
1926 let head = activated.hash;
1927 match import::handle_new_head(
1928 sender,
1929 approval_voting_sender,
1930 state,
1931 db,
1932 session_info_provider,
1933 head,
1934 last_finalized_height,
1935 )
1936 .await
1937 {
1938 Err(e) => return Err(SubsystemError::with_origin("db", e)),
1939 Ok(block_imported_candidates) => {
1940 for block_batch in block_imported_candidates {
1942 gum::debug!(
1943 target: LOG_TARGET,
1944 block_number = ?block_batch.block_number,
1945 block_hash = ?block_batch.block_hash,
1946 num_candidates = block_batch.imported_candidates.len(),
1947 "Imported new block.",
1948 );
1949
1950 state.no_show_stats.maybe_print(block_batch.block_number);
1951
1952 for (c_hash, c_entry) in block_batch.imported_candidates {
1953 metrics.on_candidate_imported();
1954
1955 let our_tranche = c_entry
1956 .approval_entry(&block_batch.block_hash)
1957 .and_then(|a| a.our_assignment().map(|a| a.tranche()));
1958
1959 if let Some(our_tranche) = our_tranche {
1960 let tick = our_tranche as Tick + block_batch.block_tick;
1961 gum::trace!(
1962 target: LOG_TARGET,
1963 tranche = our_tranche,
1964 candidate_hash = ?c_hash,
1965 block_hash = ?block_batch.block_hash,
1966 block_tick = block_batch.block_tick,
1967 "Scheduling first wakeup.",
1968 );
1969
1970 actions.push(Action::ScheduleWakeup {
1974 block_hash: block_batch.block_hash,
1975 block_number: block_batch.block_number,
1976 candidate_hash: c_hash,
1977 tick,
1978 });
1979 }
1980 }
1981 }
1982 },
1983 }
1984 }
1985
1986 actions
1987 },
1988 FromOrchestra::Signal(OverseerSignal::BlockFinalized(block_hash, block_number)) => {
1989 gum::debug!(target: LOG_TARGET, ?block_hash, ?block_number, "Block finalized");
1990 *last_finalized_height = Some(block_number);
1991
1992 crate::ops::canonicalize(db, block_number, block_hash)
1993 .map_err(|e| SubsystemError::with_origin("db", e))?;
1994
1995 wakeups.prune_finalized_wakeups(block_number);
1998 state.cleanup_assignments_gathering_timestamp(block_number);
1999
2000 Vec::new()
2006 },
2007 FromOrchestra::Signal(OverseerSignal::Conclude) => {
2008 vec![Action::Conclude]
2009 },
2010 FromOrchestra::Communication { msg } => match msg {
2011 ApprovalVotingMessage::ImportAssignment(checked_assignment, tx) => {
2012 let (check_outcome, actions) =
2013 import_assignment(sender, state, db, session_info_provider, checked_assignment)
2014 .await?;
2015 if let AssignmentCheckResult::Bad(ref err) = check_outcome {
2019 gum::debug!(target: LOG_TARGET, ?err, "Unexpected fail when importing an assignment");
2020 }
2021 let _ = tx.map(|tx| tx.send(check_outcome));
2022 actions
2023 },
2024 ApprovalVotingMessage::ImportApproval(a, tx) => {
2025 let result =
2026 import_approval(sender, state, db, session_info_provider, metrics, a, &wakeups)
2027 .await?;
2028 if let ApprovalCheckResult::Bad(ref err) = result.1 {
2032 gum::debug!(target: LOG_TARGET, ?err, "Unexpected fail when importing an approval");
2033 }
2034 let _ = tx.map(|tx| tx.send(result.1));
2035
2036 result.0
2037 },
2038 ApprovalVotingMessage::ApprovedAncestor(target, lower_bound, res) => {
2039 match handle_approved_ancestor(sender, db, target, lower_bound, wakeups, &metrics)
2040 .await
2041 {
2042 Ok(v) => {
2043 let _ = res.send(v);
2044 },
2045 Err(e) => {
2046 let _ = res.send(None);
2047 return Err(e);
2048 },
2049 }
2050
2051 Vec::new()
2052 },
2053 ApprovalVotingMessage::GetApprovalSignaturesForCandidate(candidate_hash, tx) => {
2054 metrics.on_candidate_signatures_request();
2055 get_approval_signatures_for_candidate(
2056 approval_voting_sender.clone(),
2057 spawn_handle,
2058 db,
2059 candidate_hash,
2060 tx,
2061 )
2062 .await?;
2063 Vec::new()
2064 },
2065 },
2066 };
2067
2068 Ok(actions)
2069}
2070
2071#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
2076async fn get_approval_signatures_for_candidate<
2077 Sender: SubsystemSender<ApprovalDistributionMessage>,
2078>(
2079 mut sender: Sender,
2080 spawn_handle: &Arc<dyn overseer::gen::Spawner + 'static>,
2081 db: &OverlayedBackend<'_, impl Backend>,
2082 candidate_hash: CandidateHash,
2083 tx: oneshot::Sender<
2084 HashMap<ValidatorIndex, (CoalescedApprovalCandidateHashes, ValidatorSignature)>,
2085 >,
2086) -> SubsystemResult<()> {
2087 let send_votes = |votes| {
2088 if let Err(_) = tx.send(votes) {
2089 gum::debug!(
2090 target: LOG_TARGET,
2091 "Sending approval signatures back failed, as receiver got closed."
2092 );
2093 }
2094 };
2095 let entry = match db.load_candidate_entry(&candidate_hash)? {
2096 None => {
2097 send_votes(HashMap::new());
2098 gum::debug!(
2099 target: LOG_TARGET,
2100 ?candidate_hash,
2101 "Sent back empty votes because the candidate was not found in db."
2102 );
2103 return Ok(());
2104 },
2105 Some(e) => e,
2106 };
2107
2108 let relay_hashes = entry.block_assignments.keys();
2109
2110 let mut candidate_indices = HashSet::new();
2111 let mut candidate_indices_to_candidate_hashes: HashMap<
2112 Hash,
2113 HashMap<CandidateIndex, CandidateHash>,
2114 > = HashMap::new();
2115
2116 for hash in relay_hashes {
2118 let entry = match db.load_block_entry(hash)? {
2119 None => {
2120 gum::debug!(
2121 target: LOG_TARGET,
2122 ?candidate_hash,
2123 ?hash,
2124 "Block entry for assignment missing."
2125 );
2126 continue;
2127 },
2128 Some(e) => e,
2129 };
2130 for (candidate_index, (_core_index, c_hash)) in entry.candidates().iter().enumerate() {
2131 if c_hash == &candidate_hash {
2132 candidate_indices.insert((*hash, candidate_index as u32));
2133 }
2134 candidate_indices_to_candidate_hashes
2135 .entry(*hash)
2136 .or_default()
2137 .insert(candidate_index as _, *c_hash);
2138 }
2139 }
2140
2141 let get_approvals = async move {
2142 let (tx_distribution, rx_distribution) = oneshot::channel();
2143 sender.send_unbounded_message(ApprovalDistributionMessage::GetApprovalSignatures(
2144 candidate_indices,
2145 tx_distribution,
2146 ));
2147
2148 match rx_distribution.timeout(WAIT_FOR_SIGS_TIMEOUT).await {
2151 None => {
2152 gum::warn!(
2153 target: LOG_TARGET,
2154 "Waiting for approval signatures timed out - dead lock?"
2155 );
2156 },
2157 Some(Err(_)) => gum::debug!(
2158 target: LOG_TARGET,
2159 "Request for approval signatures got cancelled by `approval-distribution`."
2160 ),
2161 Some(Ok(votes)) => {
2162 let votes = votes
2163 .into_iter()
2164 .filter_map(|(validator_index, (hash, signed_candidates_indices, signature))| {
2165 let candidates_hashes = candidate_indices_to_candidate_hashes.get(&hash);
2166
2167 if candidates_hashes.is_none() {
2168 gum::warn!(
2169 target: LOG_TARGET,
2170 ?hash,
2171 "Possible bug! Could not find map of candidate_hashes for block hash received from approval-distribution"
2172 );
2173 }
2174
2175 let num_signed_candidates = signed_candidates_indices.len();
2176
2177 let signed_candidates_hashes: Vec<CandidateHash> =
2178 signed_candidates_indices
2179 .into_iter()
2180 .filter_map(|candidate_index| {
2181 candidates_hashes.and_then(|candidate_hashes| {
2182 if let Some(candidate_hash) =
2183 candidate_hashes.get(&candidate_index)
2184 {
2185 Some(*candidate_hash)
2186 } else {
2187 gum::warn!(
2188 target: LOG_TARGET,
2189 ?candidate_index,
2190 "Possible bug! Could not find candidate hash for candidate_index coming from approval-distribution"
2191 );
2192 None
2193 }
2194 })
2195 })
2196 .collect();
2197 if num_signed_candidates == signed_candidates_hashes.len() {
2198 match signed_candidates_hashes.try_into() {
2199 Ok(signed_candidates_hashes) =>
2200 Some((validator_index, (signed_candidates_hashes, signature))),
2201 Err(_) => {
2202 gum::warn!(
2203 target: LOG_TARGET,
2204 "Skipping approval signature coalescing more than MAX_COALESCE_APPROVALS candidates"
2205 );
2206 None
2207 },
2208 }
2209 } else {
2210 gum::warn!(
2211 target: LOG_TARGET,
2212 "Possible bug! Could not find all hashes for candidates coming from approval-distribution"
2213 );
2214 None
2215 }
2216 })
2217 .collect();
2218 send_votes(votes)
2219 },
2220 }
2221 };
2222
2223 gum::trace!(
2226 target: LOG_TARGET,
2227 ?candidate_hash,
2228 "Spawning task for fetching signatures from approval-distribution"
2229 );
2230 spawn_handle.spawn(
2231 "get-approval-signatures",
2232 Some("approval-voting-subsystem"),
2233 Box::pin(get_approvals),
2234 );
2235 Ok(())
2236}
2237
2238#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
2239async fn handle_approved_ancestor<Sender: SubsystemSender<ChainApiMessage>>(
2240 sender: &mut Sender,
2241 db: &OverlayedBackend<'_, impl Backend>,
2242 target: Hash,
2243 lower_bound: BlockNumber,
2244 wakeups: &Wakeups,
2245 metrics: &Metrics,
2246) -> SubsystemResult<Option<HighestApprovedAncestorBlock>> {
2247 const MAX_TRACING_WINDOW: usize = 200;
2248 const ABNORMAL_DEPTH_THRESHOLD: usize = 5;
2249 const LOGGING_DEPTH_THRESHOLD: usize = 10;
2250
2251 let mut all_approved_max = None;
2252
2253 let target_number = {
2254 let (tx, rx) = oneshot::channel();
2255
2256 sender.send_message(ChainApiMessage::BlockNumber(target, tx)).await;
2257
2258 match rx.await {
2259 Ok(Ok(Some(n))) => n,
2260 Ok(Ok(None)) => return Ok(None),
2261 Ok(Err(_)) | Err(_) => return Ok(None),
2262 }
2263 };
2264
2265 if target_number <= lower_bound {
2266 return Ok(None);
2267 }
2268
2269 let ancestry = if target_number > lower_bound + 1 {
2273 let (tx, rx) = oneshot::channel();
2274
2275 sender
2276 .send_message(ChainApiMessage::Ancestors {
2277 hash: target,
2278 k: (target_number - (lower_bound + 1)) as usize,
2279 response_channel: tx,
2280 })
2281 .await;
2282
2283 match rx.await {
2284 Ok(Ok(a)) => a,
2285 Err(_) | Ok(Err(_)) => return Ok(None),
2286 }
2287 } else {
2288 Vec::new()
2289 };
2290 let ancestry_len = ancestry.len();
2291
2292 let mut block_descriptions = Vec::new();
2293
2294 let mut bits: BitVec<u8, Lsb0> = Default::default();
2295 for (i, block_hash) in std::iter::once(target).chain(ancestry).enumerate() {
2296 let entry = match db.load_block_entry(&block_hash)? {
2300 None => {
2301 let block_number = target_number.saturating_sub(i as u32);
2302 gum::info!(
2303 target: LOG_TARGET,
2304 unknown_number = ?block_number,
2305 unknown_hash = ?block_hash,
2306 "Chain between ({}, {}) and {} not fully known. Forcing vote on {}",
2307 target,
2308 target_number,
2309 lower_bound,
2310 lower_bound,
2311 );
2312 return Ok(None);
2313 },
2314 Some(b) => b,
2315 };
2316
2317 bits.push(entry.is_fully_approved());
2320 if entry.is_fully_approved() {
2321 if all_approved_max.is_none() {
2322 all_approved_max = Some((block_hash, target_number - i as BlockNumber));
2325 }
2326 block_descriptions.push(BlockDescription {
2327 block_hash,
2328 session: entry.session(),
2329 candidates: entry
2330 .candidates()
2331 .iter()
2332 .map(|(_idx, candidate_hash)| *candidate_hash)
2333 .collect(),
2334 });
2335 } else if bits.len() <= ABNORMAL_DEPTH_THRESHOLD {
2336 all_approved_max = None;
2337 block_descriptions.clear();
2338 } else {
2339 all_approved_max = None;
2340 block_descriptions.clear();
2341
2342 let unapproved: Vec<_> = entry.unapproved_candidates().collect();
2343 gum::debug!(
2344 target: LOG_TARGET,
2345 "Block {} is {} blocks deep and has {}/{} candidates unapproved",
2346 block_hash,
2347 bits.len() - 1,
2348 unapproved.len(),
2349 entry.candidates().len(),
2350 );
2351 if ancestry_len >= LOGGING_DEPTH_THRESHOLD && i > ancestry_len - LOGGING_DEPTH_THRESHOLD
2352 {
2353 gum::trace!(
2354 target: LOG_TARGET,
2355 ?block_hash,
2356 "Unapproved candidates at depth {}: {:?}",
2357 bits.len(),
2358 unapproved
2359 )
2360 }
2361 metrics.on_unapproved_candidates_in_unfinalized_chain(unapproved.len());
2362 for candidate_hash in unapproved {
2363 match db.load_candidate_entry(&candidate_hash)? {
2364 None => {
2365 gum::warn!(
2366 target: LOG_TARGET,
2367 ?candidate_hash,
2368 "Missing expected candidate in DB",
2369 );
2370
2371 continue;
2372 },
2373 Some(c_entry) => match c_entry.approval_entry(&block_hash) {
2374 None => {
2375 gum::warn!(
2376 target: LOG_TARGET,
2377 ?candidate_hash,
2378 ?block_hash,
2379 "Missing expected approval entry under candidate.",
2380 );
2381 },
2382 Some(a_entry) => {
2383 let status = || {
2384 let n_assignments = a_entry.n_assignments();
2385
2386 let n_approvals = c_entry
2389 .approvals()
2390 .iter()
2391 .by_vals()
2392 .enumerate()
2393 .filter(|(i, approved)| {
2394 *approved && a_entry.is_assigned(ValidatorIndex(*i as _))
2395 })
2396 .count();
2397
2398 format!(
2399 "{}/{}/{}",
2400 n_assignments,
2401 n_approvals,
2402 a_entry.n_validators(),
2403 )
2404 };
2405
2406 match a_entry.our_assignment() {
2407 None => gum::debug!(
2408 target: LOG_TARGET,
2409 ?candidate_hash,
2410 ?block_hash,
2411 status = %status(),
2412 "no assignment."
2413 ),
2414 Some(a) => {
2415 let tranche = a.tranche();
2416 let triggered = a.triggered();
2417
2418 let next_wakeup =
2419 wakeups.wakeup_for(block_hash, candidate_hash);
2420
2421 let approved =
2422 triggered && { a_entry.local_statements().1.is_some() };
2423
2424 gum::debug!(
2425 target: LOG_TARGET,
2426 ?candidate_hash,
2427 ?block_hash,
2428 tranche,
2429 ?next_wakeup,
2430 status = %status(),
2431 triggered,
2432 approved,
2433 "assigned."
2434 );
2435 },
2436 }
2437 },
2438 },
2439 }
2440 }
2441 }
2442 }
2443
2444 gum::debug!(
2445 target: LOG_TARGET,
2446 "approved blocks {}-[{}]-{}",
2447 target_number,
2448 {
2449 let mut s = String::with_capacity(bits.len());
2453 for (i, bit) in bits.iter().enumerate().take(MAX_TRACING_WINDOW) {
2454 s.push(if *bit { '1' } else { '0' });
2455 if (target_number - i as u32).is_multiple_of(10) && i != bits.len() - 1 {
2456 s.push(' ');
2457 }
2458 }
2459
2460 s
2461 },
2462 if bits.len() > MAX_TRACING_WINDOW {
2463 format!(
2464 "{}... (truncated due to large window)",
2465 target_number - MAX_TRACING_WINDOW as u32 + 1,
2466 )
2467 } else {
2468 format!("{}", lower_bound + 1)
2469 },
2470 );
2471
2472 block_descriptions.reverse();
2475
2476 let all_approved_max =
2477 all_approved_max.map(|(hash, block_number)| HighestApprovedAncestorBlock {
2478 hash,
2479 number: block_number,
2480 descriptions: block_descriptions,
2481 });
2482
2483 Ok(all_approved_max)
2484}
2485
2486fn min_prefer_some<T: std::cmp::Ord>(a: Option<T>, b: Option<T>) -> Option<T> {
2488 match (a, b) {
2489 (None, None) => None,
2490 (None, Some(x)) | (Some(x), None) => Some(x),
2491 (Some(x), Some(y)) => Some(std::cmp::min(x, y)),
2492 }
2493}
2494
2495fn schedule_wakeup_action(
2496 approval_entry: &ApprovalEntry,
2497 block_hash: Hash,
2498 block_number: BlockNumber,
2499 candidate_hash: CandidateHash,
2500 block_tick: Tick,
2501 tick_now: Tick,
2502 required_tranches: RequiredTranches,
2503) -> Option<Action> {
2504 let maybe_action = match required_tranches {
2505 _ if approval_entry.is_approved() => None,
2506 RequiredTranches::All => None,
2507 RequiredTranches::Exact { next_no_show, last_assignment_tick, .. } => {
2508 min_prefer_some(
2511 last_assignment_tick.map(|l| l + APPROVAL_DELAY).filter(|t| t > &tick_now),
2512 next_no_show,
2513 )
2514 .map(|tick| Action::ScheduleWakeup {
2515 block_hash,
2516 block_number,
2517 candidate_hash,
2518 tick,
2519 })
2520 },
2521 RequiredTranches::Pending { considered, next_no_show, clock_drift, .. } => {
2522 let next_non_empty_tranche = {
2526 let next_announced = approval_entry
2527 .tranches()
2528 .iter()
2529 .skip_while(|t| t.tranche() <= considered)
2530 .map(|t| t.tranche())
2531 .next();
2532
2533 let our_untriggered = approval_entry.our_assignment().and_then(|t| {
2534 if !t.triggered() && t.tranche() > considered {
2535 Some(t.tranche())
2536 } else {
2537 None
2538 }
2539 });
2540
2541 min_prefer_some(next_announced, our_untriggered)
2543 .map(|t| t as Tick + block_tick + clock_drift)
2544 };
2545
2546 min_prefer_some(next_non_empty_tranche, next_no_show).map(|tick| {
2547 Action::ScheduleWakeup { block_hash, block_number, candidate_hash, tick }
2548 })
2549 },
2550 };
2551
2552 match maybe_action {
2553 Some(Action::ScheduleWakeup { ref tick, .. }) => gum::trace!(
2554 target: LOG_TARGET,
2555 tick,
2556 ?candidate_hash,
2557 ?block_hash,
2558 block_tick,
2559 "Scheduling next wakeup.",
2560 ),
2561 None => gum::trace!(
2562 target: LOG_TARGET,
2563 ?candidate_hash,
2564 ?block_hash,
2565 block_tick,
2566 "No wakeup needed.",
2567 ),
2568 Some(_) => {}, }
2570
2571 maybe_action
2572}
2573
2574async fn import_assignment<Sender>(
2575 sender: &mut Sender,
2576 state: &State,
2577 db: &mut OverlayedBackend<'_, impl Backend>,
2578 session_info_provider: &mut RuntimeInfo,
2579 checked_assignment: CheckedIndirectAssignment,
2580) -> SubsystemResult<(AssignmentCheckResult, Vec<Action>)>
2581where
2582 Sender: SubsystemSender<RuntimeApiMessage>,
2583{
2584 let tick_now = state.clock.tick_now();
2585 let assignment = checked_assignment.assignment();
2586 let candidate_indices = checked_assignment.candidate_indices();
2587 let tranche = checked_assignment.tranche();
2588
2589 let block_entry = match db.load_block_entry(&assignment.block_hash)? {
2590 Some(b) => b,
2591 None => {
2592 return Ok((
2593 AssignmentCheckResult::Bad(AssignmentCheckError::UnknownBlock(
2594 assignment.block_hash,
2595 )),
2596 Vec::new(),
2597 ))
2598 },
2599 };
2600
2601 let session_info = match get_session_info_by_index(
2602 session_info_provider,
2603 sender,
2604 block_entry.parent_hash(),
2605 block_entry.session(),
2606 )
2607 .await
2608 {
2609 Some(s) => s,
2610 None => {
2611 return Ok((
2612 AssignmentCheckResult::Bad(AssignmentCheckError::UnknownSessionIndex(
2613 block_entry.session(),
2614 )),
2615 Vec::new(),
2616 ))
2617 },
2618 };
2619
2620 let n_cores = session_info.n_cores as usize;
2621
2622 if candidate_indices.len() > n_cores {
2625 gum::debug!(
2626 target: LOG_TARGET,
2627 validator = assignment.validator.0,
2628 n_cores,
2629 candidate_bitfield_len = ?candidate_indices.len(),
2630 "Oversized bitfield",
2631 );
2632
2633 return Ok((
2634 AssignmentCheckResult::Bad(AssignmentCheckError::InvalidBitfield(
2635 candidate_indices.len(),
2636 )),
2637 Vec::new(),
2638 ));
2639 }
2640
2641 let mut claimed_core_indices = Vec::new();
2642 let mut assigned_candidate_hashes = Vec::new();
2643
2644 for candidate_index in candidate_indices.iter_ones() {
2645 let (claimed_core_index, assigned_candidate_hash) =
2646 match block_entry.candidate(candidate_index) {
2647 Some((c, h)) => (*c, *h),
2648 None => {
2649 return Ok((
2650 AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidateIndex(
2651 candidate_index as _,
2652 )),
2653 Vec::new(),
2654 ))
2655 }, };
2657
2658 let mut candidate_entry = match db.load_candidate_entry(&assigned_candidate_hash)? {
2659 Some(c) => c,
2660 None => {
2661 return Ok((
2662 AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidate(
2663 candidate_index as _,
2664 assigned_candidate_hash,
2665 )),
2666 Vec::new(),
2667 ))
2668 }, };
2670
2671 if candidate_entry.approval_entry_mut(&assignment.block_hash).is_none() {
2672 return Ok((
2673 AssignmentCheckResult::Bad(AssignmentCheckError::Internal(
2674 assignment.block_hash,
2675 assigned_candidate_hash,
2676 )),
2677 Vec::new(),
2678 ));
2679 };
2680
2681 claimed_core_indices.push(claimed_core_index);
2682 assigned_candidate_hashes.push(assigned_candidate_hash);
2683 }
2684
2685 if claimed_core_indices.is_empty() {
2687 return Ok((
2688 AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCert(
2689 assignment.validator,
2690 format!("{:?}", InvalidAssignmentReason::NullAssignment),
2691 )),
2692 Vec::new(),
2693 ));
2694 }
2695
2696 let mut actions = Vec::new();
2697 let res = {
2698 let mut is_duplicate = true;
2699 for (assigned_candidate_hash, candidate_index) in
2701 assigned_candidate_hashes.iter().zip(candidate_indices.iter_ones())
2702 {
2703 let mut candidate_entry = match db.load_candidate_entry(&assigned_candidate_hash)? {
2704 Some(c) => c,
2705 None => {
2706 return Ok((
2707 AssignmentCheckResult::Bad(AssignmentCheckError::InvalidCandidate(
2708 candidate_index as _,
2709 *assigned_candidate_hash,
2710 )),
2711 Vec::new(),
2712 ))
2713 },
2714 };
2715
2716 let approval_entry = match candidate_entry.approval_entry_mut(&assignment.block_hash) {
2717 Some(a) => a,
2718 None => {
2719 return Ok((
2720 AssignmentCheckResult::Bad(AssignmentCheckError::Internal(
2721 assignment.block_hash,
2722 *assigned_candidate_hash,
2723 )),
2724 Vec::new(),
2725 ))
2726 },
2727 };
2728
2729 let is_duplicate_for_candidate = approval_entry.is_assigned(assignment.validator);
2730 is_duplicate &= is_duplicate_for_candidate;
2731 approval_entry.import_assignment(
2732 tranche,
2733 assignment.validator,
2734 tick_now,
2735 is_duplicate_for_candidate,
2736 );
2737
2738 if let Some((approval_entry, status)) = state
2741 .approval_status(sender, session_info_provider, &block_entry, &candidate_entry)
2742 .await
2743 {
2744 actions.extend(schedule_wakeup_action(
2745 approval_entry,
2746 block_entry.block_hash(),
2747 block_entry.block_number(),
2748 *assigned_candidate_hash,
2749 status.block_tick,
2750 tick_now,
2751 status.required_tranches,
2752 ));
2753 }
2754
2755 db.write_candidate_entry(candidate_entry.into());
2757 }
2758
2759 if is_duplicate {
2765 AssignmentCheckResult::AcceptedDuplicate
2766 } else if candidate_indices.count_ones() > 1 {
2767 gum::trace!(
2768 target: LOG_TARGET,
2769 validator = assignment.validator.0,
2770 candidate_hashes = ?assigned_candidate_hashes,
2771 assigned_cores = ?claimed_core_indices,
2772 ?tranche,
2773 "Imported assignments for multiple cores.",
2774 );
2775
2776 AssignmentCheckResult::Accepted
2777 } else {
2778 gum::trace!(
2779 target: LOG_TARGET,
2780 validator = assignment.validator.0,
2781 candidate_hashes = ?assigned_candidate_hashes,
2782 assigned_cores = ?claimed_core_indices,
2783 "Imported assignment for a single core.",
2784 );
2785
2786 AssignmentCheckResult::Accepted
2787 }
2788 };
2789
2790 Ok((res, actions))
2791}
2792
2793async fn import_approval<Sender>(
2794 sender: &mut Sender,
2795 state: &mut State,
2796 db: &mut OverlayedBackend<'_, impl Backend>,
2797 session_info_provider: &mut RuntimeInfo,
2798 metrics: &Metrics,
2799 approval: CheckedIndirectSignedApprovalVote,
2800 wakeups: &Wakeups,
2801) -> SubsystemResult<(Vec<Action>, ApprovalCheckResult)>
2802where
2803 Sender: SubsystemSender<RuntimeApiMessage>,
2804{
2805 macro_rules! respond_early {
2806 ($e: expr) => {{
2807 return Ok((Vec::new(), $e));
2808 }};
2809 }
2810
2811 let block_entry = match db.load_block_entry(&approval.block_hash)? {
2812 Some(b) => b,
2813 None => {
2814 respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::UnknownBlock(
2815 approval.block_hash
2816 ),))
2817 },
2818 };
2819
2820 let approved_candidates_info: Result<Vec<(CandidateIndex, CandidateHash)>, ApprovalCheckError> =
2821 approval
2822 .candidate_indices
2823 .iter_ones()
2824 .map(|candidate_index| {
2825 block_entry
2826 .candidate(candidate_index)
2827 .ok_or(ApprovalCheckError::InvalidCandidateIndex(candidate_index as _))
2828 .map(|candidate| (candidate_index as _, candidate.1))
2829 })
2830 .collect();
2831
2832 let approved_candidates_info = match approved_candidates_info {
2833 Ok(approved_candidates_info) => approved_candidates_info,
2834 Err(err) => {
2835 respond_early!(ApprovalCheckResult::Bad(err))
2836 },
2837 };
2838
2839 gum::trace!(
2840 target: LOG_TARGET,
2841 "Received approval for num_candidates {:}",
2842 approval.candidate_indices.count_ones()
2843 );
2844
2845 let mut actions = Vec::new();
2846 for (approval_candidate_index, approved_candidate_hash) in approved_candidates_info {
2847 let block_entry = match db.load_block_entry(&approval.block_hash)? {
2848 Some(b) => b,
2849 None => {
2850 respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::UnknownBlock(
2851 approval.block_hash
2852 ),))
2853 },
2854 };
2855
2856 let candidate_entry = match db.load_candidate_entry(&approved_candidate_hash)? {
2857 Some(c) => c,
2858 None => {
2859 respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::InvalidCandidate(
2860 approval_candidate_index,
2861 approved_candidate_hash
2862 ),))
2863 },
2864 };
2865
2866 match candidate_entry.approval_entry(&approval.block_hash) {
2868 None => {
2869 respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::Internal(
2870 approval.block_hash,
2871 approved_candidate_hash
2872 ),))
2873 },
2874 Some(e) if !e.is_assigned(approval.validator) => {
2875 respond_early!(ApprovalCheckResult::Bad(ApprovalCheckError::NoAssignment(
2876 approval.validator
2877 ),))
2878 },
2879 _ => {},
2880 }
2881
2882 gum::trace!(
2883 target: LOG_TARGET,
2884 validator_index = approval.validator.0,
2885 candidate_hash = ?approved_candidate_hash,
2886 para_id = ?candidate_entry.candidate_receipt().descriptor.para_id(),
2887 "Importing approval vote",
2888 );
2889
2890 let new_actions = advance_approval_state(
2891 sender,
2892 state,
2893 db,
2894 session_info_provider,
2895 &metrics,
2896 block_entry,
2897 approved_candidate_hash,
2898 candidate_entry,
2899 ApprovalStateTransition::RemoteApproval(approval.validator),
2900 wakeups,
2901 )
2902 .await;
2903 actions.extend(new_actions);
2904 }
2905
2906 Ok((actions, ApprovalCheckResult::Accepted))
2908}
2909
2910#[derive(Debug)]
2911enum ApprovalStateTransition {
2912 RemoteApproval(ValidatorIndex),
2913 LocalApproval(ValidatorIndex),
2914 WakeupProcessed,
2915}
2916
2917impl ApprovalStateTransition {
2918 fn validator_index(&self) -> Option<ValidatorIndex> {
2919 match *self {
2920 ApprovalStateTransition::RemoteApproval(v) |
2921 ApprovalStateTransition::LocalApproval(v) => Some(v),
2922 ApprovalStateTransition::WakeupProcessed => None,
2923 }
2924 }
2925
2926 fn is_local_approval(&self) -> bool {
2927 match *self {
2928 ApprovalStateTransition::RemoteApproval(_) => false,
2929 ApprovalStateTransition::LocalApproval(_) => true,
2930 ApprovalStateTransition::WakeupProcessed => false,
2931 }
2932 }
2933
2934 fn is_remote_approval(&self) -> bool {
2935 matches!(*self, ApprovalStateTransition::RemoteApproval(_))
2936 }
2937}
2938
2939async fn advance_approval_state<Sender>(
2944 sender: &mut Sender,
2945 state: &mut State,
2946 db: &mut OverlayedBackend<'_, impl Backend>,
2947 session_info_provider: &mut RuntimeInfo,
2948 metrics: &Metrics,
2949 mut block_entry: BlockEntry,
2950 candidate_hash: CandidateHash,
2951 mut candidate_entry: CandidateEntry,
2952 transition: ApprovalStateTransition,
2953 wakeups: &Wakeups,
2954) -> Vec<Action>
2955where
2956 Sender: SubsystemSender<RuntimeApiMessage>,
2957{
2958 let validator_index = transition.validator_index();
2959
2960 let already_approved_by = validator_index.as_ref().map(|v| candidate_entry.mark_approval(*v));
2961 let candidate_approved_in_block = block_entry.is_candidate_approved(&candidate_hash);
2962
2963 if !transition.is_local_approval() {
2973 if candidate_approved_in_block {
2977 return Vec::new();
2978 }
2979 }
2980
2981 let mut actions = Vec::new();
2982 let block_hash = block_entry.block_hash();
2983 let block_number = block_entry.block_number();
2984 let session_index = block_entry.session();
2985 let para_id = candidate_entry.candidate_receipt().descriptor().para_id();
2986 let tick_now = state.clock.tick_now();
2987
2988 let (is_approved, status) = if let Some((approval_entry, status)) = state
2989 .approval_status(sender, session_info_provider, &block_entry, &candidate_entry)
2990 .await
2991 {
2992 let check = approval_checking::check_approval(
2993 &candidate_entry,
2994 approval_entry,
2995 status.required_tranches.clone(),
2996 );
2997 state.observe_assignment_gathering_status(
2998 &metrics,
2999 &status.required_tranches,
3000 block_hash,
3001 block_entry.block_number(),
3002 candidate_hash,
3003 );
3004
3005 let is_approved = check.is_approved(tick_now.saturating_sub(APPROVAL_DELAY));
3009 if status.last_no_shows != 0 {
3010 metrics.on_observed_no_shows(status.last_no_shows);
3011 gum::trace!(
3012 target: LOG_TARGET,
3013 ?candidate_hash,
3014 ?block_hash,
3015 last_no_shows = ?status.last_no_shows,
3016 "Observed no_shows",
3017 );
3018 }
3019 if is_approved {
3020 gum::trace!(
3021 target: LOG_TARGET,
3022 ?candidate_hash,
3023 ?block_hash,
3024 "Candidate approved under block.",
3025 );
3026
3027 let no_shows = check.known_no_shows();
3028
3029 let was_block_approved = block_entry.is_fully_approved();
3030 block_entry.mark_approved_by_hash(&candidate_hash);
3031 let is_block_approved = block_entry.is_fully_approved();
3032
3033 if no_shows != 0 {
3034 metrics.on_no_shows(no_shows);
3035 }
3036 if check == Check::ApprovedOneThird {
3037 metrics.on_approved_by_one_third()
3041 }
3042
3043 metrics.on_candidate_approved(status.tranche_now as _);
3044
3045 if is_block_approved && !was_block_approved {
3046 metrics.on_block_approved(status.tranche_now as _);
3047 actions.push(Action::NoteApprovedInChainSelection(block_hash));
3048 }
3049
3050 db.write_block_entry(block_entry.into());
3051 } else if transition.is_local_approval() {
3052 db.write_block_entry(block_entry.into());
3055 }
3056
3057 (is_approved, status)
3058 } else {
3059 gum::warn!(
3060 target: LOG_TARGET,
3061 ?candidate_hash,
3062 ?block_hash,
3063 ?validator_index,
3064 "No approval entry for approval under block",
3065 );
3066
3067 return Vec::new();
3068 };
3069
3070 {
3071 let approval_entry = candidate_entry
3072 .approval_entry_mut(&block_hash)
3073 .expect("Approval entry just fetched; qed");
3074
3075 let was_approved = approval_entry.is_approved();
3076 let newly_approved = is_approved && !was_approved;
3077
3078 if is_approved {
3079 approval_entry.mark_approved();
3080 }
3081 if newly_approved {
3082 state.record_no_shows(session_index, para_id.into(), &status.no_show_validators);
3083 }
3084 actions.extend(schedule_wakeup_action(
3085 &approval_entry,
3086 block_hash,
3087 block_number,
3088 candidate_hash,
3089 status.block_tick,
3090 tick_now,
3091 status.required_tranches,
3092 ));
3093
3094 if is_approved && transition.is_remote_approval() {
3095 for (fork_block_hash, fork_approval_entry) in candidate_entry
3098 .block_assignments
3099 .iter()
3100 .filter(|(hash, _)| **hash != block_hash)
3101 {
3102 let assigned_on_fork_block = validator_index
3103 .as_ref()
3104 .map(|validator_index| fork_approval_entry.is_assigned(*validator_index))
3105 .unwrap_or_default();
3106 if wakeups.wakeup_for(*fork_block_hash, candidate_hash).is_none() &&
3107 !fork_approval_entry.is_approved() &&
3108 assigned_on_fork_block
3109 {
3110 let fork_block_entry = db.load_block_entry(fork_block_hash);
3111 if let Ok(Some(fork_block_entry)) = fork_block_entry {
3112 actions.push(Action::ScheduleWakeup {
3113 block_hash: *fork_block_hash,
3114 block_number: fork_block_entry.block_number(),
3115 candidate_hash,
3116 tick: tick_now + 1,
3119 })
3120 } else {
3121 gum::debug!(
3122 target: LOG_TARGET,
3123 ?fork_block_entry,
3124 ?fork_block_hash,
3125 "Failed to load block entry"
3126 )
3127 }
3128 }
3129 }
3130 }
3131 if transition.is_local_approval() || newly_approved || !already_approved_by.unwrap_or(true)
3140 {
3141 db.write_candidate_entry(candidate_entry);
3143 }
3144 }
3145
3146 actions
3147}
3148
3149fn should_trigger_assignment(
3150 approval_entry: &ApprovalEntry,
3151 candidate_entry: &CandidateEntry,
3152 required_tranches: RequiredTranches,
3153 tranche_now: DelayTranche,
3154) -> bool {
3155 match approval_entry.our_assignment() {
3156 None => false,
3157 Some(ref assignment) if assignment.triggered() => false,
3158 Some(ref assignment) if assignment.tranche() == 0 => true,
3159 Some(ref assignment) => {
3160 match required_tranches {
3161 RequiredTranches::All => !approval_checking::check_approval(
3162 &candidate_entry,
3163 &approval_entry,
3164 RequiredTranches::All,
3165 )
3166 .is_approved(Tick::max_value()),
3168 RequiredTranches::Pending { maximum_broadcast, clock_drift, .. } => {
3169 let drifted_tranche_now =
3170 tranche_now.saturating_sub(clock_drift as DelayTranche);
3171 assignment.tranche() <= maximum_broadcast &&
3172 assignment.tranche() <= drifted_tranche_now
3173 },
3174 RequiredTranches::Exact { .. } => {
3175 false
3177 },
3178 }
3179 },
3180 }
3181}
3182
3183async fn process_wakeup<Sender: SubsystemSender<RuntimeApiMessage>>(
3184 sender: &mut Sender,
3185 state: &mut State,
3186 db: &mut OverlayedBackend<'_, impl Backend>,
3187 session_info_provider: &mut RuntimeInfo,
3188 relay_block: Hash,
3189 candidate_hash: CandidateHash,
3190 metrics: &Metrics,
3191 wakeups: &Wakeups,
3192) -> SubsystemResult<Vec<Action>> {
3193 let block_entry = db.load_block_entry(&relay_block)?;
3194 let candidate_entry = db.load_candidate_entry(&candidate_hash)?;
3195
3196 let (mut block_entry, mut candidate_entry) = match (block_entry, candidate_entry) {
3198 (Some(b), Some(c)) => (b, c),
3199 _ => return Ok(Vec::new()),
3200 };
3201
3202 let (no_show_slots, needed_approvals) = match get_session_info_by_index(
3203 session_info_provider,
3204 sender,
3205 block_entry.block_hash(),
3206 block_entry.session(),
3207 )
3208 .await
3209 {
3210 Some(i) => (i.no_show_slots, i.needed_approvals),
3211 None => return Ok(Vec::new()),
3212 };
3213
3214 let block_tick = slot_number_to_tick(state.slot_duration_millis, block_entry.slot());
3215 let no_show_duration =
3216 slot_number_to_tick(state.slot_duration_millis, Slot::from(u64::from(no_show_slots)));
3217 let tranche_now = state.clock.tranche_now(state.slot_duration_millis, block_entry.slot());
3218
3219 gum::trace!(
3220 target: LOG_TARGET,
3221 tranche = tranche_now,
3222 ?candidate_hash,
3223 block_hash = ?relay_block,
3224 "Processing wakeup",
3225 );
3226
3227 let (should_trigger, backing_group) = {
3228 let approval_entry = match candidate_entry.approval_entry(&relay_block) {
3229 Some(e) => e,
3230 None => return Ok(Vec::new()),
3231 };
3232
3233 let tranches_to_approve = approval_checking::tranches_to_approve(
3234 &approval_entry,
3235 candidate_entry.approvals(),
3236 tranche_now,
3237 block_tick,
3238 no_show_duration,
3239 needed_approvals as _,
3240 );
3241
3242 let should_trigger = should_trigger_assignment(
3243 &approval_entry,
3244 &candidate_entry,
3245 tranches_to_approve.required_tranches,
3246 tranche_now,
3247 );
3248
3249 (should_trigger, approval_entry.backing_group())
3250 };
3251
3252 gum::trace!(target: LOG_TARGET, "Wakeup processed. Should trigger: {}", should_trigger);
3253
3254 let mut actions = Vec::new();
3255 let candidate_receipt = candidate_entry.candidate_receipt().clone();
3256
3257 let maybe_cert = if should_trigger {
3258 let maybe_cert = {
3259 let approval_entry = candidate_entry
3260 .approval_entry_mut(&relay_block)
3261 .expect("should_trigger only true if this fetched earlier; qed");
3262
3263 approval_entry.trigger_our_assignment(state.clock.tick_now())
3264 };
3265
3266 db.write_candidate_entry(candidate_entry.clone());
3267
3268 maybe_cert
3269 } else {
3270 None
3271 };
3272
3273 if let Some((cert, val_index, tranche)) = maybe_cert {
3274 let indirect_cert =
3275 IndirectAssignmentCertV2 { block_hash: relay_block, validator: val_index, cert };
3276
3277 gum::trace!(
3278 target: LOG_TARGET,
3279 ?candidate_hash,
3280 para_id = ?candidate_receipt.descriptor.para_id(),
3281 block_hash = ?relay_block,
3282 "Launching approval work.",
3283 );
3284
3285 let candidate_core_index = block_entry
3286 .candidates()
3287 .iter()
3288 .find_map(|(core_index, h)| (h == &candidate_hash).then_some(*core_index));
3289
3290 let claimed_core_indices = get_assignment_core_indices(&indirect_cert.cert.kind);
3291 match cores_to_candidate_indices(&claimed_core_indices, &block_entry) {
3292 Ok(claimed_candidate_indices) => {
3293 let distribute_assignment = if claimed_candidate_indices.count_ones() > 1 {
3295 !block_entry.mark_assignment_distributed(claimed_candidate_indices.clone())
3296 } else {
3297 true
3298 };
3299 db.write_block_entry(block_entry.clone());
3300 actions.push(Action::LaunchApproval {
3301 claimed_candidate_indices,
3302 candidate_hash,
3303 indirect_cert,
3304 assignment_tranche: tranche,
3305 relay_block_hash: relay_block,
3306 session: block_entry.session(),
3307 candidate: candidate_receipt,
3308 backing_group,
3309 distribute_assignment,
3310 core_index: candidate_core_index,
3311 });
3312 },
3313 Err(err) => {
3314 gum::warn!(
3317 target: LOG_TARGET,
3318 block_hash = ?relay_block,
3319 ?err,
3320 "Failed to create assignment bitfield"
3321 );
3322 },
3323 };
3324 }
3325 actions.extend(
3332 advance_approval_state(
3333 sender,
3334 state,
3335 db,
3336 session_info_provider,
3337 metrics,
3338 block_entry,
3339 candidate_hash,
3340 candidate_entry,
3341 ApprovalStateTransition::WakeupProcessed,
3342 wakeups,
3343 )
3344 .await,
3345 );
3346
3347 Ok(actions)
3348}
3349
3350#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
3354async fn launch_approval<
3355 Sender: SubsystemSender<RuntimeApiMessage>
3356 + SubsystemSender<AvailabilityRecoveryMessage>
3357 + SubsystemSender<DisputeCoordinatorMessage>
3358 + SubsystemSender<CandidateValidationMessage>,
3359>(
3360 mut sender: Sender,
3361 spawn_handle: Arc<dyn overseer::gen::Spawner + 'static>,
3362 metrics: Metrics,
3363 session_index: SessionIndex,
3364 candidate: CandidateReceipt,
3365 validator_index: ValidatorIndex,
3366 block_hash: Hash,
3367 backing_group: GroupIndex,
3368 core_index: Option<CoreIndex>,
3369 retry: RetryApprovalInfo,
3370) -> SubsystemResult<RemoteHandle<ApprovalState>> {
3371 let (a_tx, a_rx) = oneshot::channel();
3372 let (code_tx, code_rx) = oneshot::channel();
3373
3374 struct StaleGuard(Option<Metrics>);
3378
3379 impl StaleGuard {
3380 fn take(mut self) -> Metrics {
3381 self.0.take().expect(
3382 "
3383 consumed after take; so this cannot be called twice; \
3384 nothing in this function reaches into the struct to avoid this API; \
3385 qed
3386 ",
3387 )
3388 }
3389 }
3390
3391 impl Drop for StaleGuard {
3392 fn drop(&mut self) {
3393 if let Some(metrics) = self.0.as_ref() {
3394 metrics.on_approval_stale();
3395 }
3396 }
3397 }
3398
3399 let candidate_hash = candidate.hash();
3400 let para_id = candidate.descriptor.para_id();
3401 let mut next_retry = None;
3402 gum::trace!(target: LOG_TARGET, ?candidate_hash, ?para_id, "Recovering data.");
3403
3404 let timer = metrics.time_recover_and_approve();
3405 sender
3406 .send_message(AvailabilityRecoveryMessage::RecoverAvailableData(
3407 candidate.clone(),
3408 session_index,
3409 Some(backing_group),
3410 core_index,
3411 a_tx,
3412 ))
3413 .await;
3414
3415 sender
3416 .send_message(RuntimeApiMessage::Request(
3417 block_hash,
3418 RuntimeApiRequest::ValidationCodeByHash(
3419 candidate.descriptor.validation_code_hash(),
3420 code_tx,
3421 ),
3422 ))
3423 .await;
3424
3425 let candidate = candidate.clone();
3426 let metrics_guard = StaleGuard(Some(metrics));
3427 let background = async move {
3428 let _timer = timer;
3430 let available_data = match a_rx.await {
3431 Err(_) => return ApprovalState::failed(validator_index, candidate_hash),
3432 Ok(Ok(a)) => a,
3433 Ok(Err(e)) => {
3434 match &e {
3435 &RecoveryError::Unavailable => {
3436 gum::warn!(
3437 target: LOG_TARGET,
3438 ?para_id,
3439 ?candidate_hash,
3440 attempts_remaining = retry.attempts_remaining,
3441 "Data unavailable for candidate {:?}",
3442 (candidate_hash, candidate.descriptor.para_id()),
3443 );
3444 if retry.attempts_remaining > 0 {
3448 Delay::new(retry.backoff).await;
3449 next_retry = Some(RetryApprovalInfo {
3450 candidate,
3451 backing_group,
3452 core_index,
3453 session_index,
3454 attempts_remaining: retry.attempts_remaining - 1,
3455 backoff: retry.backoff,
3456 });
3457 } else {
3458 next_retry = None;
3459 }
3460 metrics_guard.take().on_approval_unavailable();
3461 },
3462 &RecoveryError::ChannelClosed => {
3463 gum::warn!(
3464 target: LOG_TARGET,
3465 ?para_id,
3466 ?candidate_hash,
3467 "Channel closed while recovering data for candidate {:?}",
3468 (candidate_hash, candidate.descriptor.para_id()),
3469 );
3470 metrics_guard.take().on_approval_unavailable();
3472 },
3473 &RecoveryError::Invalid => {
3474 gum::warn!(
3475 target: LOG_TARGET,
3476 ?para_id,
3477 ?candidate_hash,
3478 "Data recovery invalid for candidate {:?}",
3479 (candidate_hash, candidate.descriptor.para_id()),
3480 );
3481 issue_local_invalid_statement(
3482 &mut sender,
3483 session_index,
3484 candidate_hash,
3485 candidate.clone(),
3486 );
3487 metrics_guard.take().on_approval_invalid();
3488 },
3489 }
3490 return ApprovalState::failed_with_retry(
3491 validator_index,
3492 candidate_hash,
3493 next_retry,
3494 );
3495 },
3496 };
3497
3498 let validation_code = match code_rx.await {
3499 Err(_) => return ApprovalState::failed(validator_index, candidate_hash),
3500 Ok(Err(_)) => return ApprovalState::failed(validator_index, candidate_hash),
3501 Ok(Ok(Some(code))) => code,
3502 Ok(Ok(None)) => {
3503 gum::warn!(
3504 target: LOG_TARGET,
3505 "Validation code unavailable for block {:?} in the state of block {:?} (a recent descendant)",
3506 candidate.descriptor.relay_parent(),
3507 block_hash,
3508 );
3509
3510 metrics_guard.take().on_approval_unavailable();
3513 return ApprovalState::failed(validator_index, candidate_hash);
3514 },
3515 };
3516
3517 let (val_tx, val_rx) = oneshot::channel();
3518 sender
3519 .send_message(CandidateValidationMessage::ValidateFromExhaustive {
3520 validation_data: available_data.validation_data,
3521 validation_code,
3522 candidate_receipt: candidate.clone(),
3523 pov: available_data.pov,
3524 scheduling_session_index: session_index,
3525 exec_kind: PvfExecKind::Approval,
3526 response_sender: val_tx,
3527 })
3528 .await;
3529
3530 match val_rx.await {
3531 Err(_) => return ApprovalState::failed(validator_index, candidate_hash),
3532 Ok(Ok(ValidationResult::Valid(_, _))) => {
3533 gum::trace!(target: LOG_TARGET, ?candidate_hash, ?para_id, "Candidate Valid");
3537
3538 let _ = metrics_guard.take();
3539 return ApprovalState::approved(validator_index, candidate_hash);
3540 },
3541 Ok(Ok(ValidationResult::Invalid(reason))) => {
3542 gum::warn!(
3543 target: LOG_TARGET,
3544 ?reason,
3545 ?candidate_hash,
3546 ?para_id,
3547 "Detected invalid candidate as an approval checker.",
3548 );
3549
3550 issue_local_invalid_statement(
3551 &mut sender,
3552 session_index,
3553 candidate_hash,
3554 candidate.clone(),
3555 );
3556 metrics_guard.take().on_approval_invalid();
3557 return ApprovalState::failed(validator_index, candidate_hash);
3558 },
3559 Ok(Err(e)) => {
3560 gum::error!(
3561 target: LOG_TARGET,
3562 err = ?e,
3563 ?candidate_hash,
3564 ?para_id,
3565 "Failed to validate candidate due to internal error",
3566 );
3567 metrics_guard.take().on_approval_error();
3568 return ApprovalState::failed(validator_index, candidate_hash);
3569 },
3570 }
3571 };
3572 let (background, remote_handle) = background.remote_handle();
3573 spawn_handle.spawn("approval-checks", Some("approval-voting-subsystem"), Box::pin(background));
3574 Ok(remote_handle)
3575}
3576
3577#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
3580async fn issue_approval<
3581 Sender: SubsystemSender<RuntimeApiMessage>,
3582 ADSender: SubsystemSender<ApprovalDistributionMessage>,
3583>(
3584 sender: &mut Sender,
3585 approval_voting_sender: &mut ADSender,
3586 state: &mut State,
3587 db: &mut OverlayedBackend<'_, impl Backend>,
3588 session_info_provider: &mut RuntimeInfo,
3589 metrics: &Metrics,
3590 candidate_hash: CandidateHash,
3591 delayed_approvals_timers: &mut DelayedApprovalTimer,
3592 ApprovalVoteRequest { validator_index, block_hash }: ApprovalVoteRequest,
3593 wakeups: &Wakeups,
3594) -> SubsystemResult<Vec<Action>> {
3595 let mut block_entry = match db.load_block_entry(&block_hash)? {
3596 Some(b) => b,
3597 None => {
3598 metrics.on_approval_stale();
3600 return Ok(Vec::new());
3601 },
3602 };
3603
3604 let candidate_index = match block_entry.candidates().iter().position(|e| e.1 == candidate_hash)
3605 {
3606 None => {
3607 gum::warn!(
3608 target: LOG_TARGET,
3609 "Candidate hash {} is not present in the block entry's candidates for relay block {}",
3610 candidate_hash,
3611 block_entry.parent_hash(),
3612 );
3613
3614 metrics.on_approval_error();
3615 return Ok(Vec::new());
3616 },
3617 Some(idx) => idx,
3618 };
3619
3620 let candidate_hash = match block_entry.candidate(candidate_index as usize) {
3621 Some((_, h)) => *h,
3622 None => {
3623 gum::warn!(
3624 target: LOG_TARGET,
3625 "Received malformed request to approve out-of-bounds candidate index {} included at block {:?}",
3626 candidate_index,
3627 block_hash,
3628 );
3629
3630 metrics.on_approval_error();
3631 return Ok(Vec::new());
3632 },
3633 };
3634
3635 let candidate_entry = match db.load_candidate_entry(&candidate_hash)? {
3636 Some(c) => c,
3637 None => {
3638 gum::warn!(
3639 target: LOG_TARGET,
3640 "Missing entry for candidate index {} included at block {:?}",
3641 candidate_index,
3642 block_hash,
3643 );
3644
3645 metrics.on_approval_error();
3646 return Ok(Vec::new());
3647 },
3648 };
3649
3650 let session_info = match get_session_info_by_index(
3651 session_info_provider,
3652 sender,
3653 block_entry.parent_hash(),
3654 block_entry.session(),
3655 )
3656 .await
3657 {
3658 Some(s) => s,
3659 None => return Ok(Vec::new()),
3660 };
3661
3662 if block_entry
3663 .defer_candidate_signature(
3664 candidate_index as _,
3665 candidate_hash,
3666 compute_delayed_approval_sending_tick(
3667 state,
3668 &block_entry,
3669 &candidate_entry,
3670 session_info,
3671 &metrics,
3672 ),
3673 )
3674 .is_some()
3675 {
3676 gum::error!(
3677 target: LOG_TARGET,
3678 ?candidate_hash,
3679 ?block_hash,
3680 validator_index = validator_index.0,
3681 "Possible bug, we shouldn't have to defer a candidate more than once",
3682 );
3683 }
3684
3685 gum::debug!(
3686 target: LOG_TARGET,
3687 ?candidate_hash,
3688 ?block_hash,
3689 validator_index = validator_index.0,
3690 "Ready to issue approval vote",
3691 );
3692
3693 let actions = advance_approval_state(
3694 sender,
3695 state,
3696 db,
3697 session_info_provider,
3698 metrics,
3699 block_entry,
3700 candidate_hash,
3701 candidate_entry,
3702 ApprovalStateTransition::LocalApproval(validator_index as _),
3703 wakeups,
3704 )
3705 .await;
3706
3707 if let Some(next_wakeup) = maybe_create_signature(
3708 db,
3709 session_info_provider,
3710 state,
3711 sender,
3712 approval_voting_sender,
3713 block_hash,
3714 validator_index,
3715 metrics,
3716 )
3717 .await?
3718 {
3719 delayed_approvals_timers.maybe_arm_timer(
3720 next_wakeup,
3721 state.clock.as_ref(),
3722 block_hash,
3723 validator_index,
3724 );
3725 }
3726 Ok(actions)
3727}
3728
3729#[overseer::contextbounds(ApprovalVoting, prefix = self::overseer)]
3731async fn maybe_create_signature<
3732 Sender: SubsystemSender<RuntimeApiMessage>,
3733 ADSender: SubsystemSender<ApprovalDistributionMessage>,
3734>(
3735 db: &mut OverlayedBackend<'_, impl Backend>,
3736 session_info_provider: &mut RuntimeInfo,
3737 state: &State,
3738 sender: &mut Sender,
3739 approval_voting_sender: &mut ADSender,
3740 block_hash: Hash,
3741 validator_index: ValidatorIndex,
3742 metrics: &Metrics,
3743) -> SubsystemResult<Option<Tick>> {
3744 let mut block_entry = match db.load_block_entry(&block_hash)? {
3745 Some(b) => b,
3746 None => {
3747 metrics.on_approval_stale();
3749 gum::debug!(
3750 target: LOG_TARGET,
3751 "Could not find block that needs signature {:}", block_hash
3752 );
3753 return Ok(None);
3754 },
3755 };
3756
3757 let approval_params = session_info_provider
3758 .get_session_info_by_index(sender, block_hash, block_entry.session())
3759 .await
3760 .map(|info| info.approval_voting_params)
3761 .unwrap_or_default();
3762
3763 gum::trace!(
3764 target: LOG_TARGET,
3765 "Candidates pending signatures {:}", block_entry.num_candidates_pending_signature()
3766 );
3767 let tick_now = state.clock.tick_now();
3768
3769 let (candidates_to_sign, sign_no_later_then) = block_entry
3770 .get_candidates_that_need_signature(tick_now, approval_params.max_approval_coalesce_count);
3771
3772 let (candidates_hashes, candidates_indices) = match candidates_to_sign {
3773 Some(candidates_to_sign) => candidates_to_sign,
3774 None => return Ok(sign_no_later_then),
3775 };
3776
3777 let session_info = match get_session_info_by_index(
3778 session_info_provider,
3779 sender,
3780 block_entry.parent_hash(),
3781 block_entry.session(),
3782 )
3783 .await
3784 {
3785 Some(s) => s,
3786 None => {
3787 metrics.on_approval_error();
3788 gum::error!(
3789 target: LOG_TARGET,
3790 "Could not retrieve the session"
3791 );
3792 return Ok(None);
3793 },
3794 };
3795
3796 let validator_pubkey = match session_info.validators.get(validator_index) {
3797 Some(p) => p,
3798 None => {
3799 gum::error!(
3800 target: LOG_TARGET,
3801 "Validator index {} out of bounds in session {}",
3802 validator_index.0,
3803 block_entry.session(),
3804 );
3805
3806 metrics.on_approval_error();
3807 return Ok(None);
3808 },
3809 };
3810
3811 let signature = match sign_approval(
3812 &state.keystore,
3813 &validator_pubkey,
3814 &candidates_hashes,
3815 block_entry.session(),
3816 ) {
3817 Some(sig) => sig,
3818 None => {
3819 gum::error!(
3820 target: LOG_TARGET,
3821 validator_index = ?validator_index,
3822 session = ?block_entry.session(),
3823 "Could not issue approval signature. Assignment key present but not validator key?",
3824 );
3825
3826 metrics.on_approval_error();
3827 return Ok(None);
3828 },
3829 };
3830 metrics.on_approval_coalesce(candidates_hashes.len() as u32);
3831
3832 let candidate_entries = candidates_hashes
3833 .iter()
3834 .map(|candidate_hash| db.load_candidate_entry(candidate_hash))
3835 .collect::<SubsystemResult<Vec<Option<CandidateEntry>>>>()?;
3836
3837 for mut candidate_entry in candidate_entries {
3838 let approval_entry = candidate_entry.as_mut().and_then(|candidate_entry| {
3839 candidate_entry.approval_entry_mut(&block_entry.block_hash())
3840 });
3841
3842 match approval_entry {
3843 Some(approval_entry) => approval_entry.import_approval_sig(OurApproval {
3844 signature: signature.clone(),
3845 signed_candidates_indices: candidates_indices.clone(),
3846 }),
3847 None => {
3848 gum::error!(
3849 target: LOG_TARGET,
3850 candidate_entry = ?candidate_entry,
3851 "Candidate scheduled for signing approval entry should not be None"
3852 );
3853 },
3854 };
3855 candidate_entry.map(|candidate_entry| db.write_candidate_entry(candidate_entry));
3856 }
3857
3858 metrics.on_approval_produced();
3859
3860 approval_voting_sender.send_unbounded_message(ApprovalDistributionMessage::DistributeApproval(
3861 IndirectSignedApprovalVoteV2 {
3862 block_hash: block_entry.block_hash(),
3863 candidate_indices: candidates_indices,
3864 validator: validator_index,
3865 signature,
3866 },
3867 ));
3868
3869 gum::trace!(
3870 target: LOG_TARGET,
3871 ?block_hash,
3872 signed_candidates = ?block_entry.num_candidates_pending_signature(),
3873 "Issue approval votes",
3874 );
3875 block_entry.issued_approval();
3876 db.write_block_entry(block_entry.into());
3877 Ok(None)
3878}
3879
3880fn sign_approval(
3882 keystore: &LocalKeystore,
3883 public: &ValidatorId,
3884 candidate_hashes: &[CandidateHash],
3885 session_index: SessionIndex,
3886) -> Option<ValidatorSignature> {
3887 let key = keystore.key_pair::<ValidatorPair>(public).ok().flatten()?;
3888
3889 let payload = ApprovalVoteMultipleCandidates(candidate_hashes).signing_payload(session_index);
3890
3891 Some(key.sign(&payload[..]))
3892}
3893
3894fn issue_local_invalid_statement<Sender>(
3896 sender: &mut Sender,
3897 session_index: SessionIndex,
3898 candidate_hash: CandidateHash,
3899 candidate: CandidateReceipt,
3900) where
3901 Sender: SubsystemSender<DisputeCoordinatorMessage>,
3902{
3903 sender.send_unbounded_message(DisputeCoordinatorMessage::IssueLocalStatement(
3913 session_index,
3914 candidate_hash,
3915 candidate.clone(),
3916 false,
3917 ));
3918}
3919
3920fn compute_delayed_approval_sending_tick(
3922 state: &State,
3923 block_entry: &BlockEntry,
3924 candidate_entry: &CandidateEntry,
3925 session_info: &SessionInfo,
3926 metrics: &Metrics,
3927) -> Tick {
3928 let current_block_tick = slot_number_to_tick(state.slot_duration_millis, block_entry.slot());
3929 let assignment_tranche = candidate_entry
3930 .approval_entry(&block_entry.block_hash())
3931 .and_then(|approval_entry| approval_entry.our_assignment())
3932 .map(|our_assignment| our_assignment.tranche())
3933 .unwrap_or_default();
3934
3935 let assignment_triggered_tick = current_block_tick + assignment_tranche as Tick;
3936
3937 let no_show_duration_ticks = slot_number_to_tick(
3938 state.slot_duration_millis,
3939 Slot::from(u64::from(session_info.no_show_slots)),
3940 );
3941 let tick_now = state.clock.tick_now();
3942
3943 let sign_no_later_than = min(
3944 tick_now + MAX_APPROVAL_COALESCE_WAIT_TICKS as Tick,
3945 assignment_triggered_tick + no_show_duration_ticks / 2,
3949 );
3950
3951 metrics.on_delayed_approval(sign_no_later_than.checked_sub(tick_now).unwrap_or_default());
3952 sign_no_later_than
3953}