referrerpolicy=no-referrer-when-downgrade

polkadot_approval_distribution/
lib.rs

1// Copyright (C) Parity Technologies (UK) Ltd.
2// This file is part of Polkadot.
3
4// Polkadot is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Polkadot is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Polkadot.  If not, see <http://www.gnu.org/licenses/>.
16
17//! [`ApprovalDistribution`] implementation.
18//!
19//! See the documentation on [approval distribution][approval-distribution-page] in the
20//! implementers' guide.
21//!
22//! [approval-distribution-page]: https://paritytech.github.io/polkadot-sdk/book/node/approval/approval-distribution.html
23
24#![warn(missing_docs)]
25
26use self::metrics::Metrics;
27use futures::{select, FutureExt as _};
28use itertools::Itertools;
29use net_protocol::peer_set::{ProtocolVersion, ValidationVersion};
30use polkadot_node_network_protocol::{
31	self as net_protocol, filter_by_peer_version,
32	grid_topology::{RandomRouting, RequiredRouting, SessionGridTopologies, SessionGridTopology},
33	peer_set::MAX_NOTIFICATION_SIZE,
34	v3 as protocol_v3, PeerId, UnifiedReputationChange as Rep, ValidationProtocols, View,
35};
36use polkadot_node_primitives::{
37	approval::{
38		criteria::{AssignmentCriteria, InvalidAssignment},
39		time::{Clock, ClockExt, SystemClock, TICK_TOO_FAR_IN_FUTURE},
40		v1::{BlockApprovalMeta, DelayTranche, RelayVRFStory},
41		v2::{
42			AsBitIndex, AssignmentCertKindV2, CandidateBitfield, IndirectAssignmentCertV2,
43			IndirectSignedApprovalVoteV2,
44		},
45	},
46	DISPUTE_WINDOW,
47};
48use polkadot_node_subsystem::{
49	messages::{
50		ApprovalDistributionMessage, ApprovalVotingMessage, CheckedIndirectAssignment,
51		CheckedIndirectSignedApprovalVote, NetworkBridgeEvent, NetworkBridgeTxMessage,
52		RuntimeApiMessage,
53	},
54	overseer, FromOrchestra, OverseerSignal, SpawnedSubsystem, SubsystemError,
55};
56use polkadot_node_subsystem_util::{
57	reputation::{ReputationAggregator, REPUTATION_CHANGE_INTERVAL},
58	runtime::{Config as RuntimeInfoConfig, ExtendedSessionInfo, RuntimeInfo},
59};
60use polkadot_primitives::{
61	BlockNumber, CandidateHash, CandidateIndex, CoreIndex, DisputeStatement, GroupIndex, Hash,
62	SessionIndex, Slot, ValidDisputeStatementKind, ValidatorIndex, ValidatorSignature,
63	MAX_COALESCE_APPROVALS,
64};
65use rand::{CryptoRng, Rng, SeedableRng};
66use sp_core::{bounded::BoundedVec, ConstU32};
67use std::{
68	collections::{hash_map, BTreeMap, HashMap, HashSet, VecDeque},
69	sync::Arc,
70	time::Duration,
71};
72
73/// Approval distribution metrics.
74pub mod metrics;
75
76#[cfg(test)]
77mod tests;
78
79const LOG_TARGET: &str = "parachain::approval-distribution";
80
81const COST_UNEXPECTED_MESSAGE: Rep =
82	Rep::CostMinor("Peer sent an out-of-view assignment or approval");
83const COST_DUPLICATE_MESSAGE: Rep = Rep::CostMinorRepeated("Peer sent identical messages");
84const COST_ASSIGNMENT_TOO_FAR_IN_THE_FUTURE: Rep =
85	Rep::CostMinor("The vote was valid but too far in the future");
86const COST_INVALID_MESSAGE: Rep = Rep::CostMajor("The vote was bad");
87const COST_OVERSIZED_BITFIELD: Rep = Rep::CostMajor("Oversized certificate or candidate bitfield");
88
89const BENEFIT_VALID_MESSAGE: Rep = Rep::BenefitMinor("Peer sent a valid message");
90const BENEFIT_VALID_MESSAGE_FIRST: Rep =
91	Rep::BenefitMinorFirst("Valid message with new information");
92
93// Maximum valid size for the `CandidateBitfield` in the assignment messages.
94const MAX_BITFIELD_SIZE: usize = 500;
95
96/// The Approval Distribution subsystem.
97pub struct ApprovalDistribution {
98	metrics: Metrics,
99	slot_duration_millis: u64,
100	clock: Arc<dyn Clock + Send + Sync>,
101	assignment_criteria: Arc<dyn AssignmentCriteria + Send + Sync>,
102}
103
104/// Contains recently finalized
105/// or those pruned due to finalization.
106#[derive(Default)]
107struct RecentlyOutdated {
108	buf: VecDeque<Hash>,
109}
110
111impl RecentlyOutdated {
112	fn note_outdated(&mut self, hash: Hash) {
113		const MAX_BUF_LEN: usize = 20;
114
115		self.buf.push_back(hash);
116
117		while self.buf.len() > MAX_BUF_LEN {
118			let _ = self.buf.pop_front();
119		}
120	}
121
122	fn is_recent_outdated(&self, hash: &Hash) -> bool {
123		self.buf.contains(hash)
124	}
125}
126
127// Contains topology routing information for assignments and approvals.
128struct ApprovalRouting {
129	required_routing: RequiredRouting,
130	local: bool,
131	random_routing: RandomRouting,
132	peers_randomly_routed: Vec<PeerId>,
133}
134
135impl ApprovalRouting {
136	fn mark_randomly_sent(&mut self, peer: PeerId) {
137		self.random_routing.inc_sent();
138		self.peers_randomly_routed.push(peer);
139	}
140}
141
142// This struct is responsible for tracking the full state of an assignment and grid routing
143// information.
144struct ApprovalEntry {
145	// The assignment certificate.
146	assignment: IndirectAssignmentCertV2,
147	// The candidates claimed by the certificate. A mapping between bit index and candidate index.
148	assignment_claimed_candidates: CandidateBitfield,
149	// The approval signatures for each `CandidateIndex` claimed by the assignment certificate.
150	approvals: HashMap<CandidateBitfield, IndirectSignedApprovalVoteV2>,
151	// The validator index of the assignment signer.
152	validator_index: ValidatorIndex,
153	// Information required for gossiping to other peers using the grid topology.
154	routing_info: ApprovalRouting,
155}
156
157#[derive(Debug)]
158enum ApprovalEntryError {
159	InvalidValidatorIndex,
160	CandidateIndexOutOfBounds,
161	InvalidCandidateIndex,
162	DuplicateApproval,
163	UnknownAssignment,
164}
165
166impl ApprovalEntry {
167	pub fn new(
168		assignment: IndirectAssignmentCertV2,
169		candidates: CandidateBitfield,
170		routing_info: ApprovalRouting,
171	) -> ApprovalEntry {
172		Self {
173			validator_index: assignment.validator,
174			assignment,
175			approvals: HashMap::new(),
176			assignment_claimed_candidates: candidates,
177			routing_info,
178		}
179	}
180
181	// Create a `MessageSubject` to reference the assignment.
182	pub fn create_assignment_knowledge(&self, block_hash: Hash) -> (MessageSubject, MessageKind) {
183		(
184			MessageSubject(
185				block_hash,
186				self.assignment_claimed_candidates.clone(),
187				self.validator_index,
188			),
189			MessageKind::Assignment,
190		)
191	}
192
193	// Updates routing information and returns the previous information if any.
194	pub fn routing_info_mut(&mut self) -> &mut ApprovalRouting {
195		&mut self.routing_info
196	}
197
198	// Get the routing information.
199	pub fn routing_info(&self) -> &ApprovalRouting {
200		&self.routing_info
201	}
202
203	// Update routing information.
204	pub fn update_required_routing(&mut self, required_routing: RequiredRouting) {
205		self.routing_info.required_routing = required_routing;
206	}
207
208	// Tells if this entry assignment covers at least one candidate in the approval
209	pub fn includes_approval_candidates(&self, approval: &IndirectSignedApprovalVoteV2) -> bool {
210		for candidate_index in approval.candidate_indices.iter_ones() {
211			if self.assignment_claimed_candidates.bit_at((candidate_index).as_bit_index()) {
212				return true;
213			}
214		}
215		return false;
216	}
217
218	// Records a new approval. Returns error if the claimed candidate is not found or we already
219	// have received the approval.
220	pub fn note_approval(
221		&mut self,
222		approval: IndirectSignedApprovalVoteV2,
223	) -> Result<(), ApprovalEntryError> {
224		// First do some sanity checks:
225		// - check validator index matches
226		// - check claimed candidate
227		// - check for duplicate approval
228		if self.validator_index != approval.validator {
229			return Err(ApprovalEntryError::InvalidValidatorIndex);
230		}
231
232		// We need at least one of the candidates in the approval to be in this assignment
233		if !self.includes_approval_candidates(&approval) {
234			return Err(ApprovalEntryError::InvalidCandidateIndex);
235		}
236
237		if self.approvals.contains_key(&approval.candidate_indices) {
238			return Err(ApprovalEntryError::DuplicateApproval);
239		}
240
241		self.approvals.insert(approval.candidate_indices.clone(), approval.clone());
242		Ok(())
243	}
244
245	// Get the assignment certificate and claimed candidates.
246	pub fn assignment(&self) -> (IndirectAssignmentCertV2, CandidateBitfield) {
247		(self.assignment.clone(), self.assignment_claimed_candidates.clone())
248	}
249
250	// Get all approvals for all candidates claimed by the assignment.
251	pub fn approvals(&self) -> Vec<IndirectSignedApprovalVoteV2> {
252		self.approvals.values().cloned().collect::<Vec<_>>()
253	}
254
255	// Get validator index.
256	pub fn validator_index(&self) -> ValidatorIndex {
257		self.validator_index
258	}
259}
260
261// We keep track of each peer view and protocol version using this struct.
262struct PeerEntry {
263	pub view: View,
264	pub version: ProtocolVersion,
265}
266
267// In case the original grid topology mechanisms don't work on their own, we need to trade bandwidth
268// for protocol liveliness by introducing aggression.
269//
270// Aggression has 3 levels:
271//
272//  * Aggression Level 0: The basic behaviors described above.
273//  * Aggression Level 1: The originator of a message sends to all peers. Other peers follow the
274//    rules above.
275//  * Aggression Level 2: All peers send all messages to all their row and column neighbors. This
276//    means that each validator will, on average, receive each message approximately `2*sqrt(n)`
277//    times.
278// The aggression level of messages pertaining to a block increases when that block is unfinalized
279// and is a child of the finalized block.
280// This means that only one block at a time has its messages propagated with aggression > 0.
281//
282// A note on aggression thresholds: changes in propagation apply only to blocks which are the
283// _direct descendants_ of the finalized block which are older than the given threshold,
284// not to all blocks older than the threshold. Most likely, a few assignments struggle to
285// be propagated in a single block and this holds up all of its descendants blocks.
286// Accordingly, we only step on the gas for the block which is most obviously holding up finality.
287/// Aggression configuration representation
288#[derive(Clone)]
289struct AggressionConfig {
290	/// Aggression level 1: all validators send all their own messages to all peers.
291	l1_threshold: Option<BlockNumber>,
292	/// Aggression level 2: level 1 + all validators send all messages to all peers in the X and Y
293	/// dimensions.
294	l2_threshold: Option<BlockNumber>,
295	/// How often to re-send messages to all targeted recipients.
296	/// This applies to all unfinalized blocks.
297	resend_unfinalized_period: Option<BlockNumber>,
298}
299
300impl AggressionConfig {
301	/// Returns `true` if age is past threshold depending on the aggression level
302	fn should_trigger_aggression(&self, age: BlockNumber) -> bool {
303		if let Some(t) = self.l1_threshold {
304			age >= t
305		} else if let Some(t) = self.resend_unfinalized_period {
306			age > 0 && age.is_multiple_of(t)
307		} else {
308			false
309		}
310	}
311}
312
313impl Default for AggressionConfig {
314	fn default() -> Self {
315		AggressionConfig {
316			l1_threshold: Some(16),
317			l2_threshold: Some(64),
318			resend_unfinalized_period: Some(8),
319		}
320	}
321}
322
323#[derive(PartialEq)]
324enum Resend {
325	Yes,
326	No,
327}
328
329/// The [`State`] struct is responsible for tracking the overall state of the subsystem.
330///
331/// It tracks metadata about our view of the unfinalized chain,
332/// which assignments and approvals we have seen, and our peers' views.
333#[derive(Default)]
334pub struct State {
335	/// These two fields are used in conjunction to construct a view over the unfinalized chain.
336	blocks_by_number: BTreeMap<BlockNumber, Vec<Hash>>,
337	blocks: HashMap<Hash, BlockEntry>,
338
339	/// Our view updates to our peers can race with `NewBlocks` updates. We store messages received
340	/// against the directly mentioned blocks in our view in this map until `NewBlocks` is
341	/// received.
342	///
343	/// As long as the parent is already in the `blocks` map and `NewBlocks` messages aren't
344	/// delayed by more than a block length, this strategy will work well for mitigating the race.
345	/// This is also a race that occurs typically on local networks.
346	pending_known: HashMap<Hash, Vec<(PeerId, PendingMessage)>>,
347
348	/// Peer data is partially stored here, and partially inline within the [`BlockEntry`]s
349	peer_views: HashMap<PeerId, PeerEntry>,
350
351	/// Keeps a topology for various different sessions.
352	topologies: SessionGridTopologies,
353
354	/// Tracks recently finalized blocks.
355	recent_outdated_blocks: RecentlyOutdated,
356
357	/// Aggression configuration.
358	aggression_config: AggressionConfig,
359
360	/// Current approval checking finality lag.
361	approval_checking_lag: BlockNumber,
362
363	/// Aggregated reputation change
364	reputation: ReputationAggregator,
365
366	/// Slot duration in millis
367	slot_duration_millis: u64,
368}
369
370#[derive(Debug, Clone, Copy, PartialEq, Eq)]
371enum MessageKind {
372	Assignment,
373	Approval,
374}
375
376// Utility structure to identify assignments and approvals for specific candidates.
377// Assignments can span multiple candidates, while approvals refer to only one candidate.
378//
379#[derive(Debug, Clone, Hash, PartialEq, Eq)]
380struct MessageSubject(Hash, pub CandidateBitfield, ValidatorIndex);
381
382#[derive(Debug, Clone, Default)]
383struct Knowledge {
384	// When there is no entry, this means the message is unknown
385	// When there is an entry with `MessageKind::Assignment`, the assignment is known.
386	// When there is an entry with `MessageKind::Approval`, the assignment and approval are known.
387	known_messages: HashMap<MessageSubject, MessageKind>,
388}
389
390impl Knowledge {
391	fn contains(&self, message: &MessageSubject, kind: MessageKind) -> bool {
392		match (kind, self.known_messages.get(message)) {
393			(_, None) => false,
394			(MessageKind::Assignment, Some(_)) => true,
395			(MessageKind::Approval, Some(MessageKind::Assignment)) => false,
396			(MessageKind::Approval, Some(MessageKind::Approval)) => true,
397		}
398	}
399
400	fn insert(&mut self, message: MessageSubject, kind: MessageKind) -> bool {
401		let mut success = match self.known_messages.entry(message.clone()) {
402			hash_map::Entry::Vacant(vacant) => {
403				vacant.insert(kind);
404				// If there are multiple candidates assigned in the message, create
405				// separate entries for each one.
406				true
407			},
408			hash_map::Entry::Occupied(mut occupied) => match (*occupied.get(), kind) {
409				(MessageKind::Assignment, MessageKind::Assignment) => false,
410				(MessageKind::Approval, MessageKind::Approval) => false,
411				(MessageKind::Approval, MessageKind::Assignment) => false,
412				(MessageKind::Assignment, MessageKind::Approval) => {
413					*occupied.get_mut() = MessageKind::Approval;
414					true
415				},
416			},
417		};
418
419		// In case of successful insertion of multiple candidate assignments create additional
420		// entries for each assigned candidate. This fakes knowledge of individual assignments, but
421		// we need to share the same `MessageSubject` with the followup approval candidate index.
422		if kind == MessageKind::Assignment && success && message.1.count_ones() > 1 {
423			for candidate_index in message.1.iter_ones() {
424				success = success &&
425					self.insert(
426						MessageSubject(
427							message.0,
428							vec![candidate_index as u32].try_into().expect("Non-empty vec; qed"),
429							message.2,
430						),
431						kind,
432					);
433			}
434		}
435		success
436	}
437}
438
439/// Information that has been circulated to and from a peer.
440#[derive(Debug, Clone, Default)]
441struct PeerKnowledge {
442	/// The knowledge we've sent to the peer.
443	sent: Knowledge,
444	/// The knowledge we've received from the peer.
445	received: Knowledge,
446}
447
448impl PeerKnowledge {
449	fn contains(&self, message: &MessageSubject, kind: MessageKind) -> bool {
450		self.sent.contains(message, kind) || self.received.contains(message, kind)
451	}
452
453	// Generate the knowledge keys for querying if all assignments of an approval are known
454	// by this peer.
455	fn generate_assignments_keys(
456		approval: &IndirectSignedApprovalVoteV2,
457	) -> Vec<(MessageSubject, MessageKind)> {
458		approval
459			.candidate_indices
460			.iter_ones()
461			.map(|candidate_index| {
462				(
463					MessageSubject(
464						approval.block_hash,
465						(candidate_index as CandidateIndex).into(),
466						approval.validator,
467					),
468					MessageKind::Assignment,
469				)
470			})
471			.collect_vec()
472	}
473
474	// Generate the knowledge keys for querying if an approval is known by peer.
475	fn generate_approval_key(
476		approval: &IndirectSignedApprovalVoteV2,
477	) -> (MessageSubject, MessageKind) {
478		(
479			MessageSubject(
480				approval.block_hash,
481				approval.candidate_indices.clone(),
482				approval.validator,
483			),
484			MessageKind::Approval,
485		)
486	}
487}
488
489/// Information about blocks in our current view as well as whether peers know of them.
490struct BlockEntry {
491	/// Peers who we know are aware of this block and thus, the candidates within it.
492	/// This maps to their knowledge of messages.
493	known_by: HashMap<PeerId, PeerKnowledge>,
494	/// The number of the block.
495	number: BlockNumber,
496	/// The parent hash of the block.
497	parent_hash: Hash,
498	/// Our knowledge of messages.
499	knowledge: Knowledge,
500	/// A votes entry for each candidate indexed by [`CandidateIndex`].
501	candidates: Vec<CandidateEntry>,
502	/// Information about candidate metadata.
503	candidates_metadata: Vec<(CandidateHash, CoreIndex, GroupIndex)>,
504	/// The session index of this block.
505	session: SessionIndex,
506	/// Approval entries for whole block. These also contain all approvals in the case of multiple
507	/// candidates being claimed by assignments.
508	approval_entries: HashMap<(ValidatorIndex, CandidateBitfield), ApprovalEntry>,
509	/// The block vrf story.
510	vrf_story: RelayVRFStory,
511	/// The block slot.
512	slot: Slot,
513	/// Backing off from re-sending messages to peers.
514	last_resent_at_block_number: Option<u32>,
515}
516
517impl BlockEntry {
518	// Returns the peer which currently know this block.
519	pub fn known_by(&self) -> Vec<PeerId> {
520		self.known_by.keys().cloned().collect::<Vec<_>>()
521	}
522
523	pub fn insert_approval_entry(&mut self, entry: ApprovalEntry) -> &mut ApprovalEntry {
524		// First map one entry per candidate to the same key we will use in `approval_entries`.
525		// Key is (Validator_index, CandidateBitfield) that links the `ApprovalEntry` to the (K,V)
526		// entry in `candidate_entry.messages`.
527		for claimed_candidate_index in entry.assignment_claimed_candidates.iter_ones() {
528			match self.candidates.get_mut(claimed_candidate_index) {
529				Some(candidate_entry) => {
530					candidate_entry
531						.assignments
532						.entry(entry.validator_index())
533						.or_insert(entry.assignment_claimed_candidates.clone());
534				},
535				None => {
536					// This should never happen, but if it happens, it means the subsystem is
537					// broken.
538					gum::warn!(
539						target: LOG_TARGET,
540						hash = ?entry.assignment.block_hash,
541						?claimed_candidate_index,
542						"Missing candidate entry on `import_and_circulate_assignment`",
543					);
544				},
545			};
546		}
547
548		self.approval_entries
549			.entry((entry.validator_index, entry.assignment_claimed_candidates.clone()))
550			.or_insert(entry)
551	}
552
553	// Tels if all candidate_indices are valid candidates
554	pub fn contains_candidates(&self, candidate_indices: &CandidateBitfield) -> bool {
555		candidate_indices
556			.iter_ones()
557			.all(|candidate_index| self.candidates.get(candidate_index as usize).is_some())
558	}
559
560	// Saves the given approval in all ApprovalEntries that contain an assignment for any of the
561	// candidates in the approval.
562	//
563	// Returns the required routing needed for this approval and the lit of random peers the
564	// covering assignments were sent.
565	pub fn note_approval(
566		&mut self,
567		approval: IndirectSignedApprovalVoteV2,
568	) -> Result<(RequiredRouting, HashSet<PeerId>), ApprovalEntryError> {
569		let mut required_routing: Option<RequiredRouting> = None;
570		let mut peers_randomly_routed_to = HashSet::new();
571
572		if self.candidates.len() < approval.candidate_indices.len() as usize {
573			return Err(ApprovalEntryError::CandidateIndexOutOfBounds);
574		}
575
576		// First determine all assignments bitfields that might be covered by this approval
577		let covered_assignments_bitfields: HashSet<CandidateBitfield> = approval
578			.candidate_indices
579			.iter_ones()
580			.filter_map(|candidate_index| {
581				self.candidates.get_mut(candidate_index).map_or(None, |candidate_entry| {
582					candidate_entry.assignments.get(&approval.validator).cloned()
583				})
584			})
585			.collect();
586
587		// Mark the vote in all approval entries
588		for assignment_bitfield in covered_assignments_bitfields {
589			if let Some(approval_entry) =
590				self.approval_entries.get_mut(&(approval.validator, assignment_bitfield))
591			{
592				approval_entry.note_approval(approval.clone())?;
593				peers_randomly_routed_to
594					.extend(approval_entry.routing_info().peers_randomly_routed.iter());
595
596				if let Some(current_required_routing) = required_routing {
597					required_routing = Some(
598						current_required_routing
599							.combine(approval_entry.routing_info().required_routing),
600					);
601				} else {
602					required_routing = Some(approval_entry.routing_info().required_routing)
603				}
604			}
605		}
606
607		if let Some(required_routing) = required_routing {
608			Ok((required_routing, peers_randomly_routed_to))
609		} else {
610			Err(ApprovalEntryError::UnknownAssignment)
611		}
612	}
613
614	/// Returns the list of approval votes covering this candidate
615	pub fn approval_votes(
616		&self,
617		candidate_index: CandidateIndex,
618	) -> Vec<IndirectSignedApprovalVoteV2> {
619		let result: Option<
620			HashMap<(ValidatorIndex, CandidateBitfield), IndirectSignedApprovalVoteV2>,
621		> = self.candidates.get(candidate_index as usize).map(|candidate_entry| {
622			candidate_entry
623				.assignments
624				.iter()
625				.filter_map(|(validator, assignment_bitfield)| {
626					self.approval_entries.get(&(*validator, assignment_bitfield.clone()))
627				})
628				.flat_map(|approval_entry| {
629					approval_entry
630						.approvals
631						.clone()
632						.into_iter()
633						.filter(|(approved_candidates, _)| {
634							approved_candidates.bit_at(candidate_index.as_bit_index())
635						})
636						.map(|(approved_candidates, vote)| {
637							((approval_entry.validator_index, approved_candidates), vote)
638						})
639				})
640				.collect()
641		});
642
643		result.map(|result| result.into_values().collect_vec()).unwrap_or_default()
644	}
645}
646
647// Information about candidates in the context of a particular block they are included in.
648// In other words, multiple `CandidateEntry`s may exist for the same candidate,
649// if it is included by multiple blocks - this is likely the case when there are forks.
650#[derive(Debug, Default)]
651struct CandidateEntry {
652	// The value represents part of the lookup key in `approval_entries` to fetch the assignment
653	// and existing votes.
654	assignments: HashMap<ValidatorIndex, CandidateBitfield>,
655}
656
657#[derive(Debug, Clone, PartialEq)]
658enum MessageSource {
659	Peer(PeerId),
660	Local,
661}
662
663// Encountered error while validating an assignment.
664#[derive(Debug)]
665enum InvalidAssignmentError {
666	// The vrf check for the assignment failed.
667	#[allow(dead_code)]
668	CryptoCheckFailed(InvalidAssignment),
669	// The assignment did not claim any valid candidate.
670	NoClaimedCandidates,
671	// Claimed invalid candidate.
672	#[allow(dead_code)]
673	ClaimedInvalidCandidateIndex {
674		claimed_index: usize,
675		max_index: usize,
676	},
677	// The assignment claimes more candidates than the maximum allowed.
678	OversizedClaimedBitfield,
679	// `SessionInfo`  was not found for the block hash in the assignment.
680	#[allow(dead_code)]
681	SessionInfoNotFound(polkadot_node_subsystem_util::runtime::Error),
682}
683
684// Encountered error while validating an approval.
685#[derive(Debug)]
686enum InvalidVoteError {
687	// The candidate index was out of bounds.
688	CandidateIndexOutOfBounds,
689	// The candidate hash was not found in the block's candidate list.
690	CandidateHashNotFound,
691	// The validator index was out of bounds.
692	ValidatorIndexOutOfBounds,
693	// The signature of the vote was invalid.
694	InvalidSignature,
695	// The approval coalesces more candidates than the allowed maximum.
696	TooManyCandidates,
697	// `SessionInfo` was not found for the block hash in the approval.
698	#[allow(dead_code)]
699	SessionInfoNotFound(polkadot_node_subsystem_util::runtime::Error),
700}
701
702impl MessageSource {
703	fn peer_id(&self) -> Option<PeerId> {
704		match self {
705			Self::Peer(id) => Some(*id),
706			Self::Local => None,
707		}
708	}
709}
710
711enum PendingMessage {
712	Assignment(IndirectAssignmentCertV2, CandidateBitfield),
713	Approval(IndirectSignedApprovalVoteV2),
714}
715
716#[overseer::contextbounds(ApprovalDistribution, prefix = self::overseer)]
717impl State {
718	/// Build State with specified slot duration.
719	pub fn with_config(slot_duration_millis: u64) -> Self {
720		Self { slot_duration_millis, ..Default::default() }
721	}
722
723	async fn handle_network_msg<
724		N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
725		A: overseer::SubsystemSender<ApprovalVotingMessage>,
726		RA: overseer::SubsystemSender<RuntimeApiMessage>,
727	>(
728		&mut self,
729		approval_voting_sender: &mut A,
730		network_sender: &mut N,
731		runtime_api_sender: &mut RA,
732		metrics: &Metrics,
733		event: NetworkBridgeEvent<net_protocol::ApprovalDistributionMessage>,
734		rng: &mut (impl CryptoRng + Rng),
735		assignment_criteria: &(impl AssignmentCriteria + ?Sized),
736		clock: &(impl Clock + ?Sized),
737		session_info_provider: &mut RuntimeInfo,
738	) {
739		match event {
740			NetworkBridgeEvent::PeerConnected(peer_id, role, version, authority_ids) => {
741				gum::trace!(target: LOG_TARGET, ?peer_id, ?role, ?authority_ids, "Peer connected");
742				if let Some(authority_ids) = authority_ids {
743					self.topologies.update_authority_ids(peer_id, &authority_ids);
744				}
745				// insert a blank view if none already present
746				self.peer_views
747					.entry(peer_id)
748					.or_insert(PeerEntry { view: Default::default(), version });
749			},
750			NetworkBridgeEvent::PeerDisconnected(peer_id) => {
751				gum::trace!(target: LOG_TARGET, ?peer_id, "Peer disconnected");
752				self.peer_views.remove(&peer_id);
753				self.blocks.iter_mut().for_each(|(_hash, entry)| {
754					entry.known_by.remove(&peer_id);
755				})
756			},
757			NetworkBridgeEvent::NewGossipTopology(topology) => {
758				self.handle_new_session_topology(
759					network_sender,
760					topology.session,
761					topology.topology,
762					topology.local_index,
763				)
764				.await;
765			},
766			NetworkBridgeEvent::PeerViewChange(peer_id, view) => {
767				self.handle_peer_view_change(network_sender, metrics, peer_id, view, rng).await;
768			},
769			NetworkBridgeEvent::OurViewChange(view) => {
770				gum::trace!(target: LOG_TARGET, ?view, "Own view change");
771				for head in view.iter() {
772					if !self.blocks.contains_key(head) {
773						self.pending_known.entry(*head).or_default();
774					}
775				}
776
777				self.pending_known.retain(|h, _| {
778					let live = view.contains(h);
779					if !live {
780						gum::trace!(
781							target: LOG_TARGET,
782							block_hash = ?h,
783							"Cleaning up stale pending messages",
784						);
785					}
786					live
787				});
788			},
789			NetworkBridgeEvent::PeerMessage(peer_id, message) => {
790				self.process_incoming_peer_message(
791					approval_voting_sender,
792					network_sender,
793					runtime_api_sender,
794					metrics,
795					peer_id,
796					message,
797					rng,
798					assignment_criteria,
799					clock,
800					session_info_provider,
801				)
802				.await;
803			},
804			NetworkBridgeEvent::UpdatedAuthorityIds(peer_id, authority_ids) => {
805				gum::debug!(target: LOG_TARGET, ?peer_id, ?authority_ids, "Update Authority Ids");
806				// If we learn about a new PeerId for an authority ids we need to try to route the
807				// messages that should have sent to that validator according to the topology.
808				if self.topologies.update_authority_ids(peer_id, &authority_ids) {
809					if let Some(PeerEntry { view, version }) = self.peer_views.get(&peer_id) {
810						let intersection = self
811							.blocks_by_number
812							.iter()
813							.filter(|(block_number, _)| *block_number > &view.finalized_number)
814							.flat_map(|(_, hashes)| {
815								hashes.iter().filter(|hash| {
816									self.blocks
817										.get(&hash)
818										.map(|block| block.known_by.get(&peer_id).is_some())
819										.unwrap_or_default()
820								})
821							});
822						let view_intersection =
823							View::new(intersection.cloned(), view.finalized_number);
824						Self::unify_with_peer(
825							network_sender,
826							metrics,
827							&mut self.blocks,
828							&self.topologies,
829							self.peer_views.len(),
830							peer_id,
831							*version,
832							view_intersection,
833							rng,
834							true,
835						)
836						.await;
837					}
838				}
839			},
840		}
841	}
842
843	async fn handle_new_blocks<
844		N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
845		A: overseer::SubsystemSender<ApprovalVotingMessage>,
846		RA: overseer::SubsystemSender<RuntimeApiMessage>,
847	>(
848		&mut self,
849		approval_voting_sender: &mut A,
850		network_sender: &mut N,
851		runtime_api_sender: &mut RA,
852		metrics: &Metrics,
853		metas: Vec<BlockApprovalMeta>,
854		rng: &mut (impl CryptoRng + Rng),
855		assignment_criteria: &(impl AssignmentCriteria + ?Sized),
856		clock: &(impl Clock + ?Sized),
857		session_info_provider: &mut RuntimeInfo,
858	) {
859		let mut new_hashes = HashSet::new();
860
861		gum::debug!(
862			target: LOG_TARGET,
863			"Got new blocks {:?}",
864			metas.iter().map(|m| (m.hash, m.number)).collect::<Vec<_>>(),
865		);
866
867		for meta in metas {
868			match self.blocks.entry(meta.hash) {
869				hash_map::Entry::Vacant(entry) => {
870					let candidates_count = meta.candidates.len();
871					let mut candidates = Vec::with_capacity(candidates_count);
872					candidates.resize_with(candidates_count, Default::default);
873
874					entry.insert(BlockEntry {
875						known_by: HashMap::new(),
876						number: meta.number,
877						parent_hash: meta.parent_hash,
878						knowledge: Knowledge::default(),
879						candidates,
880						session: meta.session,
881						approval_entries: HashMap::new(),
882						candidates_metadata: meta.candidates,
883						vrf_story: meta.vrf_story,
884						slot: meta.slot,
885						last_resent_at_block_number: None,
886					});
887
888					self.topologies.inc_session_refs(meta.session);
889
890					new_hashes.insert(meta.hash);
891
892					// In case there are duplicates, we should only set this if the entry
893					// was vacant.
894					self.blocks_by_number.entry(meta.number).or_default().push(meta.hash);
895				},
896				_ => continue,
897			}
898		}
899
900		{
901			for (peer_id, PeerEntry { view, version }) in self.peer_views.iter() {
902				let intersection = view.iter().filter(|h| new_hashes.contains(h));
903				let view_intersection = View::new(intersection.cloned(), view.finalized_number);
904				Self::unify_with_peer(
905					network_sender,
906					metrics,
907					&mut self.blocks,
908					&self.topologies,
909					self.peer_views.len(),
910					*peer_id,
911					*version,
912					view_intersection,
913					rng,
914					false,
915				)
916				.await;
917			}
918
919			let pending_now_known = self
920				.pending_known
921				.keys()
922				.filter(|k| self.blocks.contains_key(k))
923				.copied()
924				.collect::<Vec<_>>();
925
926			let to_import = pending_now_known
927				.into_iter()
928				.inspect(|h| {
929					gum::trace!(
930						target: LOG_TARGET,
931						block_hash = ?h,
932						"Extracting pending messages for new block"
933					)
934				})
935				.filter_map(|k| self.pending_known.remove(&k))
936				.flatten()
937				.collect::<Vec<_>>();
938
939			if !to_import.is_empty() {
940				gum::debug!(
941					target: LOG_TARGET,
942					num = to_import.len(),
943					"Processing pending assignment/approvals",
944				);
945
946				let _timer = metrics.time_import_pending_now_known();
947
948				for (peer_id, message) in to_import {
949					match message {
950						PendingMessage::Assignment(assignment, claimed_indices) => {
951							self.import_and_circulate_assignment(
952								approval_voting_sender,
953								network_sender,
954								runtime_api_sender,
955								metrics,
956								MessageSource::Peer(peer_id),
957								assignment,
958								claimed_indices,
959								rng,
960								assignment_criteria,
961								clock,
962								session_info_provider,
963							)
964							.await;
965						},
966						PendingMessage::Approval(approval_vote) => {
967							self.import_and_circulate_approval(
968								approval_voting_sender,
969								network_sender,
970								runtime_api_sender,
971								metrics,
972								MessageSource::Peer(peer_id),
973								approval_vote,
974								session_info_provider,
975							)
976							.await;
977						},
978					}
979				}
980			}
981		}
982
983		self.enable_aggression(network_sender, Resend::Yes, metrics).await;
984	}
985
986	async fn handle_new_session_topology<N: overseer::SubsystemSender<NetworkBridgeTxMessage>>(
987		&mut self,
988		network_sender: &mut N,
989		session: SessionIndex,
990		topology: SessionGridTopology,
991		local_index: Option<ValidatorIndex>,
992	) {
993		if local_index.is_none() {
994			// this subsystem only matters to validators.
995			return;
996		}
997
998		self.topologies.insert_topology(session, topology, local_index);
999		let topology = self.topologies.get_topology(session).expect("just inserted above; qed");
1000
1001		adjust_required_routing_and_propagate(
1002			network_sender,
1003			&mut self.blocks,
1004			&self.topologies,
1005			|block_entry| block_entry.session == session,
1006			|required_routing, local, validator_index| {
1007				if required_routing == &RequiredRouting::PendingTopology {
1008					topology
1009						.local_grid_neighbors()
1010						.required_routing_by_index(*validator_index, local)
1011				} else {
1012					*required_routing
1013				}
1014			},
1015			&self.peer_views,
1016		)
1017		.await;
1018	}
1019
1020	async fn process_incoming_assignments<A, N, R, RA>(
1021		&mut self,
1022		approval_voting_sender: &mut A,
1023		network_sender: &mut N,
1024		runtime_api_sender: &mut RA,
1025		metrics: &Metrics,
1026		peer_id: PeerId,
1027		assignments: Vec<(IndirectAssignmentCertV2, CandidateBitfield)>,
1028		rng: &mut R,
1029		assignment_criteria: &(impl AssignmentCriteria + ?Sized),
1030		clock: &(impl Clock + ?Sized),
1031		session_info_provider: &mut RuntimeInfo,
1032	) where
1033		A: overseer::SubsystemSender<ApprovalVotingMessage>,
1034		N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
1035		RA: overseer::SubsystemSender<RuntimeApiMessage>,
1036		R: CryptoRng + Rng,
1037	{
1038		for (assignment, claimed_indices) in assignments {
1039			if let Some(pending) = self.pending_known.get_mut(&assignment.block_hash) {
1040				let block_hash = &assignment.block_hash;
1041				let validator_index = assignment.validator;
1042
1043				gum::trace!(
1044					target: LOG_TARGET,
1045					%peer_id,
1046					?block_hash,
1047					?claimed_indices,
1048					?validator_index,
1049					"Pending assignment",
1050				);
1051
1052				pending.push((peer_id, PendingMessage::Assignment(assignment, claimed_indices)));
1053
1054				continue;
1055			}
1056
1057			self.import_and_circulate_assignment(
1058				approval_voting_sender,
1059				network_sender,
1060				runtime_api_sender,
1061				metrics,
1062				MessageSource::Peer(peer_id),
1063				assignment,
1064				claimed_indices,
1065				rng,
1066				assignment_criteria,
1067				clock,
1068				session_info_provider,
1069			)
1070			.await;
1071		}
1072	}
1073
1074	// Entry point for processing an approval coming from a peer.
1075	async fn process_incoming_approvals<
1076		N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
1077		A: overseer::SubsystemSender<ApprovalVotingMessage>,
1078		RA: overseer::SubsystemSender<RuntimeApiMessage>,
1079	>(
1080		&mut self,
1081		approval_voting_sender: &mut A,
1082		network_sender: &mut N,
1083		runtime_api_sender: &mut RA,
1084		metrics: &Metrics,
1085		peer_id: PeerId,
1086		approvals: Vec<IndirectSignedApprovalVoteV2>,
1087		session_info_provider: &mut RuntimeInfo,
1088	) {
1089		gum::trace!(
1090			target: LOG_TARGET,
1091			peer_id = %peer_id,
1092			num = approvals.len(),
1093			"Processing approvals from a peer",
1094		);
1095		for approval_vote in approvals.into_iter() {
1096			if let Some(pending) = self.pending_known.get_mut(&approval_vote.block_hash) {
1097				let block_hash = approval_vote.block_hash;
1098				let validator_index = approval_vote.validator;
1099
1100				gum::trace!(
1101					target: LOG_TARGET,
1102					%peer_id,
1103					?block_hash,
1104					?validator_index,
1105					"Pending assignment candidates {:?}",
1106					approval_vote.candidate_indices,
1107				);
1108
1109				pending.push((peer_id, PendingMessage::Approval(approval_vote)));
1110
1111				continue;
1112			}
1113
1114			self.import_and_circulate_approval(
1115				approval_voting_sender,
1116				network_sender,
1117				runtime_api_sender,
1118				metrics,
1119				MessageSource::Peer(peer_id),
1120				approval_vote,
1121				session_info_provider,
1122			)
1123			.await;
1124		}
1125	}
1126
1127	async fn process_incoming_peer_message<A, N, RA, R>(
1128		&mut self,
1129		approval_voting_sender: &mut A,
1130		network_sender: &mut N,
1131		runtime_api_sender: &mut RA,
1132		metrics: &Metrics,
1133		peer_id: PeerId,
1134		msg: ValidationProtocols<protocol_v3::ApprovalDistributionMessage>,
1135		rng: &mut R,
1136		assignment_criteria: &(impl AssignmentCriteria + ?Sized),
1137		clock: &(impl Clock + ?Sized),
1138		session_info_provider: &mut RuntimeInfo,
1139	) where
1140		A: overseer::SubsystemSender<ApprovalVotingMessage>,
1141		N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
1142		RA: overseer::SubsystemSender<RuntimeApiMessage>,
1143		R: CryptoRng + Rng,
1144	{
1145		match msg {
1146			ValidationProtocols::V3(protocol_v3::ApprovalDistributionMessage::Assignments(
1147				assignments,
1148			)) => {
1149				gum::trace!(
1150					target: LOG_TARGET,
1151					peer_id = %peer_id,
1152					num = assignments.len(),
1153					"Processing assignments from a peer",
1154				);
1155				let sanitized_assignments =
1156					self.sanitize_v2_assignments(peer_id, network_sender, assignments).await;
1157
1158				self.process_incoming_assignments(
1159					approval_voting_sender,
1160					network_sender,
1161					runtime_api_sender,
1162					metrics,
1163					peer_id,
1164					sanitized_assignments,
1165					rng,
1166					assignment_criteria,
1167					clock,
1168					session_info_provider,
1169				)
1170				.await;
1171			},
1172			ValidationProtocols::V3(protocol_v3::ApprovalDistributionMessage::Approvals(
1173				approvals,
1174			)) => {
1175				let sanitized_approvals =
1176					self.sanitize_v2_approvals(peer_id, network_sender, approvals).await;
1177				self.process_incoming_approvals(
1178					approval_voting_sender,
1179					network_sender,
1180					runtime_api_sender,
1181					metrics,
1182					peer_id,
1183					sanitized_approvals,
1184					session_info_provider,
1185				)
1186				.await;
1187			},
1188		}
1189	}
1190
1191	// handle a peer view change: requires that the peer is already connected
1192	// and has an entry in the `PeerData` struct.
1193	async fn handle_peer_view_change<N: overseer::SubsystemSender<NetworkBridgeTxMessage>, R>(
1194		&mut self,
1195		network_sender: &mut N,
1196		metrics: &Metrics,
1197		peer_id: PeerId,
1198		view: View,
1199		rng: &mut R,
1200	) where
1201		R: CryptoRng + Rng,
1202	{
1203		gum::trace!(target: LOG_TARGET, ?view, "Peer view change");
1204		let finalized_number = view.finalized_number;
1205
1206		let (old_view, protocol_version) =
1207			if let Some(peer_entry) = self.peer_views.get_mut(&peer_id) {
1208				(Some(std::mem::replace(&mut peer_entry.view, view.clone())), peer_entry.version)
1209			} else {
1210				// This shouldn't happen, but if it does we assume protocol version 3.
1211				gum::warn!(
1212					target: LOG_TARGET,
1213					?peer_id,
1214					?view,
1215					"Peer view change for missing `peer_entry`"
1216				);
1217
1218				(None, ValidationVersion::V3.into())
1219			};
1220
1221		let old_finalized_number = old_view.map(|v| v.finalized_number).unwrap_or(0);
1222
1223		// we want to prune every block known_by peer up to (including) view.finalized_number
1224		let blocks = &mut self.blocks;
1225		// the `BTreeMap::range` is constrained by stored keys
1226		// so the loop won't take ages if the new finalized_number skyrockets
1227		// but we need to make sure the range is not empty, otherwise it will panic
1228		// it shouldn't be, we make sure of this in the network bridge
1229		let range = old_finalized_number..=finalized_number;
1230		if !range.is_empty() && !blocks.is_empty() {
1231			self.blocks_by_number
1232				.range(range)
1233				.flat_map(|(_number, hashes)| hashes)
1234				.for_each(|hash| {
1235					if let Some(entry) = blocks.get_mut(hash) {
1236						entry.known_by.remove(&peer_id);
1237					}
1238				});
1239		}
1240
1241		Self::unify_with_peer(
1242			network_sender,
1243			metrics,
1244			&mut self.blocks,
1245			&self.topologies,
1246			self.peer_views.len(),
1247			peer_id,
1248			protocol_version,
1249			view,
1250			rng,
1251			false,
1252		)
1253		.await;
1254	}
1255
1256	async fn handle_block_finalized<N: overseer::SubsystemSender<NetworkBridgeTxMessage>>(
1257		&mut self,
1258		network_sender: &mut N,
1259		metrics: &Metrics,
1260		finalized_number: BlockNumber,
1261	) {
1262		// we want to prune every block up to (including) finalized_number
1263		// why +1 here?
1264		// split_off returns everything after the given key, including the key
1265		let split_point = finalized_number.saturating_add(1);
1266		let mut old_blocks = self.blocks_by_number.split_off(&split_point);
1267
1268		// after split_off old_blocks actually contains new blocks, we need to swap
1269		std::mem::swap(&mut self.blocks_by_number, &mut old_blocks);
1270
1271		// now that we pruned `self.blocks_by_number`, let's clean up `self.blocks` too
1272		old_blocks.values().flatten().for_each(|relay_block| {
1273			self.recent_outdated_blocks.note_outdated(*relay_block);
1274			if let Some(block_entry) = self.blocks.remove(relay_block) {
1275				self.topologies.dec_session_refs(block_entry.session);
1276			}
1277		});
1278
1279		// If a block was finalized, this means we may need to move our aggression
1280		// forward to the now oldest block(s).
1281		self.enable_aggression(network_sender, Resend::No, metrics).await;
1282	}
1283
1284	// When finality is lagging as a last resort nodes start sending the messages they have
1285	// multiples times. This means it is safe to accept duplicate messages without punishing the
1286	// peer and reduce the reputation and can end up banning the Peer, which in turn will create
1287	// more no-shows.
1288	fn accept_duplicates_from_validators(
1289		blocks_by_number: &BTreeMap<BlockNumber, Vec<Hash>>,
1290		topologies: &SessionGridTopologies,
1291		aggression_config: &AggressionConfig,
1292		entry: &BlockEntry,
1293		peer: PeerId,
1294	) -> bool {
1295		let topology = topologies.get_topology(entry.session);
1296		let min_age = blocks_by_number.iter().next().map(|(num, _)| num);
1297		let max_age = blocks_by_number.iter().rev().next().map(|(num, _)| num);
1298
1299		// Return if we don't have at least 1 block.
1300		let (min_age, max_age) = match (min_age, max_age) {
1301			(Some(min), Some(max)) => (*min, *max),
1302			_ => return false,
1303		};
1304
1305		let age = max_age.saturating_sub(min_age);
1306
1307		aggression_config.should_trigger_aggression(age) &&
1308			topology.map(|topology| topology.is_validator(&peer)).unwrap_or(false)
1309	}
1310
1311	async fn import_and_circulate_assignment<A, N, RA, R>(
1312		&mut self,
1313		approval_voting_sender: &mut A,
1314		network_sender: &mut N,
1315		runtime_api_sender: &mut RA,
1316		metrics: &Metrics,
1317		source: MessageSource,
1318		assignment: IndirectAssignmentCertV2,
1319		claimed_candidate_indices: CandidateBitfield,
1320		rng: &mut R,
1321		assignment_criteria: &(impl AssignmentCriteria + ?Sized),
1322		clock: &(impl Clock + ?Sized),
1323		session_info_provider: &mut RuntimeInfo,
1324	) where
1325		A: overseer::SubsystemSender<ApprovalVotingMessage>,
1326		N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
1327		RA: overseer::SubsystemSender<RuntimeApiMessage>,
1328		R: CryptoRng + Rng,
1329	{
1330		let block_hash = assignment.block_hash;
1331		let validator_index = assignment.validator;
1332
1333		let entry = match self.blocks.get_mut(&block_hash) {
1334			Some(entry) => entry,
1335			None => {
1336				if let Some(peer_id) = source.peer_id() {
1337					gum::trace!(
1338						target: LOG_TARGET,
1339						?peer_id,
1340						hash = ?block_hash,
1341						?validator_index,
1342						"Unexpected assignment",
1343					);
1344					if !self.recent_outdated_blocks.is_recent_outdated(&block_hash) {
1345						modify_reputation(
1346							&mut self.reputation,
1347							network_sender,
1348							peer_id,
1349							COST_UNEXPECTED_MESSAGE,
1350						)
1351						.await;
1352						gum::debug!(target: LOG_TARGET, "Received assignment for invalid block");
1353						metrics.on_assignment_recent_outdated();
1354					}
1355				}
1356				metrics.on_assignment_invalid_block();
1357				return;
1358			},
1359		};
1360
1361		// Compute metadata on the assignment.
1362		let (message_subject, message_kind) = (
1363			MessageSubject(block_hash, claimed_candidate_indices.clone(), validator_index),
1364			MessageKind::Assignment,
1365		);
1366
1367		if let Some(peer_id) = source.peer_id() {
1368			// check if our knowledge of the peer already contains this assignment
1369			match entry.known_by.entry(peer_id) {
1370				hash_map::Entry::Occupied(mut peer_knowledge) => {
1371					let peer_knowledge = peer_knowledge.get_mut();
1372					if peer_knowledge.contains(&message_subject, message_kind) {
1373						// wasn't included before
1374						if !peer_knowledge.received.insert(message_subject.clone(), message_kind) {
1375							if !Self::accept_duplicates_from_validators(
1376								&self.blocks_by_number,
1377								&self.topologies,
1378								&self.aggression_config,
1379								entry,
1380								peer_id,
1381							) {
1382								gum::debug!(
1383									target: LOG_TARGET,
1384									?peer_id,
1385									?message_subject,
1386									"Duplicate assignment",
1387								);
1388
1389								modify_reputation(
1390									&mut self.reputation,
1391									network_sender,
1392									peer_id,
1393									COST_DUPLICATE_MESSAGE,
1394								)
1395								.await;
1396							}
1397
1398							metrics.on_assignment_duplicate();
1399						} else {
1400							gum::trace!(
1401								target: LOG_TARGET,
1402								?peer_id,
1403								hash = ?block_hash,
1404								?validator_index,
1405								?message_subject,
1406								"We sent the message to the peer while peer was sending it to us. Known race condition.",
1407							);
1408						}
1409						return;
1410					}
1411				},
1412				hash_map::Entry::Vacant(_) => {
1413					gum::debug!(
1414						target: LOG_TARGET,
1415						?peer_id,
1416						?message_subject,
1417						"Assignment from a peer is out of view",
1418					);
1419					modify_reputation(
1420						&mut self.reputation,
1421						network_sender,
1422						peer_id,
1423						COST_UNEXPECTED_MESSAGE,
1424					)
1425					.await;
1426					metrics.on_assignment_out_of_view();
1427				},
1428			}
1429
1430			// if the assignment is known to be valid, reward the peer
1431			if entry.knowledge.contains(&message_subject, message_kind) {
1432				modify_reputation(
1433					&mut self.reputation,
1434					network_sender,
1435					peer_id,
1436					BENEFIT_VALID_MESSAGE,
1437				)
1438				.await;
1439				if let Some(peer_knowledge) = entry.known_by.get_mut(&peer_id) {
1440					gum::trace!(target: LOG_TARGET, ?peer_id, ?message_subject, "Known assignment");
1441					peer_knowledge.received.insert(message_subject, message_kind);
1442				}
1443				metrics.on_assignment_good_known();
1444				return;
1445			}
1446
1447			let result = Self::check_assignment_valid(
1448				assignment_criteria,
1449				&entry,
1450				&assignment,
1451				&claimed_candidate_indices,
1452				session_info_provider,
1453				runtime_api_sender,
1454			)
1455			.await;
1456
1457			match result {
1458				Ok(checked_assignment) => {
1459					let current_tranche = clock.tranche_now(self.slot_duration_millis, entry.slot);
1460					let too_far_in_future =
1461						current_tranche + TICK_TOO_FAR_IN_FUTURE as DelayTranche;
1462
1463					if checked_assignment.tranche() >= too_far_in_future {
1464						gum::debug!(
1465							target: LOG_TARGET,
1466							hash = ?block_hash,
1467							?peer_id,
1468							"Got an assignment too far in the future",
1469						);
1470						modify_reputation(
1471							&mut self.reputation,
1472							network_sender,
1473							peer_id,
1474							COST_ASSIGNMENT_TOO_FAR_IN_THE_FUTURE,
1475						)
1476						.await;
1477						metrics.on_assignment_far();
1478
1479						return;
1480					}
1481
1482					approval_voting_sender
1483						.send_message(ApprovalVotingMessage::ImportAssignment(
1484							checked_assignment,
1485							None,
1486						))
1487						.await;
1488					modify_reputation(
1489						&mut self.reputation,
1490						network_sender,
1491						peer_id,
1492						BENEFIT_VALID_MESSAGE_FIRST,
1493					)
1494					.await;
1495					entry.knowledge.insert(message_subject.clone(), message_kind);
1496					if let Some(peer_knowledge) = entry.known_by.get_mut(&peer_id) {
1497						peer_knowledge.received.insert(message_subject.clone(), message_kind);
1498					}
1499				},
1500				Err(error) => {
1501					gum::info!(
1502						target: LOG_TARGET,
1503						hash = ?block_hash,
1504						?peer_id,
1505						?error,
1506						"Got a bad assignment from peer",
1507					);
1508					modify_reputation(
1509						&mut self.reputation,
1510						network_sender,
1511						peer_id,
1512						COST_INVALID_MESSAGE,
1513					)
1514					.await;
1515					metrics.on_assignment_bad();
1516					return;
1517				},
1518			}
1519		} else {
1520			if !entry.knowledge.insert(message_subject.clone(), message_kind) {
1521				// if we already imported an assignment, there is no need to distribute it again
1522				gum::warn!(
1523					target: LOG_TARGET,
1524					?message_subject,
1525					"Importing locally an already known assignment",
1526				);
1527				return;
1528			} else {
1529				gum::debug!(
1530					target: LOG_TARGET,
1531					?message_subject,
1532					"Importing locally a new assignment",
1533				);
1534			}
1535		}
1536
1537		// Invariant: to our knowledge, none of the peers except for the `source` know about the
1538		// assignment.
1539		metrics.on_assignment_imported(&assignment.cert.kind);
1540
1541		let topology = self.topologies.get_topology(entry.session);
1542		let local = source == MessageSource::Local;
1543
1544		let required_routing = topology.map_or(RequiredRouting::PendingTopology, |t| {
1545			t.local_grid_neighbors().required_routing_by_index(validator_index, local)
1546		});
1547		// Peers that we will send the assignment to.
1548		let mut peers = HashSet::new();
1549
1550		let peers_to_route_to = topology
1551			.as_ref()
1552			.map(|t| t.peers_to_route(required_routing))
1553			.unwrap_or_default();
1554
1555		for peer in peers_to_route_to {
1556			if !entry.known_by.contains_key(&peer) {
1557				continue;
1558			}
1559
1560			peers.insert(peer);
1561		}
1562
1563		// All the peers that know the relay chain block.
1564		let peers_to_filter = entry.known_by();
1565
1566		let approval_entry = entry.insert_approval_entry(ApprovalEntry::new(
1567			assignment.clone(),
1568			claimed_candidate_indices.clone(),
1569			ApprovalRouting {
1570				required_routing,
1571				local,
1572				random_routing: Default::default(),
1573				peers_randomly_routed: Default::default(),
1574			},
1575		));
1576
1577		// Dispatch the message to all peers in the routing set which
1578		// know the block.
1579		//
1580		// If the topology isn't known yet (race with networking subsystems)
1581		// then messages will be sent when we get it.
1582
1583		let assignments = vec![(assignment, claimed_candidate_indices.clone())];
1584		let n_peers_total = self.peer_views.len();
1585		let source_peer = source.peer_id();
1586
1587		// Filter destination peers
1588		for peer in peers_to_filter.into_iter() {
1589			if Some(peer) == source_peer {
1590				continue;
1591			}
1592
1593			if peers.contains(&peer) {
1594				continue;
1595			}
1596
1597			if !topology.map(|topology| topology.is_validator(&peer)).unwrap_or(false) {
1598				continue;
1599			}
1600
1601			// Note: at this point, we haven't received the message from any peers
1602			// other than the source peer, and we just got it, so we haven't sent it
1603			// to any peers either.
1604			let route_random =
1605				approval_entry.routing_info().random_routing.sample(n_peers_total, rng);
1606
1607			if route_random {
1608				approval_entry.routing_info_mut().mark_randomly_sent(peer);
1609				peers.insert(peer);
1610			}
1611
1612			if approval_entry.routing_info().random_routing.is_complete() {
1613				break;
1614			}
1615		}
1616
1617		// Add the metadata of the assignment to the knowledge of each peer.
1618		for peer in peers.iter() {
1619			// we already filtered peers above, so this should always be Some
1620			if let Some(peer_knowledge) = entry.known_by.get_mut(peer) {
1621				peer_knowledge.sent.insert(message_subject.clone(), message_kind);
1622			}
1623		}
1624
1625		if !peers.is_empty() {
1626			gum::trace!(
1627				target: LOG_TARGET,
1628				?block_hash,
1629				?claimed_candidate_indices,
1630				local = source.peer_id().is_none(),
1631				num_peers = peers.len(),
1632				"Sending an assignment to peers",
1633			);
1634
1635			let peers = peers
1636				.iter()
1637				.filter_map(|peer_id| {
1638					self.peer_views.get(peer_id).map(|peer_entry| (*peer_id, peer_entry.version))
1639				})
1640				.collect::<Vec<_>>();
1641
1642			send_assignments_batched(network_sender, assignments, &peers).await;
1643		}
1644	}
1645
1646	async fn check_assignment_valid<RA: overseer::SubsystemSender<RuntimeApiMessage>>(
1647		assignment_criteria: &(impl AssignmentCriteria + ?Sized),
1648		entry: &BlockEntry,
1649		assignment: &IndirectAssignmentCertV2,
1650		claimed_candidate_indices: &CandidateBitfield,
1651		runtime_info: &mut RuntimeInfo,
1652		runtime_api_sender: &mut RA,
1653	) -> Result<CheckedIndirectAssignment, InvalidAssignmentError> {
1654		let ExtendedSessionInfo { ref session_info, .. } = runtime_info
1655			.get_session_info_by_index(runtime_api_sender, assignment.block_hash, entry.session)
1656			.await
1657			.map_err(|err| InvalidAssignmentError::SessionInfoNotFound(err))?;
1658
1659		if claimed_candidate_indices.len() > session_info.n_cores as usize {
1660			return Err(InvalidAssignmentError::OversizedClaimedBitfield);
1661		}
1662
1663		let claimed_cores: Vec<CoreIndex> = claimed_candidate_indices
1664			.iter_ones()
1665			.map(|candidate_index| {
1666				entry.candidates_metadata.get(candidate_index).map(|(_, core, _)| *core).ok_or(
1667					InvalidAssignmentError::ClaimedInvalidCandidateIndex {
1668						claimed_index: candidate_index,
1669						max_index: entry.candidates_metadata.len(),
1670					},
1671				)
1672			})
1673			.collect::<Result<Vec<_>, InvalidAssignmentError>>()?;
1674
1675		let Ok(claimed_cores) = claimed_cores.try_into() else {
1676			return Err(InvalidAssignmentError::NoClaimedCandidates);
1677		};
1678
1679		let backing_groups = claimed_candidate_indices
1680			.iter_ones()
1681			.flat_map(|candidate_index| {
1682				entry.candidates_metadata.get(candidate_index).map(|(_, _, group)| *group)
1683			})
1684			.collect::<Vec<_>>();
1685
1686		assignment_criteria
1687			.check_assignment_cert(
1688				claimed_cores,
1689				assignment.validator,
1690				&polkadot_node_primitives::approval::criteria::Config::from(session_info),
1691				entry.vrf_story.clone(),
1692				&assignment.cert,
1693				backing_groups,
1694			)
1695			.map_err(|err| InvalidAssignmentError::CryptoCheckFailed(err))
1696			.map(|tranche| {
1697				CheckedIndirectAssignment::from_checked(
1698					assignment.clone(),
1699					claimed_candidate_indices.clone(),
1700					tranche,
1701				)
1702			})
1703	}
1704	// Checks if an approval can be processed.
1705	// Returns true if we can continue with processing the approval and false otherwise.
1706	async fn check_approval_can_be_processed<
1707		N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
1708	>(
1709		network_sender: &mut N,
1710		assignments_knowledge_key: &Vec<(MessageSubject, MessageKind)>,
1711		approval_knowledge_key: &(MessageSubject, MessageKind),
1712		entry: &mut BlockEntry,
1713		blocks_by_number: &BTreeMap<BlockNumber, Vec<Hash>>,
1714		topologies: &SessionGridTopologies,
1715		aggression_config: &AggressionConfig,
1716		reputation: &mut ReputationAggregator,
1717		peer_id: PeerId,
1718		metrics: &Metrics,
1719	) -> bool {
1720		for message_subject in assignments_knowledge_key {
1721			if !entry.knowledge.contains(&message_subject.0, message_subject.1) {
1722				gum::trace!(
1723					target: LOG_TARGET,
1724					?peer_id,
1725					?message_subject,
1726					"Unknown approval assignment",
1727				);
1728				modify_reputation(reputation, network_sender, peer_id, COST_UNEXPECTED_MESSAGE)
1729					.await;
1730				metrics.on_approval_unknown_assignment();
1731				return false;
1732			}
1733		}
1734
1735		// check if our knowledge of the peer already contains this approval
1736		match entry.known_by.entry(peer_id) {
1737			hash_map::Entry::Occupied(mut knowledge) => {
1738				let peer_knowledge = knowledge.get_mut();
1739				if peer_knowledge.contains(&approval_knowledge_key.0, approval_knowledge_key.1) {
1740					if !peer_knowledge
1741						.received
1742						.insert(approval_knowledge_key.0.clone(), approval_knowledge_key.1)
1743					{
1744						if !Self::accept_duplicates_from_validators(
1745							blocks_by_number,
1746							topologies,
1747							aggression_config,
1748							entry,
1749							peer_id,
1750						) {
1751							gum::trace!(
1752								target: LOG_TARGET,
1753								?peer_id,
1754								?approval_knowledge_key,
1755								"Duplicate approval",
1756							);
1757							modify_reputation(
1758								reputation,
1759								network_sender,
1760								peer_id,
1761								COST_DUPLICATE_MESSAGE,
1762							)
1763							.await;
1764						}
1765						metrics.on_approval_duplicate();
1766					}
1767					return false;
1768				}
1769			},
1770			hash_map::Entry::Vacant(_) => {
1771				gum::debug!(
1772					target: LOG_TARGET,
1773					?peer_id,
1774					?approval_knowledge_key,
1775					"Approval from a peer is out of view",
1776				);
1777				modify_reputation(reputation, network_sender, peer_id, COST_UNEXPECTED_MESSAGE)
1778					.await;
1779				metrics.on_approval_out_of_view();
1780			},
1781		}
1782
1783		if entry.knowledge.contains(&approval_knowledge_key.0, approval_knowledge_key.1) {
1784			if let Some(peer_knowledge) = entry.known_by.get_mut(&peer_id) {
1785				peer_knowledge
1786					.received
1787					.insert(approval_knowledge_key.0.clone(), approval_knowledge_key.1);
1788			}
1789
1790			// We already processed this approval no need to continue.
1791			gum::trace!(target: LOG_TARGET, ?peer_id, ?approval_knowledge_key, "Known approval");
1792			metrics.on_approval_good_known();
1793			modify_reputation(reputation, network_sender, peer_id, BENEFIT_VALID_MESSAGE).await;
1794			false
1795		} else {
1796			true
1797		}
1798	}
1799
1800	async fn import_and_circulate_approval<
1801		N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
1802		A: overseer::SubsystemSender<ApprovalVotingMessage>,
1803		RA: overseer::SubsystemSender<RuntimeApiMessage>,
1804	>(
1805		&mut self,
1806		approval_voting_sender: &mut A,
1807		network_sender: &mut N,
1808		runtime_api_sender: &mut RA,
1809		metrics: &Metrics,
1810		source: MessageSource,
1811		vote: IndirectSignedApprovalVoteV2,
1812		session_info_provider: &mut RuntimeInfo,
1813	) {
1814		let block_hash = vote.block_hash;
1815		let validator_index = vote.validator;
1816		let candidate_indices = &vote.candidate_indices;
1817		let entry = match self.blocks.get_mut(&block_hash) {
1818			Some(entry) if entry.contains_candidates(&vote.candidate_indices) => entry,
1819			_ => {
1820				if let Some(peer_id) = source.peer_id() {
1821					if !self.recent_outdated_blocks.is_recent_outdated(&block_hash) {
1822						gum::debug!(
1823							target: LOG_TARGET,
1824							?peer_id,
1825							?block_hash,
1826							?validator_index,
1827							?candidate_indices,
1828							"Approval from a peer is out of view",
1829						);
1830						modify_reputation(
1831							&mut self.reputation,
1832							network_sender,
1833							peer_id,
1834							COST_UNEXPECTED_MESSAGE,
1835						)
1836						.await;
1837						metrics.on_approval_invalid_block();
1838					} else {
1839						metrics.on_approval_recent_outdated();
1840					}
1841				}
1842				return;
1843			},
1844		};
1845
1846		// compute metadata on the assignment.
1847		let assignments_knowledge_keys = PeerKnowledge::generate_assignments_keys(&vote);
1848		let approval_knwowledge_key = PeerKnowledge::generate_approval_key(&vote);
1849
1850		if let Some(peer_id) = source.peer_id() {
1851			if !Self::check_approval_can_be_processed(
1852				network_sender,
1853				&assignments_knowledge_keys,
1854				&approval_knwowledge_key,
1855				entry,
1856				&self.blocks_by_number,
1857				&self.topologies,
1858				&self.aggression_config,
1859				&mut self.reputation,
1860				peer_id,
1861				metrics,
1862			)
1863			.await
1864			{
1865				return;
1866			}
1867
1868			let result =
1869				Self::check_vote_valid(&vote, &entry, session_info_provider, runtime_api_sender)
1870					.await;
1871
1872			match result {
1873				Ok(vote) => {
1874					approval_voting_sender
1875						.send_message(ApprovalVotingMessage::ImportApproval(vote, None))
1876						.await;
1877
1878					modify_reputation(
1879						&mut self.reputation,
1880						network_sender,
1881						peer_id,
1882						BENEFIT_VALID_MESSAGE_FIRST,
1883					)
1884					.await;
1885
1886					entry
1887						.knowledge
1888						.insert(approval_knwowledge_key.0.clone(), approval_knwowledge_key.1);
1889					if let Some(peer_knowledge) = entry.known_by.get_mut(&peer_id) {
1890						peer_knowledge
1891							.received
1892							.insert(approval_knwowledge_key.0.clone(), approval_knwowledge_key.1);
1893					}
1894				},
1895				Err(err) => {
1896					modify_reputation(
1897						&mut self.reputation,
1898						network_sender,
1899						peer_id,
1900						COST_INVALID_MESSAGE,
1901					)
1902					.await;
1903
1904					gum::info!(
1905						target: LOG_TARGET,
1906						?peer_id,
1907						?err,
1908						"Got a bad approval from peer",
1909					);
1910					metrics.on_approval_bad();
1911					return;
1912				},
1913			}
1914		} else {
1915			if !entry
1916				.knowledge
1917				.insert(approval_knwowledge_key.0.clone(), approval_knwowledge_key.1)
1918			{
1919				// if we already imported all approvals, there is no need to distribute it again
1920				gum::warn!(
1921					target: LOG_TARGET,
1922					"Importing locally an already known approval",
1923				);
1924				return;
1925			} else {
1926				gum::debug!(
1927					target: LOG_TARGET,
1928					"Importing locally a new approval",
1929				);
1930			}
1931		}
1932
1933		let (required_routing, peers_randomly_routed_to) = match entry.note_approval(vote.clone()) {
1934			Ok(required_routing) => required_routing,
1935			Err(err) => {
1936				gum::warn!(
1937					target: LOG_TARGET,
1938					hash = ?block_hash,
1939					validator_index = ?vote.validator,
1940					candidate_bitfield = ?vote.candidate_indices,
1941					?err,
1942					"Possible bug: Vote import failed",
1943				);
1944				metrics.on_approval_bug();
1945				return;
1946			},
1947		};
1948
1949		// Invariant: to our knowledge, none of the peers except for the `source` know about the
1950		// approval.
1951		metrics.on_approval_imported();
1952
1953		// Dispatch a ApprovalDistributionV3Message::Approval(vote)
1954		// to all peers required by the topology, with the exception of the source peer.
1955		let topology = self.topologies.get_topology(entry.session);
1956		let source_peer = source.peer_id();
1957
1958		let peer_filter = move |peer| {
1959			if Some(peer) == source_peer.as_ref() {
1960				return false;
1961			}
1962
1963			// Here we're leaning on a few behaviors of assignment propagation:
1964			//   1. At this point, the only peer we're aware of which has the approval message is
1965			//      the source peer.
1966			//   2. We have sent the assignment message to every peer in the required routing which
1967			//      is aware of this block _unless_ the peer we originally received the assignment
1968			//      from was part of the required routing. In that case, we've sent the assignment
1969			//      to all aware peers in the required routing _except_ the original source of the
1970			//      assignment. Hence the `in_topology_check`.
1971			//   3. Any randomly selected peers have been sent the assignment already.
1972			let in_topology = topology
1973				.map_or(false, |t| t.local_grid_neighbors().route_to_peer(required_routing, peer));
1974			in_topology || peers_randomly_routed_to.contains(peer)
1975		};
1976
1977		let peers = entry
1978			.known_by
1979			.iter()
1980			.filter(|(p, _)| peer_filter(p))
1981			.filter_map(|(p, _)| self.peer_views.get(p).map(|entry| (*p, entry.version)))
1982			.collect::<Vec<_>>();
1983
1984		// Add the metadata of the assignment to the knowledge of each peer.
1985		for peer in peers.iter() {
1986			// we already filtered peers above, so this should always be Some
1987			if let Some(entry) = entry.known_by.get_mut(&peer.0) {
1988				entry.sent.insert(approval_knwowledge_key.0.clone(), approval_knwowledge_key.1);
1989			}
1990		}
1991
1992		if !peers.is_empty() {
1993			let approvals = vec![vote];
1994			gum::trace!(
1995				target: LOG_TARGET,
1996				?block_hash,
1997				local = source.peer_id().is_none(),
1998				num_peers = peers.len(),
1999				"Sending an approval to peers",
2000			);
2001			send_approvals_batched(network_sender, approvals, &peers).await;
2002		}
2003	}
2004
2005	// Checks if the approval vote is valid.
2006	async fn check_vote_valid<RA: overseer::SubsystemSender<RuntimeApiMessage>>(
2007		vote: &IndirectSignedApprovalVoteV2,
2008		entry: &BlockEntry,
2009		runtime_info: &mut RuntimeInfo,
2010		runtime_api_sender: &mut RA,
2011	) -> Result<CheckedIndirectSignedApprovalVote, InvalidVoteError> {
2012		if vote.candidate_indices.len() > entry.candidates_metadata.len() {
2013			return Err(InvalidVoteError::CandidateIndexOutOfBounds);
2014		}
2015
2016		let candidate_hashes = vote
2017			.candidate_indices
2018			.iter_ones()
2019			.flat_map(|candidate_index| {
2020				entry
2021					.candidates_metadata
2022					.get(candidate_index)
2023					.map(|(candidate_hash, _, _)| *candidate_hash)
2024			})
2025			.collect::<Vec<_>>();
2026
2027		let ExtendedSessionInfo { ref session_info, ref approval_voting_params, .. } = runtime_info
2028			.get_session_info_by_index(runtime_api_sender, vote.block_hash, entry.session)
2029			.await
2030			.map_err(|err| InvalidVoteError::SessionInfoNotFound(err))?;
2031
2032		// Enforce the runtime's coalescing limit: reject votes coalescing more candidates than
2033		// `max_approval_coalesce_count`.
2034		if candidate_hashes.len() > approval_voting_params.max_approval_coalesce_count as usize {
2035			return Err(InvalidVoteError::TooManyCandidates);
2036		}
2037
2038		let pubkey = session_info
2039			.validators
2040			.get(vote.validator)
2041			.ok_or(InvalidVoteError::ValidatorIndexOutOfBounds)?;
2042		let candidate_hashes: BoundedVec<CandidateHash, ConstU32<{ MAX_COALESCE_APPROVALS }>> =
2043			candidate_hashes.try_into().map_err(|_| InvalidVoteError::TooManyCandidates)?;
2044		let first_candidate =
2045			*candidate_hashes.first().ok_or(InvalidVoteError::CandidateHashNotFound)?;
2046		DisputeStatement::Valid(ValidDisputeStatementKind::ApprovalCheckingMultipleCandidates(
2047			candidate_hashes,
2048		))
2049		.check_signature(&pubkey, first_candidate, entry.session, &vote.signature)
2050		.map_err(|_| InvalidVoteError::InvalidSignature)
2051		.map(|_| CheckedIndirectSignedApprovalVote::from_checked(vote.clone()))
2052	}
2053
2054	/// Retrieve approval signatures from state for the given relay block/indices:
2055	fn get_approval_signatures(
2056		&mut self,
2057		indices: HashSet<(Hash, CandidateIndex)>,
2058	) -> HashMap<ValidatorIndex, (Hash, Vec<CandidateIndex>, ValidatorSignature)> {
2059		let mut all_sigs = HashMap::new();
2060		for (hash, index) in indices {
2061			let block_entry = match self.blocks.get(&hash) {
2062				None => {
2063					gum::debug!(
2064						target: LOG_TARGET,
2065						?hash,
2066						"`get_approval_signatures`: could not find block entry for given hash!"
2067					);
2068					continue;
2069				},
2070				Some(e) => e,
2071			};
2072
2073			let sigs = block_entry.approval_votes(index).into_iter().map(|approval| {
2074				(
2075					approval.validator,
2076					(
2077						hash,
2078						approval
2079							.candidate_indices
2080							.iter_ones()
2081							.map(|val| val as CandidateIndex)
2082							.collect_vec(),
2083						approval.signature,
2084					),
2085				)
2086			});
2087			all_sigs.extend(sigs);
2088		}
2089		all_sigs
2090	}
2091
2092	async fn unify_with_peer(
2093		sender: &mut impl overseer::SubsystemSender<NetworkBridgeTxMessage>,
2094		metrics: &Metrics,
2095		entries: &mut HashMap<Hash, BlockEntry>,
2096		topologies: &SessionGridTopologies,
2097		total_peers: usize,
2098		peer_id: PeerId,
2099		protocol_version: ProtocolVersion,
2100		view: View,
2101		rng: &mut (impl CryptoRng + Rng),
2102		retry_known_blocks: bool,
2103	) {
2104		metrics.on_unify_with_peer();
2105		let _timer = metrics.time_unify_with_peer();
2106
2107		let mut assignments_to_send = Vec::new();
2108		let mut approvals_to_send = Vec::new();
2109
2110		let view_finalized_number = view.finalized_number;
2111		for head in view.into_iter() {
2112			let mut block = head;
2113
2114			// Walk the chain back to last finalized block of the peer view.
2115			loop {
2116				let entry = match entries.get_mut(&block) {
2117					Some(entry) if entry.number > view_finalized_number => entry,
2118					_ => break,
2119				};
2120
2121				// Any peer which is in the `known_by` see and we know its peer_id authority id
2122				// mapping has already been sent all messages it's meant to get for that block and
2123				// all in-scope prior blocks. In case, we just learnt about its peer_id
2124				// authority-id mapping we have to retry sending the messages that should be sent
2125				// to it for all un-finalized blocks.
2126				if entry.known_by.contains_key(&peer_id) && !retry_known_blocks {
2127					break;
2128				}
2129
2130				let peer_knowledge = entry.known_by.entry(peer_id).or_default();
2131				let topology = topologies.get_topology(entry.session);
2132
2133				// We want to iterate the `approval_entries` of the block entry as these contain
2134				// all assignments that also link all approval votes.
2135				for approval_entry in entry.approval_entries.values_mut() {
2136					// Propagate the message to all peers in the required routing set OR
2137					// randomly sample peers.
2138					{
2139						let required_routing = approval_entry.routing_info().required_routing;
2140						let routing_info = &mut approval_entry.routing_info_mut();
2141						let rng = &mut *rng;
2142						let mut peer_filter = move |peer_id| {
2143							let in_topology = topology.as_ref().map_or(false, |t| {
2144								t.local_grid_neighbors().route_to_peer(required_routing, peer_id)
2145							});
2146							in_topology || {
2147								if !topology
2148									.map(|topology| topology.is_validator(peer_id))
2149									.unwrap_or(false)
2150								{
2151									return false;
2152								}
2153
2154								let route_random =
2155									routing_info.random_routing.sample(total_peers, rng);
2156								if route_random {
2157									routing_info.mark_randomly_sent(*peer_id);
2158								}
2159
2160								route_random
2161							}
2162						};
2163
2164						if !peer_filter(&peer_id) {
2165							continue;
2166						}
2167					}
2168
2169					let assignment_message = approval_entry.assignment();
2170					let approval_messages = approval_entry.approvals();
2171					let (assignment_knowledge, message_kind) =
2172						approval_entry.create_assignment_knowledge(block);
2173
2174					// Only send stuff a peer doesn't know in the context of a relay chain
2175					// block.
2176					if !peer_knowledge.contains(&assignment_knowledge, message_kind) {
2177						peer_knowledge.sent.insert(assignment_knowledge, message_kind);
2178						assignments_to_send.push(assignment_message);
2179					}
2180
2181					// Filter approval votes.
2182					for approval_message in approval_messages {
2183						let approval_knowledge =
2184							PeerKnowledge::generate_approval_key(&approval_message);
2185
2186						if !peer_knowledge.contains(&approval_knowledge.0, approval_knowledge.1) {
2187							approvals_to_send.push(approval_message);
2188							peer_knowledge.sent.insert(approval_knowledge.0, approval_knowledge.1);
2189						}
2190					}
2191				}
2192
2193				block = entry.parent_hash;
2194			}
2195		}
2196
2197		if !assignments_to_send.is_empty() {
2198			gum::trace!(
2199				target: LOG_TARGET,
2200				?peer_id,
2201				?protocol_version,
2202				num = assignments_to_send.len(),
2203				"Sending assignments to unified peer",
2204			);
2205
2206			send_assignments_batched(
2207				sender,
2208				assignments_to_send,
2209				&vec![(peer_id, protocol_version)],
2210			)
2211			.await;
2212		}
2213
2214		if !approvals_to_send.is_empty() {
2215			gum::trace!(
2216				target: LOG_TARGET,
2217				?peer_id,
2218				?protocol_version,
2219				num = approvals_to_send.len(),
2220				"Sending approvals to unified peer",
2221			);
2222
2223			send_approvals_batched(sender, approvals_to_send, &vec![(peer_id, protocol_version)])
2224				.await;
2225		}
2226	}
2227
2228	// It is very important that aggression starts with oldest unfinalized block, rather than oldest
2229	// unapproved block. Using the gossip approach to distribute potentially
2230	// missing votes to validators requires that we always trigger on finality lag, even if
2231	// we have have the approval lag value. The reason for this, is to avoid finality stall
2232	// when more than 1/3 nodes go offline for a period o time. When they come back
2233	// there wouldn't get any of the approvals since the on-line nodes would never trigger
2234	// aggression as they have approved all the candidates and don't detect any approval lag.
2235	//
2236	// In order to switch to using approval lag as a trigger we need a request/response protocol
2237	// to fetch votes from validators rather than use gossip.
2238	async fn enable_aggression<N: overseer::SubsystemSender<NetworkBridgeTxMessage>>(
2239		&mut self,
2240		network_sender: &mut N,
2241		resend: Resend,
2242		metrics: &Metrics,
2243	) {
2244		let config = self.aggression_config.clone();
2245		let min_age = self.blocks_by_number.iter().next().map(|(num, _)| num);
2246		let max_age = self.blocks_by_number.iter().rev().next().map(|(num, _)| num);
2247
2248		// Return if we don't have at least 1 block.
2249		let (min_age, max_age) = match (min_age, max_age) {
2250			(Some(min), Some(max)) => (*min, *max),
2251			_ => return, // empty.
2252		};
2253
2254		let age = max_age.saturating_sub(min_age);
2255
2256		// Trigger on approval checking lag.
2257		if !self.aggression_config.should_trigger_aggression(age) {
2258			gum::trace!(
2259				target: LOG_TARGET,
2260				approval_checking_lag = self.approval_checking_lag,
2261				age,
2262				"Aggression not enabled",
2263			);
2264			return;
2265		}
2266		gum::debug!(target: LOG_TARGET, min_age, max_age, "Aggression enabled",);
2267
2268		adjust_required_routing_and_propagate(
2269			network_sender,
2270			&mut self.blocks,
2271			&self.topologies,
2272			|block_entry| {
2273				let block_age = max_age - block_entry.number;
2274				// We want to resend only for blocks of min_age, there is no point in
2275				// resending for blocks newer than that, because we are just going to create load
2276				// and not gain anything.
2277				let diff_from_min_age = block_entry.number - min_age;
2278
2279				// We want to back-off on resending for blocks that have been resent recently, to
2280				// give time for nodes to process all the extra messages, if we still have not
2281				// finalized we are going to resend again after unfinalized_period * 2 since the
2282				// last resend.
2283				let blocks_since_last_sent = block_entry
2284					.last_resent_at_block_number
2285					.map(|last_resent_at_block_number| max_age - last_resent_at_block_number);
2286
2287				let can_resend_at_this_age = blocks_since_last_sent
2288					.zip(config.resend_unfinalized_period)
2289					.map(|(blocks_since_last_sent, unfinalized_period)| {
2290						blocks_since_last_sent >= unfinalized_period * 2
2291					})
2292					.unwrap_or(true);
2293
2294				if resend == Resend::Yes &&
2295					config.resend_unfinalized_period.as_ref().map_or(false, |p| {
2296						block_age > 0 &&
2297							block_age % p == 0 && diff_from_min_age == 0 &&
2298							can_resend_at_this_age
2299					}) {
2300					// Retry sending to all peers.
2301					for (_, knowledge) in block_entry.known_by.iter_mut() {
2302						knowledge.sent = Knowledge::default();
2303					}
2304					block_entry.last_resent_at_block_number = Some(max_age);
2305					gum::debug!(
2306						target: LOG_TARGET,
2307						block_number = ?block_entry.number,
2308						?max_age,
2309						"Aggression enabled with resend for block",
2310					);
2311					true
2312				} else {
2313					false
2314				}
2315			},
2316			|required_routing, _, _| *required_routing,
2317			&self.peer_views,
2318		)
2319		.await;
2320
2321		adjust_required_routing_and_propagate(
2322			network_sender,
2323			&mut self.blocks,
2324			&self.topologies,
2325			|block_entry| {
2326				// Ramp up aggression only for the very oldest block(s).
2327				// Approval voting can get stuck on a single block preventing
2328				// its descendants from being finalized. Waste minimal bandwidth
2329				// this way. Also, disputes might prevent finality - again, nothing
2330				// to waste bandwidth on newer blocks for.
2331				block_entry.number == min_age
2332			},
2333			|required_routing, local, _| {
2334				// It's a bit surprising not to have a topology at this age.
2335				if *required_routing == RequiredRouting::PendingTopology {
2336					gum::debug!(
2337						target: LOG_TARGET,
2338						lag = ?self.approval_checking_lag,
2339						"Encountered old block pending gossip topology",
2340					);
2341					return *required_routing;
2342				}
2343
2344				let mut new_required_routing = *required_routing;
2345
2346				if config.l1_threshold.as_ref().map_or(false, |t| &age >= t) {
2347					// Message originator sends to everyone.
2348					if local && new_required_routing != RequiredRouting::All {
2349						metrics.on_aggression_l1();
2350						new_required_routing = RequiredRouting::All;
2351					}
2352				}
2353
2354				if config.l2_threshold.as_ref().map_or(false, |t| &age >= t) {
2355					// Message originator sends to everyone. Everyone else sends to XY.
2356					if !local && new_required_routing != RequiredRouting::GridXY {
2357						metrics.on_aggression_l2();
2358						new_required_routing = RequiredRouting::GridXY;
2359					}
2360				}
2361				new_required_routing
2362			},
2363			&self.peer_views,
2364		)
2365		.await;
2366	}
2367
2368	// Filter out oversized candidate and certificate core bitfields.
2369	// For each invalid assignment we also punish the peer.
2370	async fn sanitize_v2_assignments(
2371		&mut self,
2372		peer_id: PeerId,
2373		sender: &mut impl overseer::SubsystemSender<NetworkBridgeTxMessage>,
2374		assignments: Vec<(IndirectAssignmentCertV2, CandidateBitfield)>,
2375	) -> Vec<(IndirectAssignmentCertV2, CandidateBitfield)> {
2376		let mut sanitized_assignments = Vec::new();
2377		for (cert, candidate_bitfield) in assignments.into_iter() {
2378			let cert_bitfield_bits = match &cert.cert.kind {
2379				AssignmentCertKindV2::RelayVRFDelay { core_index } => core_index.0 as usize + 1,
2380				AssignmentCertKindV2::RelayVRFModuloCompact { core_bitfield } => {
2381					core_bitfield.len()
2382				},
2383			};
2384
2385			let candidate_bitfield_bits = candidate_bitfield.len();
2386
2387			// Our bitfield has `Lsb0`.
2388			let msb = candidate_bitfield_bits - 1;
2389
2390			// Ensure bitfields length under hard limit.
2391			if cert_bitfield_bits > MAX_BITFIELD_SIZE
2392				|| candidate_bitfield_bits > MAX_BITFIELD_SIZE
2393				// Ensure minimum bitfield size - MSB needs to be one.
2394				|| !candidate_bitfield.bit_at(msb.as_bit_index())
2395			{
2396				// Punish the peer for the invalid message.
2397				modify_reputation(&mut self.reputation, sender, peer_id, COST_OVERSIZED_BITFIELD)
2398					.await;
2399				for candidate_index in candidate_bitfield.iter_ones() {
2400					gum::debug!(target: LOG_TARGET, block_hash = ?cert.block_hash, ?candidate_index, validator_index = ?cert.validator, "Bad assignment v2, oversized bitfield");
2401				}
2402			} else {
2403				sanitized_assignments.push((cert, candidate_bitfield))
2404			}
2405		}
2406
2407		sanitized_assignments
2408	}
2409
2410	// Filter out obviously invalid candidate indices.
2411	async fn sanitize_v2_approvals(
2412		&mut self,
2413		peer_id: PeerId,
2414		sender: &mut impl overseer::SubsystemSender<NetworkBridgeTxMessage>,
2415		approval: Vec<IndirectSignedApprovalVoteV2>,
2416	) -> Vec<IndirectSignedApprovalVoteV2> {
2417		let mut sanitized_approvals = Vec::new();
2418		for approval in approval.into_iter() {
2419			let has_no_approved_candidates = approval.candidate_indices.first_one().is_none();
2420			if approval.candidate_indices.len() as usize > MAX_BITFIELD_SIZE ||
2421				has_no_approved_candidates
2422			{
2423				// Punish the peer for the invalid message.
2424				modify_reputation(
2425					&mut self.reputation,
2426					sender,
2427					peer_id,
2428					if has_no_approved_candidates {
2429						COST_INVALID_MESSAGE
2430					} else {
2431						COST_OVERSIZED_BITFIELD
2432					},
2433				)
2434				.await;
2435				gum::debug!(
2436					target: LOG_TARGET,
2437					block_hash = ?approval.block_hash,
2438					candidate_indices_len = ?approval.candidate_indices.len(),
2439					"Bad approval v2, invalid candidate indices size"
2440				);
2441			} else {
2442				sanitized_approvals.push(approval)
2443			}
2444		}
2445
2446		sanitized_approvals
2447	}
2448}
2449
2450// This adjusts the required routing of messages in blocks that pass the block filter
2451// according to the modifier function given.
2452//
2453// The modifier accepts as inputs the current required-routing state, whether
2454// the message is locally originating, and the validator index of the message issuer.
2455//
2456// Then, if the topology is known, this propagates messages to all peers in the required
2457// routing set which are aware of the block. Peers which are unaware of the block
2458// will have the message sent when it enters their view in `unify_with_peer`.
2459//
2460// Note that the required routing of a message can be modified even if the
2461// topology is unknown yet.
2462#[overseer::contextbounds(ApprovalDistribution, prefix = self::overseer)]
2463async fn adjust_required_routing_and_propagate<
2464	N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
2465	BlockFilter,
2466	RoutingModifier,
2467>(
2468	network_sender: &mut N,
2469	blocks: &mut HashMap<Hash, BlockEntry>,
2470	topologies: &SessionGridTopologies,
2471	block_filter: BlockFilter,
2472	routing_modifier: RoutingModifier,
2473	peer_views: &HashMap<PeerId, PeerEntry>,
2474) where
2475	BlockFilter: Fn(&mut BlockEntry) -> bool,
2476	RoutingModifier: Fn(&RequiredRouting, bool, &ValidatorIndex) -> RequiredRouting,
2477{
2478	let mut peer_assignments = HashMap::new();
2479	let mut peer_approvals = HashMap::new();
2480
2481	// Iterate all blocks in the session, producing payloads
2482	// for each connected peer.
2483	for (block_hash, block_entry) in blocks {
2484		if !block_filter(block_entry) {
2485			continue;
2486		}
2487
2488		let topology = match topologies.get_topology(block_entry.session) {
2489			Some(t) => t,
2490			None => continue,
2491		};
2492
2493		// We just need to iterate the `approval_entries` of the block entry as these contain all
2494		// assignments that also link all approval votes.
2495		for approval_entry in block_entry.approval_entries.values_mut() {
2496			let new_required_routing = routing_modifier(
2497				&approval_entry.routing_info().required_routing,
2498				approval_entry.routing_info().local,
2499				&approval_entry.validator_index(),
2500			);
2501
2502			approval_entry.update_required_routing(new_required_routing);
2503
2504			if approval_entry.routing_info().required_routing.is_empty() {
2505				continue;
2506			}
2507
2508			let assignment_message = approval_entry.assignment();
2509			let approval_messages = approval_entry.approvals();
2510			let (assignment_knowledge, message_kind) =
2511				approval_entry.create_assignment_knowledge(*block_hash);
2512
2513			for (peer, peer_knowledge) in &mut block_entry.known_by {
2514				if !topology
2515					.local_grid_neighbors()
2516					.route_to_peer(approval_entry.routing_info().required_routing, peer)
2517				{
2518					continue;
2519				}
2520
2521				// Only send stuff a peer doesn't know in the context of a relay chain block.
2522				if !peer_knowledge.contains(&assignment_knowledge, message_kind) {
2523					peer_knowledge.sent.insert(assignment_knowledge.clone(), message_kind);
2524					peer_assignments
2525						.entry(*peer)
2526						.or_insert_with(Vec::new)
2527						.push(assignment_message.clone());
2528				}
2529
2530				// Filter approval votes.
2531				for approval_message in &approval_messages {
2532					let approval_knowledge = PeerKnowledge::generate_approval_key(approval_message);
2533
2534					if !peer_knowledge.contains(&approval_knowledge.0, approval_knowledge.1) {
2535						peer_knowledge.sent.insert(approval_knowledge.0, approval_knowledge.1);
2536						peer_approvals
2537							.entry(*peer)
2538							.or_insert_with(Vec::new)
2539							.push(approval_message.clone());
2540					}
2541				}
2542			}
2543		}
2544	}
2545
2546	// Send messages in accumulated packets, assignments preceding approvals.
2547	for (peer, assignments_packet) in peer_assignments {
2548		if let Some(peer_view) = peer_views.get(&peer) {
2549			send_assignments_batched(
2550				network_sender,
2551				assignments_packet,
2552				&vec![(peer, peer_view.version)],
2553			)
2554			.await;
2555		} else {
2556			// This should never happen.
2557			gum::warn!(target: LOG_TARGET, ?peer, "Unknown protocol version for peer",);
2558		}
2559	}
2560
2561	for (peer, approvals_packet) in peer_approvals {
2562		if let Some(peer_view) = peer_views.get(&peer) {
2563			send_approvals_batched(
2564				network_sender,
2565				approvals_packet,
2566				&vec![(peer, peer_view.version)],
2567			)
2568			.await;
2569		} else {
2570			// This should never happen.
2571			gum::warn!(target: LOG_TARGET, ?peer, "Unknown protocol version for peer",);
2572		}
2573	}
2574}
2575
2576/// Modify the reputation of a peer based on its behavior.
2577async fn modify_reputation(
2578	reputation: &mut ReputationAggregator,
2579	sender: &mut impl overseer::SubsystemSender<NetworkBridgeTxMessage>,
2580	peer_id: PeerId,
2581	rep: Rep,
2582) {
2583	gum::trace!(
2584		target: LOG_TARGET,
2585		reputation = ?rep,
2586		?peer_id,
2587		"Reputation change for peer",
2588	);
2589	reputation.modify(sender, peer_id, rep).await;
2590}
2591
2592#[overseer::contextbounds(ApprovalDistribution, prefix = self::overseer)]
2593impl ApprovalDistribution {
2594	/// Create a new instance of the [`ApprovalDistribution`] subsystem.
2595	pub fn new(
2596		metrics: Metrics,
2597		slot_duration_millis: u64,
2598		assignment_criteria: Arc<dyn AssignmentCriteria + Send + Sync>,
2599	) -> Self {
2600		Self::new_with_clock(
2601			metrics,
2602			slot_duration_millis,
2603			Arc::new(SystemClock),
2604			assignment_criteria,
2605		)
2606	}
2607
2608	/// Create a new instance of the [`ApprovalDistribution`] subsystem, with a custom clock.
2609	pub fn new_with_clock(
2610		metrics: Metrics,
2611		slot_duration_millis: u64,
2612		clock: Arc<dyn Clock + Send + Sync>,
2613		assignment_criteria: Arc<dyn AssignmentCriteria + Send + Sync>,
2614	) -> Self {
2615		Self { metrics, slot_duration_millis, clock, assignment_criteria }
2616	}
2617
2618	async fn run<Context>(self, ctx: Context) {
2619		let mut state =
2620			State { slot_duration_millis: self.slot_duration_millis, ..Default::default() };
2621		// According to the docs of `rand`, this is a ChaCha12 RNG in practice
2622		// and will always be chosen for strong performance and security properties.
2623		let mut rng = rand::rngs::StdRng::from_entropy();
2624		let mut session_info_provider = RuntimeInfo::new_with_config(RuntimeInfoConfig {
2625			keystore: None,
2626			session_cache_lru_size: DISPUTE_WINDOW.get(),
2627		});
2628
2629		self.run_inner(
2630			ctx,
2631			&mut state,
2632			REPUTATION_CHANGE_INTERVAL,
2633			&mut rng,
2634			&mut session_info_provider,
2635		)
2636		.await
2637	}
2638
2639	/// Used for testing.
2640	async fn run_inner<Context>(
2641		self,
2642		mut ctx: Context,
2643		state: &mut State,
2644		reputation_interval: Duration,
2645		rng: &mut (impl CryptoRng + Rng),
2646		session_info_provider: &mut RuntimeInfo,
2647	) {
2648		let new_reputation_delay = || futures_timer::Delay::new(reputation_interval).fuse();
2649		let mut reputation_delay = new_reputation_delay();
2650		let mut approval_voting_sender = ctx.sender().clone();
2651		let mut network_sender = ctx.sender().clone();
2652		let mut runtime_api_sender = ctx.sender().clone();
2653
2654		loop {
2655			select! {
2656				_ = reputation_delay => {
2657					state.reputation.send(ctx.sender()).await;
2658					reputation_delay = new_reputation_delay();
2659				},
2660				message = ctx.recv().fuse() => {
2661					let message = match message {
2662						Ok(message) => message,
2663						Err(e) => {
2664							gum::debug!(target: LOG_TARGET, err = ?e, "Failed to receive a message from Overseer, exiting");
2665							return
2666						},
2667					};
2668
2669					if self.handle_from_orchestra(message, &mut approval_voting_sender, &mut network_sender, &mut runtime_api_sender, state, rng, session_info_provider).await {
2670						return;
2671					}
2672
2673				},
2674			}
2675		}
2676	}
2677
2678	/// Handles a from orchestra message received by approval distribution subystem.
2679	///
2680	/// Returns `true` if the subsystem should be stopped.
2681	pub async fn handle_from_orchestra<
2682		N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
2683		A: overseer::SubsystemSender<ApprovalVotingMessage>,
2684		RA: overseer::SubsystemSender<RuntimeApiMessage>,
2685	>(
2686		&self,
2687		message: FromOrchestra<ApprovalDistributionMessage>,
2688		approval_voting_sender: &mut A,
2689		network_sender: &mut N,
2690		runtime_api_sender: &mut RA,
2691		state: &mut State,
2692		rng: &mut (impl CryptoRng + Rng),
2693		session_info_provider: &mut RuntimeInfo,
2694	) -> bool {
2695		match message {
2696			FromOrchestra::Communication { msg } => {
2697				Self::handle_incoming(
2698					approval_voting_sender,
2699					network_sender,
2700					runtime_api_sender,
2701					state,
2702					msg,
2703					&self.metrics,
2704					rng,
2705					self.assignment_criteria.as_ref(),
2706					self.clock.as_ref(),
2707					session_info_provider,
2708				)
2709				.await
2710			},
2711			FromOrchestra::Signal(OverseerSignal::ActiveLeaves(_update)) => {
2712				gum::trace!(target: LOG_TARGET, "active leaves signal (ignored)");
2713				// the relay chain blocks relevant to the approval subsystems
2714				// are those that are available, but not finalized yet
2715				// activated and deactivated heads hence are irrelevant to this subsystem, other
2716				// than for tracing purposes.
2717			},
2718			FromOrchestra::Signal(OverseerSignal::BlockFinalized(_hash, number)) => {
2719				gum::trace!(target: LOG_TARGET, number = %number, "finalized signal");
2720				state.handle_block_finalized(network_sender, &self.metrics, number).await;
2721			},
2722			FromOrchestra::Signal(OverseerSignal::Conclude) => return true,
2723		}
2724		false
2725	}
2726
2727	async fn handle_incoming<
2728		N: overseer::SubsystemSender<NetworkBridgeTxMessage>,
2729		A: overseer::SubsystemSender<ApprovalVotingMessage>,
2730		RA: overseer::SubsystemSender<RuntimeApiMessage>,
2731	>(
2732		approval_voting_sender: &mut A,
2733		network_sender: &mut N,
2734		runtime_api_sender: &mut RA,
2735		state: &mut State,
2736		msg: ApprovalDistributionMessage,
2737		metrics: &Metrics,
2738		rng: &mut (impl CryptoRng + Rng),
2739		assignment_criteria: &(impl AssignmentCriteria + ?Sized),
2740		clock: &(impl Clock + ?Sized),
2741		session_info_provider: &mut RuntimeInfo,
2742	) {
2743		match msg {
2744			ApprovalDistributionMessage::NetworkBridgeUpdate(event) => {
2745				state
2746					.handle_network_msg(
2747						approval_voting_sender,
2748						network_sender,
2749						runtime_api_sender,
2750						metrics,
2751						event,
2752						rng,
2753						assignment_criteria,
2754						clock,
2755						session_info_provider,
2756					)
2757					.await;
2758			},
2759			ApprovalDistributionMessage::NewBlocks(metas) => {
2760				state
2761					.handle_new_blocks(
2762						approval_voting_sender,
2763						network_sender,
2764						runtime_api_sender,
2765						metrics,
2766						metas,
2767						rng,
2768						assignment_criteria,
2769						clock,
2770						session_info_provider,
2771					)
2772					.await;
2773			},
2774			ApprovalDistributionMessage::DistributeAssignment(cert, candidate_indices) => {
2775				gum::debug!(
2776					target: LOG_TARGET,
2777					?candidate_indices,
2778					block_hash = ?cert.block_hash,
2779					assignment_kind = ?cert.cert.kind,
2780					"Distributing our assignment on candidates",
2781				);
2782
2783				state
2784					.import_and_circulate_assignment(
2785						approval_voting_sender,
2786						network_sender,
2787						runtime_api_sender,
2788						&metrics,
2789						MessageSource::Local,
2790						cert,
2791						candidate_indices,
2792						rng,
2793						assignment_criteria,
2794						clock,
2795						session_info_provider,
2796					)
2797					.await;
2798			},
2799			ApprovalDistributionMessage::DistributeApproval(vote) => {
2800				gum::debug!(
2801					target: LOG_TARGET,
2802					"Distributing our approval vote on candidate (block={}, index={:?})",
2803					vote.block_hash,
2804					vote.candidate_indices,
2805				);
2806
2807				state
2808					.import_and_circulate_approval(
2809						approval_voting_sender,
2810						network_sender,
2811						runtime_api_sender,
2812						metrics,
2813						MessageSource::Local,
2814						vote,
2815						session_info_provider,
2816					)
2817					.await;
2818			},
2819			ApprovalDistributionMessage::GetApprovalSignatures(indices, tx) => {
2820				let sigs = state.get_approval_signatures(indices);
2821				if let Err(_) = tx.send(sigs) {
2822					gum::debug!(
2823						target: LOG_TARGET,
2824						"Sending back approval signatures failed, oneshot got closed"
2825					);
2826				}
2827			},
2828			ApprovalDistributionMessage::ApprovalCheckingLagUpdate(lag) => {
2829				gum::debug!(target: LOG_TARGET, lag, "Received `ApprovalCheckingLagUpdate`");
2830				state.approval_checking_lag = lag;
2831			},
2832		}
2833	}
2834}
2835
2836#[overseer::subsystem(ApprovalDistribution, error=SubsystemError, prefix=self::overseer)]
2837impl<Context> ApprovalDistribution {
2838	fn start(self, ctx: Context) -> SpawnedSubsystem {
2839		let future = self.run(ctx).map(|_| Ok(())).boxed();
2840
2841		SpawnedSubsystem { name: "approval-distribution-subsystem", future }
2842	}
2843}
2844
2845/// Ensures the batch size is always at least 1 element.
2846const fn ensure_size_not_zero(size: usize) -> usize {
2847	if 0 == size {
2848		panic!("Batch size must be at least 1 (MAX_NOTIFICATION_SIZE constant is too low)",);
2849	}
2850
2851	size
2852}
2853
2854/// The maximum amount of assignments per batch is 33% of maximum allowed by protocol.
2855/// This is an arbitrary value. Bumping this up increases the maximum amount of approvals or
2856/// assignments we send in a single message to peers. Exceeding `MAX_NOTIFICATION_SIZE` will violate
2857/// the protocol configuration.
2858pub const MAX_ASSIGNMENT_BATCH_SIZE: usize = ensure_size_not_zero(
2859	MAX_NOTIFICATION_SIZE as usize /
2860		std::mem::size_of::<(IndirectAssignmentCertV2, CandidateIndex)>() /
2861		3,
2862);
2863
2864/// The maximum amount of approvals per batch is 33% of maximum allowed by protocol.
2865pub const MAX_APPROVAL_BATCH_SIZE: usize = ensure_size_not_zero(
2866	MAX_NOTIFICATION_SIZE as usize / std::mem::size_of::<IndirectSignedApprovalVoteV2>() / 3,
2867);
2868
2869// Low level helper for sending assignments.
2870async fn send_assignments_batched_inner(
2871	sender: &mut impl overseer::SubsystemSender<NetworkBridgeTxMessage>,
2872	batch: impl IntoIterator<Item = (IndirectAssignmentCertV2, CandidateBitfield)>,
2873	peers: Vec<PeerId>,
2874	_peer_version: ValidationVersion,
2875) {
2876	sender
2877		.send_message(NetworkBridgeTxMessage::SendValidationMessage(
2878			peers,
2879			ValidationProtocols::V3(protocol_v3::ValidationProtocol::ApprovalDistribution(
2880				protocol_v3::ApprovalDistributionMessage::Assignments(batch.into_iter().collect()),
2881			)),
2882		))
2883		.await;
2884}
2885
2886/// Send assignments while honoring the `max_notification_size` of the protocol.
2887///
2888/// Splitting the messages into multiple notifications allows more granular processing at the
2889/// destination, such that the subsystem doesn't get stuck for long processing a batch
2890/// of assignments and can `select!` other tasks.
2891pub(crate) async fn send_assignments_batched(
2892	network_sender: &mut impl overseer::SubsystemSender<NetworkBridgeTxMessage>,
2893	v2_assignments: impl IntoIterator<Item = (IndirectAssignmentCertV2, CandidateBitfield)> + Clone,
2894	peers: &[(PeerId, ProtocolVersion)],
2895) {
2896	let v3_peers = filter_by_peer_version(peers, ValidationVersion::V3.into());
2897
2898	if !v3_peers.is_empty() {
2899		let mut v3 = v2_assignments.into_iter().peekable();
2900
2901		while v3.peek().is_some() {
2902			let batch = v3.by_ref().take(MAX_ASSIGNMENT_BATCH_SIZE).collect::<Vec<_>>();
2903			send_assignments_batched_inner(
2904				network_sender,
2905				batch,
2906				v3_peers.clone(),
2907				ValidationVersion::V3,
2908			)
2909			.await;
2910		}
2911	}
2912}
2913
2914/// Send approvals while honoring the `max_notification_size` of the protocol and peer version.
2915pub(crate) async fn send_approvals_batched(
2916	sender: &mut impl overseer::SubsystemSender<NetworkBridgeTxMessage>,
2917	approvals: impl IntoIterator<Item = IndirectSignedApprovalVoteV2> + Clone,
2918	peers: &[(PeerId, ProtocolVersion)],
2919) {
2920	let v3_peers = filter_by_peer_version(peers, ValidationVersion::V3.into());
2921
2922	if !v3_peers.is_empty() {
2923		let mut batches = approvals.into_iter().peekable();
2924
2925		while batches.peek().is_some() {
2926			let batch: Vec<_> = batches.by_ref().take(MAX_APPROVAL_BATCH_SIZE).collect();
2927
2928			sender
2929				.send_message(NetworkBridgeTxMessage::SendValidationMessage(
2930					v3_peers.clone(),
2931					ValidationProtocols::V3(protocol_v3::ValidationProtocol::ApprovalDistribution(
2932						protocol_v3::ApprovalDistributionMessage::Approvals(batch),
2933					)),
2934				))
2935				.await;
2936		}
2937	}
2938}