1use std::{
5 sync::{Arc, Weak},
6 time::Duration,
7};
8
9use consensus_config::AuthorityIndex;
10use consensus_types::block::Round;
11use futures::StreamExt;
12use mysten_metrics::spawn_monitored_task;
13use parking_lot::{Mutex, RwLock};
14use tokio::{
15 task::JoinHandle,
16 time::{sleep, timeout},
17};
18use tracing::{debug, error, info};
19
20use crate::{
21 block::BlockAPI as _,
22 context::Context,
23 dag_state::DagState,
24 error::ConsensusError,
25 network::{SerializedBlockForm, ValidatorNetworkClient, ValidatorNetworkService},
26 task::{join_and_propagate_panic, reap_finished_task},
27};
28
29const SUBSCRIPTION_TIMEOUT: Duration = Duration::from_secs(30);
38
39pub(crate) struct Subscriber<C: ValidatorNetworkClient, S: ValidatorNetworkService> {
45 context: Arc<Context>,
46 network_client: Arc<C>,
47 authority_service: Arc<S>,
48 dag_state: Arc<RwLock<DagState>>,
49 subscriptions: Arc<Mutex<Box<[Option<JoinHandle<()>>]>>>,
50 retired_subscriptions: Arc<Mutex<Vec<JoinHandle<()>>>>,
52}
53
54impl<C: ValidatorNetworkClient, S: ValidatorNetworkService> Subscriber<C, S> {
55 pub(crate) fn new(
56 context: Arc<Context>,
57 network_client: Arc<C>,
58 authority_service: Arc<S>,
59 dag_state: Arc<RwLock<DagState>>,
60 ) -> Self {
61 let subscriptions = (0..context.committee.size())
62 .map(|_| None)
63 .collect::<Vec<_>>();
64 Self {
65 context,
66 network_client,
67 authority_service,
68 dag_state,
69 subscriptions: Arc::new(Mutex::new(subscriptions.into_boxed_slice())),
70 retired_subscriptions: Arc::new(Mutex::new(Vec::new())),
71 }
72 }
73
74 pub(crate) fn subscribe(&self, peer: AuthorityIndex) {
75 if peer == self.context.own_index {
76 error!("Attempt to subscribe to own validator {peer} is ignored!");
77 return;
78 }
79 let context = self.context.clone();
80 let network_client = self.network_client.clone();
81 let authority_service = Arc::downgrade(&self.authority_service);
84 let dag_state = Arc::downgrade(&self.dag_state);
85
86 let mut subscriptions = self.subscriptions.lock();
87 self.unsubscribe_locked(peer, &mut subscriptions[peer.value()]);
88 subscriptions[peer.value()] = Some(spawn_monitored_task!(Self::subscription_loop(
89 context,
90 network_client,
91 authority_service,
92 dag_state,
93 peer,
94 )));
95 }
96
97 pub(crate) async fn stop(&self) {
98 {
99 let mut subscriptions = self.subscriptions.lock();
100 for (peer, _) in self.context.committee.authorities() {
101 self.unsubscribe_locked(peer, &mut subscriptions[peer.value()]);
102 }
103 }
104
105 let subscriptions = std::mem::take(&mut *self.retired_subscriptions.lock());
107 for subscription in subscriptions {
108 join_and_propagate_panic(subscription).await;
109 }
110 }
111
112 fn unsubscribe_locked(&self, peer: AuthorityIndex, subscription: &mut Option<JoinHandle<()>>) {
113 let peer_hostname = &self.context.committee.authority(peer).hostname;
114 if let Some(subscription) = subscription.take() {
115 subscription.abort();
116 let mut retired_subscriptions = self.retired_subscriptions.lock();
117 retired_subscriptions.retain_mut(|task| !reap_finished_task(task));
120 retired_subscriptions.push(subscription);
121 }
122 self.context
125 .metrics
126 .node_metrics
127 .subscribed_to
128 .with_label_values(&[peer_hostname])
129 .set(0);
130 }
131
132 async fn subscription_loop(
133 context: Arc<Context>,
134 network_client: Arc<C>,
135 authority_service: Weak<S>,
136 dag_state: Weak<RwLock<DagState>>,
137 peer: AuthorityIndex,
138 ) {
139 const IMMEDIATE_RETRIES: i64 = 3;
140 const MIN_TIMEOUT: Duration = Duration::from_millis(500);
141 let mut backoff = mysten_common::backoff::ExponentialBackoff::new(
143 Duration::from_millis(100),
144 Duration::from_secs(10),
145 );
146
147 let peer_hostname = &context.committee.authority(peer).hostname;
148 let mut retries: i64 = 0;
149 'subscription: loop {
150 context
151 .metrics
152 .node_metrics
153 .subscribed_to
154 .with_label_values(&[peer_hostname])
155 .set(0);
156
157 let mut delay = Duration::ZERO;
158 if retries > IMMEDIATE_RETRIES {
159 delay = backoff.next().unwrap();
160 debug!(
161 "Delaying retry {} of peer {} subscription, in {} seconds",
162 retries,
163 peer_hostname,
164 delay.as_secs_f32(),
165 );
166 sleep(delay).await;
167 } else if retries > 0 {
168 tokio::task::yield_now().await;
170 }
171 retries += 1;
172
173 let last_accepted: Round = {
174 let Some(dag_state) = dag_state.upgrade() else {
175 return;
176 };
177 let dag_state = dag_state.read();
178 let gc_round = dag_state.gc_round();
179 dag_state
180 .get_last_block_for_authority(peer)
181 .round()
182 .max(gc_round)
183 };
184
185 let request_timeout = MIN_TIMEOUT.max(delay);
187 let subscribe = timeout(
193 SUBSCRIPTION_TIMEOUT,
194 network_client.subscribe_blocks(peer, last_accepted, request_timeout),
195 )
196 .await;
197 let mut blocks = match subscribe {
198 Ok(Ok(blocks)) => {
199 debug!(
200 "Subscribed to peer {} {} after {} attempts",
201 peer, peer_hostname, retries
202 );
203 context
204 .metrics
205 .node_metrics
206 .subscriber_connection_attempts
207 .with_label_values(&[peer_hostname.as_str(), "success"])
208 .inc();
209 blocks
210 }
211 Ok(Err(e)) => {
212 debug!(
213 "Failed to subscribe to blocks from peer {} {}: {}",
214 peer, peer_hostname, e
215 );
216 context
217 .metrics
218 .node_metrics
219 .subscriber_connection_attempts
220 .with_label_values(&[peer_hostname.as_str(), "failure"])
221 .inc();
222 continue 'subscription;
223 }
224 Err(_) => {
225 debug!(
226 "Timed out subscribing to blocks from peer {} {} after {:?}",
227 peer, peer_hostname, SUBSCRIPTION_TIMEOUT
228 );
229 context
230 .metrics
231 .node_metrics
232 .subscriber_connection_attempts
233 .with_label_values(&[peer_hostname.as_str(), "failure"])
234 .inc();
235 continue 'subscription;
236 }
237 };
238
239 context
241 .metrics
242 .node_metrics
243 .subscribed_to
244 .with_label_values(&[peer_hostname])
245 .set(1);
246
247 'stream: loop {
248 match timeout(SUBSCRIPTION_TIMEOUT, blocks.next()).await {
249 Ok(Some(block)) => {
250 context
251 .metrics
252 .node_metrics
253 .subscribed_blocks
254 .with_label_values(&[peer_hostname])
255 .inc();
256 let Some(authority_service) = authority_service.upgrade() else {
257 return;
258 };
259 if matches!(block.block, SerializedBlockForm::Slim(_)) {
264 let reason: &'static str =
265 (&ConsensusError::UnexpectedBlockForm).into();
266 context
267 .metrics
268 .node_metrics
269 .subscribe_stream_form_failures
270 .with_label_values(&[peer_hostname, reason])
271 .inc();
272 retries = 0;
273 backoff.reset();
274 continue 'stream;
275 }
276 let result = authority_service.handle_send_block(peer, block).await;
277 if let Err(e) = result {
278 match e {
279 ConsensusError::BlockRejected { block_ref, reason } => {
280 debug!(
281 "Failed to process block from peer {} {} for block {:?}: {}",
282 peer, peer_hostname, block_ref, reason
283 );
284 }
285 _ => {
286 info!(
287 "Invalid block received from peer {} {}: {}",
288 peer, peer_hostname, e
289 );
290 }
291 }
292 }
293 retries = 0;
297 backoff.reset();
298 }
299 Ok(None) => {
300 debug!(
301 "Subscription to blocks from peer {} {} ended",
302 peer, peer_hostname
303 );
304 retries += 1;
305 break 'stream;
306 }
307 Err(_) => {
308 info!(
309 "Subscription to blocks from peer {} {} made no progress for {:?}",
310 peer, peer_hostname, SUBSCRIPTION_TIMEOUT
311 );
312 retries += 1;
313 break 'stream;
314 }
315 }
316 }
317 }
318 }
319}
320
321#[cfg(test)]
322mod test {
323 use async_trait::async_trait;
324 use bytes::Bytes;
325 use consensus_types::block::BlockRef;
326 use futures::stream;
327
328 use super::*;
329 use crate::{
330 VerifiedBlock,
331 commit::CommitRange,
332 error::ConsensusResult,
333 network::{BlockStream, ExtendedSerializedBlock, test_network::TestService},
334 storage::mem_store::MemStore,
335 };
336
337 struct SubscriberTestClient {
338 subscribe_calls: Mutex<Vec<Round>>,
340 block_interval: Option<Duration>,
343 hang_on_subscribe: bool,
345 emit_slim: bool,
346 }
347
348 impl SubscriberTestClient {
349 fn new() -> Self {
350 Self::new_with_block_interval(Duration::from_millis(1))
351 }
352
353 fn new_pending() -> Self {
354 Self {
355 subscribe_calls: Mutex::new(Vec::new()),
356 block_interval: None,
357 hang_on_subscribe: false,
358 emit_slim: false,
359 }
360 }
361
362 fn new_with_block_interval(interval: Duration) -> Self {
363 Self {
364 subscribe_calls: Mutex::new(Vec::new()),
365 block_interval: Some(interval),
366 hang_on_subscribe: false,
367 emit_slim: false,
368 }
369 }
370
371 fn new_hanging_subscribe() -> Self {
372 Self {
373 subscribe_calls: Mutex::new(Vec::new()),
374 block_interval: None,
375 hang_on_subscribe: true,
376 emit_slim: false,
377 }
378 }
379
380 fn subscribe_calls(&self) -> Vec<Round> {
381 self.subscribe_calls.lock().clone()
382 }
383 }
384
385 #[async_trait]
386 impl ValidatorNetworkClient for SubscriberTestClient {
387 async fn send_block(
388 &self,
389 _peer: AuthorityIndex,
390 _block: &VerifiedBlock,
391 _timeout: Duration,
392 ) -> ConsensusResult<()> {
393 unimplemented!("Unimplemented")
394 }
395
396 async fn subscribe_blocks(
397 &self,
398 _peer: AuthorityIndex,
399 last_received: Round,
400 _timeout: Duration,
401 ) -> ConsensusResult<BlockStream> {
402 self.subscribe_calls.lock().push(last_received);
403 if self.hang_on_subscribe {
404 std::future::pending::<()>().await;
405 }
406 let Some(interval) = self.block_interval else {
407 return Ok(Box::pin(stream::pending()));
408 };
409 let emit_slim = self.emit_slim;
410 let block_stream = stream::unfold(0u8, move |i| async move {
411 sleep(interval).await;
412 let block = if emit_slim && i % 2 == 0 {
415 ExtendedSerializedBlock {
416 block: SerializedBlockForm::Slim(Bytes::from(vec![2u8; 8])),
417 excluded_ancestors: vec![],
418 }
419 } else {
420 ExtendedSerializedBlock {
421 block: SerializedBlockForm::Full(Bytes::from(vec![1u8; 8])),
422 excluded_ancestors: vec![],
423 }
424 };
425 Some((block, i.wrapping_add(1)))
426 })
427 .take(10);
428 Ok(Box::pin(block_stream))
429 }
430
431 async fn fetch_blocks(
432 &self,
433 _peer: AuthorityIndex,
434 _block_refs: Vec<BlockRef>,
435 _fetch_after_rounds: Vec<Round>,
436 _fetch_missing_ancestors: bool,
437 _timeout: Duration,
438 ) -> ConsensusResult<Vec<Bytes>> {
439 unimplemented!("Unimplemented")
440 }
441
442 async fn fetch_commits(
443 &self,
444 _peer: AuthorityIndex,
445 _commit_range: CommitRange,
446 _timeout: Duration,
447 ) -> ConsensusResult<(Vec<Bytes>, Vec<Bytes>)> {
448 unimplemented!("Unimplemented")
449 }
450
451 async fn fetch_latest_blocks(
452 &self,
453 _peer: AuthorityIndex,
454 _authorities: Vec<AuthorityIndex>,
455 _timeout: Duration,
456 ) -> ConsensusResult<Vec<Bytes>> {
457 unimplemented!("Unimplemented")
458 }
459
460 async fn get_latest_rounds(
461 &self,
462 _peer: AuthorityIndex,
463 _timeout: Duration,
464 ) -> ConsensusResult<(Vec<Round>, Vec<Round>)> {
465 unimplemented!("Unimplemented")
466 }
467 }
468
469 #[tokio::test(flavor = "current_thread", start_paused = true)]
470 async fn subscriber_retries() {
471 let (context, _keys) = Context::new_for_test(4);
472 let context = Arc::new(context);
473 let authority_service = Arc::new(Mutex::new(TestService::new()));
474 let network_client = Arc::new(SubscriberTestClient::new());
475 let store = Arc::new(MemStore::new());
476 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
477 let subscriber = Subscriber::new(
478 context.clone(),
479 network_client,
480 authority_service.clone(),
481 dag_state,
482 );
483
484 let peer = context.committee.to_authority_index(2).unwrap();
485 subscriber.subscribe(peer);
486
487 for _ in 0..10 {
489 tokio::time::sleep(Duration::from_secs(1)).await;
490 let service = authority_service.lock();
491 if service.handle_send_block.len() >= 100 {
492 break;
493 }
494 }
495
496 let service = authority_service.lock();
499 assert!(service.handle_send_block.len() >= 100);
500 for (p, block) in service.handle_send_block.iter() {
501 assert_eq!(*p, peer);
502 assert_eq!(
503 *block,
504 ExtendedSerializedBlock {
505 block: SerializedBlockForm::Full(Bytes::from(vec![1u8; 8])),
506 excluded_ancestors: vec![],
507 }
508 );
509 }
510 }
511
512 #[tokio::test(flavor = "current_thread", start_paused = true)]
516 async fn subscriber_drops_slim_payloads_without_delivering() {
517 let (context, _keys) = Context::new_for_test(4);
518 let context = Arc::new(context);
519 let authority_service = Arc::new(Mutex::new(TestService::new()));
520 let network_client = Arc::new(SubscriberTestClient {
521 subscribe_calls: Mutex::new(Vec::new()),
522 block_interval: Some(Duration::from_millis(1)),
523 hang_on_subscribe: false,
524 emit_slim: true,
525 });
526 let store = Arc::new(MemStore::new());
527 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
528 let subscriber = Subscriber::new(
529 context.clone(),
530 network_client,
531 authority_service.clone(),
532 dag_state,
533 );
534
535 let peer = context.committee.to_authority_index(2).unwrap();
536 subscriber.subscribe(peer);
537
538 for _ in 0..10 {
539 tokio::time::sleep(Duration::from_secs(1)).await;
540 if authority_service.lock().handle_send_block.len() >= 20 {
541 break;
542 }
543 }
544
545 let service = authority_service.lock();
546 assert!(!service.handle_send_block.is_empty());
547 for (_, block) in service.handle_send_block.iter() {
548 assert!(
549 matches!(block.block, SerializedBlockForm::Full(_)),
550 "a slim payload must never be delivered"
551 );
552 }
553 assert!(
554 context
555 .metrics
556 .node_metrics
557 .subscribe_stream_form_failures
558 .with_label_values(&[
559 context.committee.authority(peer).hostname.as_str(),
560 "UnexpectedBlockForm",
561 ])
562 .get()
563 > 0,
564 "dropped slim payloads must be counted"
565 );
566 }
567
568 #[tokio::test(flavor = "current_thread", start_paused = true)]
569 async fn subscriber_reconnects_when_stream_makes_no_progress() {
570 let (context, _keys) = Context::new_for_test(4);
571 let context = Arc::new(context);
572 let authority_service = Arc::new(Mutex::new(TestService::new()));
573 let network_client = Arc::new(SubscriberTestClient::new_pending());
574 let store = Arc::new(MemStore::new());
575 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
576 let subscriber = Subscriber::new(
577 context.clone(),
578 network_client.clone(),
579 authority_service,
580 dag_state,
581 );
582
583 let peer = context.committee.to_authority_index(2).unwrap();
584 subscriber.subscribe(peer);
585
586 tokio::time::sleep(SUBSCRIPTION_TIMEOUT + Duration::from_millis(1)).await;
587
588 assert!(
589 network_client.subscribe_calls().len() >= 2,
590 "an idle subscription should be re-established"
591 );
592 }
593
594 #[tokio::test(flavor = "current_thread", start_paused = true)]
595 async fn subscriber_retries_when_subscribing_makes_no_progress() {
596 let (context, _keys) = Context::new_for_test(4);
597 let context = Arc::new(context);
598 let authority_service = Arc::new(Mutex::new(TestService::new()));
599 let network_client = Arc::new(SubscriberTestClient::new_hanging_subscribe());
602 let store = Arc::new(MemStore::new());
603 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
604 let subscriber = Subscriber::new(
605 context.clone(),
606 network_client.clone(),
607 authority_service,
608 dag_state,
609 );
610
611 let peer = context.committee.to_authority_index(2).unwrap();
612 subscriber.subscribe(peer);
613
614 tokio::time::sleep(SUBSCRIPTION_TIMEOUT + Duration::from_millis(1)).await;
615
616 assert!(
617 network_client.subscribe_calls().len() >= 2,
618 "subscribing should be abandoned and retried when the peer never responds"
619 );
620 }
621
622 #[tokio::test(flavor = "current_thread", start_paused = true)]
623 async fn subscriber_stays_subscribed_when_stream_progresses_within_idle_timeout() {
624 let (context, _keys) = Context::new_for_test(4);
625 let context = Arc::new(context);
626 let authority_service = Arc::new(Mutex::new(TestService::new()));
627 let network_client = Arc::new(SubscriberTestClient::new_with_block_interval(
630 SUBSCRIPTION_TIMEOUT - Duration::from_secs(1),
631 ));
632 let store = Arc::new(MemStore::new());
633 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
634 let subscriber = Subscriber::new(
635 context.clone(),
636 network_client.clone(),
637 authority_service.clone(),
638 dag_state,
639 );
640
641 let peer = context.committee.to_authority_index(2).unwrap();
642 subscriber.subscribe(peer);
643
644 tokio::time::sleep(SUBSCRIPTION_TIMEOUT * 4).await;
645
646 assert_eq!(
647 network_client.subscribe_calls().len(),
648 1,
649 "a stream that keeps delivering blocks within the idle timeout should not be re-established"
650 );
651 assert!(
652 !authority_service.lock().handle_send_block.is_empty(),
653 "blocks from the slow stream should have been processed"
654 );
655 }
656
657 #[tokio::test(flavor = "current_thread", start_paused = true)]
661 async fn subscriber_recomputes_resume_round_on_reconnect() {
662 use crate::block::TestBlock;
663
664 let (context, _keys) = Context::new_for_test(4);
665 let context = Arc::new(context);
666 let store = Arc::new(MemStore::new());
667 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
668 let network_client = Arc::new(SubscriberTestClient::new());
669 let authority_service = Arc::new(Mutex::new(TestService::new()));
670 let subscriber = Subscriber::new(
671 context.clone(),
672 network_client.clone(),
673 authority_service,
674 dag_state.clone(),
675 );
676
677 let peer = context.committee.to_authority_index(2).unwrap();
678 subscriber.subscribe(peer);
679
680 tokio::time::sleep(Duration::from_secs(3)).await;
682 {
683 let recorded = network_client.subscribe_calls();
684 assert!(
685 !recorded.is_empty() && recorded.iter().all(|&r| r == 0),
686 "before a block is accepted, every reconnect should resume from round 0: {recorded:?}"
687 );
688 }
689
690 const RESUME_ROUND: Round = 10;
692 dag_state.write().accept_block(VerifiedBlock::new_for_test(
693 TestBlock::new(RESUME_ROUND, peer.value() as u32).build(),
694 ));
695
696 let mut observed_resume = false;
699 for _ in 0..10 {
700 tokio::time::sleep(Duration::from_secs(1)).await;
701 if network_client.subscribe_calls().last() == Some(&RESUME_ROUND) {
702 observed_resume = true;
703 break;
704 }
705 }
706 assert!(
707 observed_resume,
708 "after accepting a block at round {RESUME_ROUND}, the subscriber should resume from it; \
709 recorded resume rounds: {:?}",
710 network_client.subscribe_calls()
711 );
712 }
713}