1mod collation_manager;
18mod common;
19mod error;
20mod peer_manager;
21mod state;
22#[cfg(test)]
23mod tests;
24
25use crate::{
26 validator_side_experimental::{common::MIN_FETCH_TIMER_DELAY, peer_manager::PersistentDb},
27 LOG_TARGET,
28};
29use collation_manager::CollationManager;
30use common::{ProspectiveCandidate, MAX_STORED_SCORES_PER_PARA};
31use error::{log_error, FatalError, FatalResult, Result};
32use futures::{future::Fuse, select, FutureExt, StreamExt};
33use polkadot_node_clock::Clock;
34use polkadot_node_network_protocol::{
35 self as net_protocol, peer_set::PeerSet, v1 as protocol_v1, v2 as protocol_v2,
36 v3_collation as protocol_v3, CollationProtocols, PeerId,
37};
38use polkadot_node_subsystem::{
39 messages::{CollatorProtocolMessage, NetworkBridgeEvent, NetworkBridgeTxMessage},
40 overseer, ActivatedLeaf, CollatorProtocolSenderTrait, FromOrchestra, OverseerSignal,
41};
42use polkadot_node_subsystem_util::database::Database;
43use sp_keystore::KeystorePtr;
44use std::{future, future::Future, pin::Pin, sync::Arc, time::Duration};
45
46#[cfg(test)]
47use peer_manager::Db;
48use peer_manager::PeerManager;
49
50use state::State;
51
52pub use crate::validator_side_metrics::Metrics;
53
54const DEFAULT_PERSIST_INTERVAL_SECS: u64 = 600;
56
57#[derive(Debug, Clone, Copy)]
59pub struct ReputationConfig {
60 pub col_reputation_data: u32,
62 pub persist_interval: Option<Duration>,
65}
66
67#[overseer::contextbounds(CollatorProtocol, prefix = self::overseer)]
69pub(crate) async fn run<Context>(
70 mut ctx: Context,
71 keystore: KeystorePtr,
72 metrics: Metrics,
73 db: Arc<dyn Database>,
74 reputation_config: ReputationConfig,
75 clock: Arc<dyn Clock>,
76) -> FatalResult<()> {
77 let persist_interval = reputation_config
78 .persist_interval
79 .unwrap_or(Duration::from_secs(DEFAULT_PERSIST_INTERVAL_SECS));
80 gum::info!(
81 LOG_TARGET,
82 persist_interval_secs = persist_interval.as_secs(),
83 "Running experimental collator protocol"
84 );
85 if let Some(state) =
86 initialize(&mut ctx, keystore, metrics, db, reputation_config, clock.clone()).await?
87 {
88 run_inner(ctx, state, persist_interval, clock).await?;
89 }
90
91 Ok(())
92}
93
94#[overseer::contextbounds(CollatorProtocol, prefix = self::overseer)]
95async fn initialize<Context>(
96 ctx: &mut Context,
97 keystore: KeystorePtr,
98 metrics: Metrics,
99 db: Arc<dyn Database>,
100 reputation_config: ReputationConfig,
101 clock: Arc<dyn Clock>,
102) -> FatalResult<Option<State<PersistentDb>>> {
103 loop {
104 let first_leaf = match wait_for_first_leaf(ctx).await? {
105 Some(activated_leaf) => {
106 gum::debug!(
107 target: LOG_TARGET,
108 number = activated_leaf.number,
109 hash = ?activated_leaf.hash,
110 "Got the first active leaf notification, trying to initialize subsystem."
111 );
112 activated_leaf
113 },
114 None => return Ok(None),
115 };
116
117 let collation_manager =
118 CollationManager::new(ctx.sender(), keystore.clone(), first_leaf, clock.clone())
119 .await?;
120
121 let scheduled_paras = collation_manager.assignments();
122
123 gum::debug!(
124 target: LOG_TARGET,
125 ?scheduled_paras,
126 "Collator protocol initial assignments",
127 );
128
129 let (backend, task) = match PersistentDb::new(
131 db.clone(),
132 reputation_config,
133 MAX_STORED_SCORES_PER_PARA,
134 )
135 .await
136 {
137 Ok(result) => result,
138 Err(e) => {
139 gum::error!(
140 target: LOG_TARGET,
141 error = ?e,
142 "Failed to initialize persistent reputation DB"
143 );
144 return Err(FatalError::ReputationDbInit(e));
145 },
146 };
147
148 ctx.spawn_blocking("collator-reputation-persistence-task", task)
150 .map_err(|e| FatalError::SpawnTask(e.to_string()))?;
151
152 gum::trace!(target: LOG_TARGET, "Spawned background reputation persistence task");
153
154 match PeerManager::startup(
155 backend,
156 ctx.sender(),
157 scheduled_paras.into_iter().collect(),
158 clock.clone(),
159 )
160 .await
161 {
162 Ok(peer_manager) => {
163 return Ok(Some(State::new(peer_manager, collation_manager, metrics)))
164 },
165 Err(err) => {
166 log_error(Err(err))?;
167 continue;
168 },
169 }
170 }
171}
172
173#[overseer::contextbounds(CollatorProtocol, prefix = self::overseer)]
175async fn wait_for_first_leaf<Context>(ctx: &mut Context) -> FatalResult<Option<ActivatedLeaf>> {
176 loop {
177 match ctx.recv().await.map_err(FatalError::SubsystemReceive)? {
178 FromOrchestra::Signal(OverseerSignal::Conclude) => return Ok(None),
179 FromOrchestra::Signal(OverseerSignal::ActiveLeaves(update)) => {
180 if let Some(activated) = update.activated {
181 return Ok(Some(activated));
182 }
183 },
184 FromOrchestra::Signal(OverseerSignal::BlockFinalized(_, _)) => {},
185 FromOrchestra::Communication { msg } => {
186 match msg {
189 CollatorProtocolMessage::NetworkBridgeUpdate(
190 NetworkBridgeEvent::PeerConnected(peer_id, ..),
191 ) => {
192 gum::debug!(
193 target: LOG_TARGET,
194 ?peer_id,
195 "Disconnecting peer that connected before subsystem initialization",
196 );
197 ctx.send_message(NetworkBridgeTxMessage::DisconnectPeers(
198 vec![peer_id],
199 PeerSet::Collation,
200 ))
201 .await;
202 },
203 CollatorProtocolMessage::NetworkBridgeUpdate(
204 NetworkBridgeEvent::PeerMessage(peer_id, ..),
205 ) => {
206 gum::debug!(
207 target: LOG_TARGET,
208 ?peer_id,
209 "Disconnecting peer that sent message before subsystem initialization",
210 );
211 ctx.send_message(NetworkBridgeTxMessage::DisconnectPeers(
212 vec![peer_id],
213 PeerSet::Collation,
214 ))
215 .await;
216 },
217 msg => {
218 gum::trace!(
219 target: LOG_TARGET,
220 ?msg,
221 "Received msg before first active leaves update, dropping.",
222 );
223 },
224 }
225 },
226 }
227 }
228}
229
230fn create_timer(
231 clock: &dyn Clock,
232 maybe_delay: Option<Duration>,
233) -> Fuse<Pin<Box<dyn Future<Output = ()> + Send>>> {
234 let timer: Pin<Box<dyn Future<Output = ()> + Send>> = match maybe_delay {
235 Some(delay) => clock.delay(delay),
236 None => Box::pin(future::pending::<()>()),
237 };
238
239 timer.fuse()
240}
241
242fn create_persistence_timer(
244 clock: &dyn Clock,
245 interval: Duration,
246) -> Fuse<Pin<Box<dyn Future<Output = ()> + Send>>> {
247 clock.delay(interval).fuse()
248}
249
250#[overseer::contextbounds(CollatorProtocol, prefix = self::overseer)]
251async fn run_inner<Context>(
252 mut ctx: Context,
253 mut state: State<PersistentDb>,
254 persist_interval: Duration,
255 clock: Arc<dyn Clock>,
256) -> FatalResult<()> {
257 let mut timer = create_timer(&*clock, None);
258 let mut persistence_timer = create_persistence_timer(&*clock, persist_interval);
259
260 loop {
261 select! {
262 res = ctx.recv().fuse() => {
268 match res {
269 Ok(FromOrchestra::Communication { msg }) => {
270 gum::trace!(target: LOG_TARGET, msg = ?msg, "received a message");
271 process_msg(
272 ctx.sender(),
273 &mut state,
274 msg,
275 ).await;
276 }
277 Ok(FromOrchestra::Signal(OverseerSignal::Conclude)) | Err(_) => {
278 state.persist_reputations().await;
280 break
281 },
282 Ok(FromOrchestra::Signal(OverseerSignal::BlockFinalized(hash, number))) => {
283 state.handle_finalized_block(ctx.sender(), hash, number).await?;
284 },
285 Ok(FromOrchestra::Signal(_)) => continue,
286 }
287 },
288 resp = state.collation_response_stream().select_next_some() => {
289 state.handle_fetched_collation(ctx.sender(), resp).await;
290 },
291 _ = &mut timer => {
292 },
295 _ = &mut persistence_timer => {
296 state.background_persist_reputations();
298 persistence_timer = create_persistence_timer(&*clock, persist_interval);
300 },
301 }
302
303 let maybe_delay = state.try_launch_new_fetch_requests(ctx.sender()).await;
311 timer = create_timer(
312 &*clock,
313 maybe_delay.map(|delay| std::cmp::max(delay, MIN_FETCH_TIMER_DELAY)),
314 );
315 }
316
317 Ok(())
318}
319
320async fn process_msg<Sender: CollatorProtocolSenderTrait>(
322 sender: &mut Sender,
323 state: &mut State<PersistentDb>,
324 msg: CollatorProtocolMessage,
325) {
326 use CollatorProtocolMessage::*;
327
328 let _timer = state.metrics().time_process_msg();
329
330 match msg {
331 CollateOn(id) => {
332 gum::warn!(
333 target: LOG_TARGET,
334 para_id = %id,
335 "CollateOn message is not expected on the validator side of the protocol",
336 );
337 },
338 DistributeCollation { .. } => {
339 gum::warn!(
340 target: LOG_TARGET,
341 "DistributeCollation message is not expected on the validator side of the protocol",
342 );
343 },
344 NetworkBridgeUpdate(event) => {
345 if let Err(e) = handle_network_msg(sender, state, event).await {
346 gum::warn!(
347 target: LOG_TARGET,
348 err = ?e,
349 "Failed to handle incoming network message",
350 );
351 }
352 },
353 Seconded(parent, stmt) => {
354 state.handle_seconded_collation(sender, stmt, parent).await;
355 },
356 Invalid(parent, candidate_receipt) => {
357 state.handle_invalid_collation(candidate_receipt, parent).await;
358 },
359 ConnectToBackingGroups => {
360 gum::warn!(
361 target: LOG_TARGET,
362 "ConnectToBackingGroups message is not expected on the validator side of the protocol",
363 );
364 },
365 DisconnectFromBackingGroups => {
366 gum::warn!(
367 target: LOG_TARGET,
368 "DisconnectFromBackingGroups message is not expected on the validator side of the protocol",
369 );
370 },
371 }
372}
373
374async fn handle_network_msg<Sender: CollatorProtocolSenderTrait>(
376 sender: &mut Sender,
377 state: &mut State<PersistentDb>,
378 bridge_message: NetworkBridgeEvent<net_protocol::CollatorProtocolMessage>,
379) -> Result<()> {
380 use NetworkBridgeEvent::*;
381
382 match bridge_message {
383 PeerConnected(peer_id, observed_role, protocol_version, _) => {
384 let version = match protocol_version.try_into() {
385 Ok(version) => version,
386 Err(err) => {
387 gum::error!(
389 target: LOG_TARGET,
390 ?peer_id,
391 ?observed_role,
392 ?err,
393 "Unsupported protocol version"
394 );
395 return Ok(());
396 },
397 };
398 state.handle_peer_connected(sender, peer_id, version).await;
399 },
400 PeerDisconnected(peer_id) => {
401 state.handle_peer_disconnected(peer_id).await;
402 },
403 NewGossipTopology { .. } => {
404 },
406 PeerViewChange(_, _) => {
407 },
409 OurViewChange(view) => {
410 state.handle_our_view_change(sender, view).await?;
411 },
412 PeerMessage(remote, msg) => {
413 process_incoming_peer_message(sender, state, remote, msg).await;
414 },
415 UpdatedAuthorityIds { .. } => {
416 },
418 }
419
420 Ok(())
421}
422
423async fn process_incoming_peer_message<Sender: CollatorProtocolSenderTrait>(
424 sender: &mut Sender,
425 state: &mut State<PersistentDb>,
426 origin: PeerId,
427 msg: CollationProtocols<
428 protocol_v1::CollatorProtocolMessage,
429 protocol_v2::CollatorProtocolMessage,
430 protocol_v3::CollatorProtocolMessage,
431 >,
432) {
433 use protocol_v1::CollatorProtocolMessage as V1;
434 use protocol_v2::CollatorProtocolMessage as V2;
435 use protocol_v3::CollatorProtocolMessage as V3;
436
437 match msg {
438 CollationProtocols::V1(V1::Declare(_collator_id, para_id, _signature)) |
439 CollationProtocols::V2(V2::Declare(_collator_id, para_id, _signature)) |
440 CollationProtocols::V3(V3::Declare(_collator_id, para_id, _signature)) => {
441 state.handle_declare(sender, origin, para_id).await;
442 },
443 CollationProtocols::V1(V1::CollationSeconded(..)) |
444 CollationProtocols::V2(V2::CollationSeconded(..)) |
445 CollationProtocols::V3(V3::CollationSeconded(..)) => {
446 gum::warn!(
447 target: LOG_TARGET,
448 peer_id = ?origin,
449 "Unexpected `CollationSeconded` message",
450 );
451 },
452 CollationProtocols::V1(V1::AdvertiseCollation(relay_parent)) => {
453 state.handle_advertisement(sender, origin, relay_parent, None, None).await;
454 },
455 CollationProtocols::V2(V2::AdvertiseCollation {
456 scheduling_parent,
457 candidate_hash,
458 parent_head_data_hash,
459 ..
460 }) => {
461 state
462 .handle_advertisement(
463 sender,
464 origin,
465 scheduling_parent,
466 Some(ProspectiveCandidate { candidate_hash, parent_head_data_hash }),
467 None,
468 )
469 .await;
470 },
471 CollationProtocols::V3(V3::AdvertiseCollation {
472 scheduling_parent,
473 candidate_hash,
474 parent_head_data_hash,
475 candidate_descriptor_version,
476 ..
477 }) => {
478 state
479 .handle_advertisement(
480 sender,
481 origin,
482 scheduling_parent,
483 Some(ProspectiveCandidate { candidate_hash, parent_head_data_hash }),
484 Some(candidate_descriptor_version),
485 )
486 .await;
487 },
488 }
489}