1#![cfg(feature = "full-node")]
37
38use super::{HeaderProvider, HeaderProviderProvider};
39use futures::channel::oneshot;
40use polkadot_node_primitives::MAX_FINALITY_LAG as PRIMITIVES_MAX_FINALITY_LAG;
41use polkadot_node_subsystem::messages::{
42 ApprovalVotingParallelMessage, ChainSelectionMessage, DisputeCoordinatorMessage,
43 HighestApprovedAncestorBlock,
44};
45use polkadot_node_subsystem_util::metrics::{self, prometheus};
46use polkadot_overseer::{AllMessages, Handle, PriorityLevel};
47use polkadot_primitives::{Block as PolkadotBlock, BlockNumber, Hash, Header as PolkadotHeader};
48use sp_consensus::{Error as ConsensusError, SelectChain};
49use std::sync::Arc;
50
51pub use sc_service::SpawnTaskHandle;
52
53const MAX_FINALITY_LAG: polkadot_primitives::BlockNumber = PRIMITIVES_MAX_FINALITY_LAG;
60
61const LOG_TARGET: &str = "parachain::chain-selection";
62
63#[derive(Debug, Default, Clone)]
65pub struct Metrics(Option<MetricsInner>);
66
67#[derive(Debug, Clone)]
68struct MetricsInner {
69 approval_checking_finality_lag: prometheus::Gauge<prometheus::U64>,
70 disputes_finality_lag: prometheus::Gauge<prometheus::U64>,
71}
72
73impl metrics::Metrics for Metrics {
74 fn try_register(registry: &prometheus::Registry) -> Result<Self, prometheus::PrometheusError> {
75 let metrics = MetricsInner {
76 approval_checking_finality_lag: prometheus::register(
77 prometheus::Gauge::with_opts(
78 prometheus::Opts::new(
79 "polkadot_parachain_approval_checking_finality_lag",
80 "How far behind the head of the chain the Approval Checking protocol wants to vote",
81 )
82 )?,
83 registry,
84 )?,
85 disputes_finality_lag: prometheus::register(
86 prometheus::Gauge::with_opts(
87 prometheus::Opts::new(
88 "polkadot_parachain_disputes_finality_lag",
89 "How far behind the head of the chain the Disputes protocol wants to vote",
90 )
91 )?,
92 registry,
93 )?,
94 };
95
96 Ok(Metrics(Some(metrics)))
97 }
98}
99
100impl Metrics {
101 fn note_approval_checking_finality_lag(&self, lag: BlockNumber) {
102 if let Some(ref metrics) = self.0 {
103 metrics.approval_checking_finality_lag.set(lag as _);
104 }
105 }
106
107 fn note_disputes_finality_lag(&self, lag: BlockNumber) {
108 if let Some(ref metrics) = self.0 {
109 metrics.disputes_finality_lag.set(lag as _);
110 }
111 }
112}
113
114enum IsDisputesAwareWithOverseer<B: sc_client_api::Backend<PolkadotBlock>> {
118 Yes(SelectRelayChainInner<B, Handle>),
119 No,
120}
121
122impl<B> Clone for IsDisputesAwareWithOverseer<B>
123where
124 B: sc_client_api::Backend<PolkadotBlock>,
125 SelectRelayChainInner<B, Handle>: Clone,
126{
127 fn clone(&self) -> Self {
128 match self {
129 Self::Yes(ref inner) => Self::Yes(inner.clone()),
130 Self::No => Self::No,
131 }
132 }
133}
134
135pub struct SelectRelayChain<B: sc_client_api::Backend<PolkadotBlock>> {
137 longest_chain: sc_consensus::LongestChain<B, PolkadotBlock>,
138 selection: IsDisputesAwareWithOverseer<B>,
139}
140
141impl<B> Clone for SelectRelayChain<B>
142where
143 B: sc_client_api::Backend<PolkadotBlock>,
144 SelectRelayChainInner<B, Handle>: Clone,
145{
146 fn clone(&self) -> Self {
147 Self { longest_chain: self.longest_chain.clone(), selection: self.selection.clone() }
148 }
149}
150
151impl<B> SelectRelayChain<B>
152where
153 B: sc_client_api::Backend<PolkadotBlock> + 'static,
154{
155 pub fn new_longest_chain(backend: Arc<B>) -> Self {
157 gum::debug!(target: LOG_TARGET, "Using {} chain selection algorithm", "longest");
158
159 Self {
160 longest_chain: sc_consensus::LongestChain::new(backend.clone()),
161 selection: IsDisputesAwareWithOverseer::No,
162 }
163 }
164
165 pub fn new_with_overseer(
168 backend: Arc<B>,
169 overseer: Handle,
170 metrics: Metrics,
171 spawn_handle: Option<SpawnTaskHandle>,
172 ) -> Self {
173 gum::debug!(target: LOG_TARGET, "Using dispute aware relay-chain selection algorithm",);
174
175 SelectRelayChain {
176 longest_chain: sc_consensus::LongestChain::new(backend.clone()),
177 selection: IsDisputesAwareWithOverseer::Yes(SelectRelayChainInner::new(
178 backend,
179 overseer,
180 metrics,
181 spawn_handle,
182 )),
183 }
184 }
185
186 pub fn as_longest_chain(&self) -> &sc_consensus::LongestChain<B, PolkadotBlock> {
188 &self.longest_chain
189 }
190}
191
192#[async_trait::async_trait]
193impl<B> SelectChain<PolkadotBlock> for SelectRelayChain<B>
194where
195 B: sc_client_api::Backend<PolkadotBlock> + 'static,
196{
197 async fn leaves(&self) -> Result<Vec<Hash>, ConsensusError> {
198 match self.selection {
199 IsDisputesAwareWithOverseer::Yes(ref selection) => selection.leaves().await,
200 IsDisputesAwareWithOverseer::No => self.longest_chain.leaves().await,
201 }
202 }
203
204 async fn best_chain(&self) -> Result<PolkadotHeader, ConsensusError> {
205 match self.selection {
206 IsDisputesAwareWithOverseer::Yes(ref selection) => selection.best_chain().await,
207 IsDisputesAwareWithOverseer::No => self.longest_chain.best_chain().await,
208 }
209 }
210
211 async fn finality_target(
212 &self,
213 target_hash: Hash,
214 maybe_max_number: Option<BlockNumber>,
215 ) -> Result<Hash, ConsensusError> {
216 if let IsDisputesAwareWithOverseer::Yes(ref selection) = self.selection {
217 selection
218 .finality_target_with_longest_chain(target_hash, maybe_max_number)
219 .await
220 } else {
221 self.longest_chain.finality_target(target_hash, maybe_max_number).await
222 }
223 }
224}
225
226pub struct SelectRelayChainInner<B, OH> {
229 backend: Arc<B>,
230 overseer: OH,
231 metrics: Metrics,
232 spawn_handle: Option<SpawnTaskHandle>,
233}
234
235impl<B, OH> SelectRelayChainInner<B, OH>
236where
237 B: HeaderProviderProvider<PolkadotBlock>,
238 OH: OverseerHandleT + OverseerHandleWithPriorityT,
239{
240 pub fn new(
243 backend: Arc<B>,
244 overseer: OH,
245 metrics: Metrics,
246 spawn_handle: Option<SpawnTaskHandle>,
247 ) -> Self {
248 SelectRelayChainInner { backend, overseer, metrics, spawn_handle }
249 }
250
251 fn block_header(&self, hash: Hash) -> Result<PolkadotHeader, ConsensusError> {
252 match HeaderProvider::header(self.backend.header_provider(), hash) {
253 Ok(Some(header)) => Ok(header),
254 Ok(None) => {
255 Err(ConsensusError::ChainLookup(format!("Missing header with hash {:?}", hash,)))
256 },
257 Err(e) => Err(ConsensusError::ChainLookup(format!(
258 "Lookup failed for header with hash {:?}: {:?}",
259 hash, e,
260 ))),
261 }
262 }
263
264 fn block_number(&self, hash: Hash) -> Result<BlockNumber, ConsensusError> {
265 match HeaderProvider::number(self.backend.header_provider(), hash) {
266 Ok(Some(number)) => Ok(number),
267 Ok(None) => {
268 Err(ConsensusError::ChainLookup(format!("Missing number with hash {:?}", hash,)))
269 },
270 Err(e) => Err(ConsensusError::ChainLookup(format!(
271 "Lookup failed for number with hash {:?}: {:?}",
272 hash, e,
273 ))),
274 }
275 }
276}
277
278impl<B, OH> Clone for SelectRelayChainInner<B, OH>
279where
280 B: HeaderProviderProvider<PolkadotBlock> + Send + Sync,
281 OH: OverseerHandleT + OverseerHandleWithPriorityT,
282{
283 fn clone(&self) -> Self {
284 SelectRelayChainInner {
285 backend: self.backend.clone(),
286 overseer: self.overseer.clone(),
287 metrics: self.metrics.clone(),
288 spawn_handle: self.spawn_handle.clone(),
289 }
290 }
291}
292
293#[derive(thiserror::Error, Debug)]
294enum Error {
295 #[error("Request for leaves from chain selection got canceled")]
298 LeavesCanceled(oneshot::Canceled),
299 #[error("Request for leaves from chain selection got canceled")]
300 BestLeafContainingCanceled(oneshot::Canceled),
301 #[error("Request for determining the undisputed chain from DisputeCoordinator got canceled")]
303 DetermineUndisputedChainCanceled(oneshot::Canceled),
304 #[error("Request approved ancestor from approval voting got canceled")]
305 ApprovedAncestorCanceled(oneshot::Canceled),
306 #[error("ChainSelection returned no leaves")]
308 EmptyLeaves,
309}
310
311#[async_trait::async_trait]
315pub trait OverseerHandleT: Clone + Send + Sync {
316 async fn send_msg<M: Send + Into<AllMessages>>(&mut self, msg: M, origin: &'static str);
317}
318
319#[async_trait::async_trait]
321pub trait OverseerHandleWithPriorityT: Clone + Send + Sync {
322 async fn send_msg_with_priority<M: Send + Into<AllMessages>>(
323 &mut self,
324 msg: M,
325 origin: &'static str,
326 priority: PriorityLevel,
327 );
328}
329
330#[async_trait::async_trait]
331impl OverseerHandleT for Handle {
332 async fn send_msg<M: Send + Into<AllMessages>>(&mut self, msg: M, origin: &'static str) {
333 Handle::send_msg(self, msg, origin).await
334 }
335}
336
337#[async_trait::async_trait]
338impl OverseerHandleWithPriorityT for Handle {
339 async fn send_msg_with_priority<M: Send + Into<AllMessages>>(
340 &mut self,
341 msg: M,
342 origin: &'static str,
343 priority: PriorityLevel,
344 ) {
345 Handle::send_msg_with_priority(self, msg, origin, priority).await
346 }
347}
348
349impl<B, OH> SelectRelayChainInner<B, OH>
350where
351 B: HeaderProviderProvider<PolkadotBlock>,
352 OH: OverseerHandleT + OverseerHandleWithPriorityT + 'static,
353{
354 async fn leaves(&self) -> Result<Vec<Hash>, ConsensusError> {
357 let (tx, rx) = oneshot::channel();
358
359 self.overseer
360 .clone()
361 .send_msg(ChainSelectionMessage::Leaves(tx), std::any::type_name::<Self>())
362 .await;
363
364 let leaves = rx
365 .await
366 .map_err(Error::LeavesCanceled)
367 .map_err(|e| ConsensusError::Other(Box::new(e)))?;
368
369 gum::trace!(target: LOG_TARGET, ?leaves, "Chain selection leaves");
370
371 Ok(leaves)
372 }
373
374 async fn best_chain(&self) -> Result<PolkadotHeader, ConsensusError> {
376 let best_leaf = *self
380 .leaves()
381 .await?
382 .first()
383 .ok_or_else(|| ConsensusError::Other(Box::new(Error::EmptyLeaves)))?;
384
385 gum::trace!(target: LOG_TARGET, ?best_leaf, "Best chain");
386
387 self.block_header(best_leaf)
388 }
389
390 pub(crate) async fn finality_target_with_longest_chain(
400 &self,
401 target_hash: Hash,
402 maybe_max_number: Option<BlockNumber>,
403 ) -> Result<Hash, ConsensusError> {
404 let mut overseer = self.overseer.clone();
405
406 let subchain_head = {
407 let (tx, rx) = oneshot::channel();
408 overseer
409 .send_msg(
410 ChainSelectionMessage::BestLeafContaining(target_hash, tx),
411 std::any::type_name::<Self>(),
412 )
413 .await;
414
415 let best = rx
416 .await
417 .map_err(Error::BestLeafContainingCanceled)
418 .map_err(|e| ConsensusError::Other(Box::new(e)))?;
419
420 gum::trace!(target: LOG_TARGET, ?best, "Best leaf containing");
421
422 match best {
423 None => return Ok(target_hash),
425 Some(best) => best,
426 }
427 };
428
429 let target_number = self.block_number(target_hash)?;
430
431 let subchain_head = match maybe_max_number {
433 None => subchain_head,
434 Some(max) => {
435 if max <= target_number {
436 if max < target_number {
437 gum::warn!(
438 LOG_TARGET,
439 max_number = max,
440 target_number,
441 "`finality_target` max number is less than target number",
442 );
443 }
444 return Ok(target_hash);
445 }
446 let subchain_header = self.block_header(subchain_head)?;
448
449 if subchain_header.number <= max {
450 gum::trace!(target: LOG_TARGET, ?subchain_head, "Constrained sub-chain head",);
451 subchain_head
452 } else {
453 let (ancestor_hash, _) =
454 crate::grandpa_support::walk_backwards_to_target_block(
455 self.backend.header_provider(),
456 max,
457 &subchain_header,
458 )
459 .map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))?;
460 gum::trace!(
461 target: LOG_TARGET,
462 ?ancestor_hash,
463 "Grandpa walk backwards sub-chain head"
464 );
465 ancestor_hash
466 }
467 },
468 };
469
470 let initial_leaf = subchain_head;
471 let initial_leaf_number = self.block_number(initial_leaf)?;
472
473 let (subchain_head, subchain_number, subchain_block_descriptions) = {
475 let (tx, rx) = oneshot::channel();
476 overseer
477 .send_msg_with_priority(
478 ApprovalVotingParallelMessage::ApprovedAncestor(
479 subchain_head,
480 target_number,
481 tx,
482 ),
483 std::any::type_name::<Self>(),
484 PriorityLevel::High,
485 )
486 .await;
487
488 match rx
489 .await
490 .map_err(Error::ApprovedAncestorCanceled)
491 .map_err(|e| ConsensusError::Other(Box::new(e)))?
492 {
493 None => (target_hash, target_number, Vec::new()),
495 Some(HighestApprovedAncestorBlock { number, hash, descriptions }) => {
496 (hash, number, descriptions)
497 },
498 }
499 };
500
501 gum::trace!(target: LOG_TARGET, ?subchain_head, "Ancestor approval restriction applied",);
502
503 let lag = initial_leaf_number.saturating_sub(subchain_number);
504 self.metrics.note_approval_checking_finality_lag(lag);
505
506 if let Some(spawn_handle) = &self.spawn_handle {
509 let mut overseer_handle = self.overseer.clone();
510 let lag_update_task = async move {
511 overseer_handle
512 .send_msg_with_priority(
513 ApprovalVotingParallelMessage::ApprovalCheckingLagUpdate(lag),
514 std::any::type_name::<Self>(),
515 PriorityLevel::High,
516 )
517 .await;
518 };
519
520 spawn_handle.spawn(
521 "approval-checking-lag-update",
522 Some("relay-chain-selection"),
523 Box::pin(lag_update_task),
524 );
525 }
526
527 let (lag, subchain_head) = {
528 if Some(subchain_block_descriptions.len() as _) !=
530 subchain_number.checked_sub(target_number)
531 {
532 gum::error!(
533 LOG_TARGET,
534 present_block_descriptions = subchain_block_descriptions.len(),
535 target_number,
536 subchain_number,
537 "Mismatch of anticipated block descriptions and block number difference.",
538 );
539 return Ok(target_hash);
540 }
541 let (tx, rx) = oneshot::channel();
543 overseer
544 .send_msg_with_priority(
545 DisputeCoordinatorMessage::DetermineUndisputedChain {
546 base: (target_number, target_hash),
547 block_descriptions: subchain_block_descriptions,
548 tx,
549 },
550 std::any::type_name::<Self>(),
551 PriorityLevel::High,
552 )
553 .await;
554
555 let (lag, subchain_head) =
559 match rx.await.map_err(Error::DetermineUndisputedChainCanceled) {
560 Ok((subchain_number, subchain_head)) => {
562 let lag_disputes = initial_leaf_number.saturating_sub(subchain_number);
564 self.metrics.note_disputes_finality_lag(lag_disputes);
565 (lag_disputes, subchain_head)
566 },
567 Err(e) => {
568 gum::error!(
569 target: LOG_TARGET,
570 error = ?e,
571 "Call to `DetermineUndisputedChain` failed",
572 );
573 return Ok(target_hash);
578 },
579 };
580 (lag, subchain_head)
581 };
582
583 gum::trace!(
584 target: LOG_TARGET,
585 ?subchain_head,
586 "Disputed blocks in ancestry restriction applied",
587 );
588
589 if lag > MAX_FINALITY_LAG {
591 let safe_target = initial_leaf_number - MAX_FINALITY_LAG;
594
595 if safe_target <= target_number {
596 gum::warn!(target: LOG_TARGET, ?target_hash, "Safeguard enforced finalization");
597 Ok(target_hash)
599 } else {
600 let initial_leaf_header = self.block_header(initial_leaf)?;
602 let (forced_target, _) = crate::grandpa_support::walk_backwards_to_target_block(
603 self.backend.header_provider(),
604 safe_target,
605 &initial_leaf_header,
606 )
607 .map_err(|e| ConsensusError::ChainLookup(format!("{:?}", e)))?;
608
609 gum::warn!(
610 target: LOG_TARGET,
611 ?forced_target,
612 "Safeguard enforced finalization of child"
613 );
614
615 Ok(forced_target)
616 }
617 } else {
618 Ok(subchain_head)
619 }
620 }
621}