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::{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 let result = authority_service.handle_send_block(peer, block).await;
260 if let Err(e) = result {
261 match e {
262 ConsensusError::BlockRejected { block_ref, reason } => {
263 debug!(
264 "Failed to process block from peer {} {} for block {:?}: {}",
265 peer, peer_hostname, block_ref, reason
266 );
267 }
268 _ => {
269 info!(
270 "Invalid block received from peer {} {}: {}",
271 peer, peer_hostname, e
272 );
273 }
274 }
275 }
276 retries = 0;
280 backoff.reset();
281 }
282 Ok(None) => {
283 debug!(
284 "Subscription to blocks from peer {} {} ended",
285 peer, peer_hostname
286 );
287 retries += 1;
288 break 'stream;
289 }
290 Err(_) => {
291 info!(
292 "Subscription to blocks from peer {} {} made no progress for {:?}",
293 peer, peer_hostname, SUBSCRIPTION_TIMEOUT
294 );
295 retries += 1;
296 break 'stream;
297 }
298 }
299 }
300 }
301 }
302}
303
304#[cfg(test)]
305mod test {
306 use async_trait::async_trait;
307 use bytes::Bytes;
308 use consensus_types::block::BlockRef;
309 use futures::stream;
310
311 use super::*;
312 use crate::{
313 VerifiedBlock,
314 commit::CommitRange,
315 error::ConsensusResult,
316 network::{BlockStream, ExtendedSerializedBlock, test_network::TestService},
317 storage::mem_store::MemStore,
318 };
319
320 struct SubscriberTestClient {
321 subscribe_calls: Mutex<Vec<Round>>,
323 block_interval: Option<Duration>,
326 hang_on_subscribe: bool,
328 }
329
330 impl SubscriberTestClient {
331 fn new() -> Self {
332 Self::new_with_block_interval(Duration::from_millis(1))
333 }
334
335 fn new_pending() -> Self {
336 Self {
337 subscribe_calls: Mutex::new(Vec::new()),
338 block_interval: None,
339 hang_on_subscribe: false,
340 }
341 }
342
343 fn new_with_block_interval(interval: Duration) -> Self {
344 Self {
345 subscribe_calls: Mutex::new(Vec::new()),
346 block_interval: Some(interval),
347 hang_on_subscribe: false,
348 }
349 }
350
351 fn new_hanging_subscribe() -> Self {
352 Self {
353 subscribe_calls: Mutex::new(Vec::new()),
354 block_interval: None,
355 hang_on_subscribe: true,
356 }
357 }
358
359 fn subscribe_calls(&self) -> Vec<Round> {
360 self.subscribe_calls.lock().clone()
361 }
362 }
363
364 #[async_trait]
365 impl ValidatorNetworkClient for SubscriberTestClient {
366 async fn send_block(
367 &self,
368 _peer: AuthorityIndex,
369 _block: &VerifiedBlock,
370 _timeout: Duration,
371 ) -> ConsensusResult<()> {
372 unimplemented!("Unimplemented")
373 }
374
375 async fn subscribe_blocks(
376 &self,
377 _peer: AuthorityIndex,
378 last_received: Round,
379 _timeout: Duration,
380 ) -> ConsensusResult<BlockStream> {
381 self.subscribe_calls.lock().push(last_received);
382 if self.hang_on_subscribe {
383 std::future::pending::<()>().await;
384 }
385 let Some(interval) = self.block_interval else {
386 return Ok(Box::pin(stream::pending()));
387 };
388 let block_stream = stream::unfold((), move |_| async move {
389 sleep(interval).await;
390 let block = ExtendedSerializedBlock {
391 block: Bytes::from(vec![1u8; 8]),
392 excluded_ancestors: vec![],
393 };
394 Some((block, ()))
395 })
396 .take(10);
397 Ok(Box::pin(block_stream))
398 }
399
400 async fn fetch_blocks(
401 &self,
402 _peer: AuthorityIndex,
403 _block_refs: Vec<BlockRef>,
404 _fetch_after_rounds: Vec<Round>,
405 _fetch_missing_ancestors: bool,
406 _timeout: Duration,
407 ) -> ConsensusResult<Vec<Bytes>> {
408 unimplemented!("Unimplemented")
409 }
410
411 async fn fetch_commits(
412 &self,
413 _peer: AuthorityIndex,
414 _commit_range: CommitRange,
415 _timeout: Duration,
416 ) -> ConsensusResult<(Vec<Bytes>, Vec<Bytes>)> {
417 unimplemented!("Unimplemented")
418 }
419
420 async fn fetch_latest_blocks(
421 &self,
422 _peer: AuthorityIndex,
423 _authorities: Vec<AuthorityIndex>,
424 _timeout: Duration,
425 ) -> ConsensusResult<Vec<Bytes>> {
426 unimplemented!("Unimplemented")
427 }
428
429 async fn get_latest_rounds(
430 &self,
431 _peer: AuthorityIndex,
432 _timeout: Duration,
433 ) -> ConsensusResult<(Vec<Round>, Vec<Round>)> {
434 unimplemented!("Unimplemented")
435 }
436 }
437
438 #[tokio::test(flavor = "current_thread", start_paused = true)]
439 async fn subscriber_retries() {
440 let (context, _keys) = Context::new_for_test(4);
441 let context = Arc::new(context);
442 let authority_service = Arc::new(Mutex::new(TestService::new()));
443 let network_client = Arc::new(SubscriberTestClient::new());
444 let store = Arc::new(MemStore::new());
445 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
446 let subscriber = Subscriber::new(
447 context.clone(),
448 network_client,
449 authority_service.clone(),
450 dag_state,
451 );
452
453 let peer = context.committee.to_authority_index(2).unwrap();
454 subscriber.subscribe(peer);
455
456 for _ in 0..10 {
458 tokio::time::sleep(Duration::from_secs(1)).await;
459 let service = authority_service.lock();
460 if service.handle_send_block.len() >= 100 {
461 break;
462 }
463 }
464
465 let service = authority_service.lock();
468 assert!(service.handle_send_block.len() >= 100);
469 for (p, block) in service.handle_send_block.iter() {
470 assert_eq!(*p, peer);
471 assert_eq!(
472 *block,
473 ExtendedSerializedBlock {
474 block: Bytes::from(vec![1u8; 8]),
475 excluded_ancestors: vec![]
476 }
477 );
478 }
479 }
480
481 #[tokio::test(flavor = "current_thread", start_paused = true)]
482 async fn subscriber_reconnects_when_stream_makes_no_progress() {
483 let (context, _keys) = Context::new_for_test(4);
484 let context = Arc::new(context);
485 let authority_service = Arc::new(Mutex::new(TestService::new()));
486 let network_client = Arc::new(SubscriberTestClient::new_pending());
487 let store = Arc::new(MemStore::new());
488 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
489 let subscriber = Subscriber::new(
490 context.clone(),
491 network_client.clone(),
492 authority_service,
493 dag_state,
494 );
495
496 let peer = context.committee.to_authority_index(2).unwrap();
497 subscriber.subscribe(peer);
498
499 tokio::time::sleep(SUBSCRIPTION_TIMEOUT + Duration::from_millis(1)).await;
500
501 assert!(
502 network_client.subscribe_calls().len() >= 2,
503 "an idle subscription should be re-established"
504 );
505 }
506
507 #[tokio::test(flavor = "current_thread", start_paused = true)]
508 async fn subscriber_retries_when_subscribing_makes_no_progress() {
509 let (context, _keys) = Context::new_for_test(4);
510 let context = Arc::new(context);
511 let authority_service = Arc::new(Mutex::new(TestService::new()));
512 let network_client = Arc::new(SubscriberTestClient::new_hanging_subscribe());
515 let store = Arc::new(MemStore::new());
516 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
517 let subscriber = Subscriber::new(
518 context.clone(),
519 network_client.clone(),
520 authority_service,
521 dag_state,
522 );
523
524 let peer = context.committee.to_authority_index(2).unwrap();
525 subscriber.subscribe(peer);
526
527 tokio::time::sleep(SUBSCRIPTION_TIMEOUT + Duration::from_millis(1)).await;
528
529 assert!(
530 network_client.subscribe_calls().len() >= 2,
531 "subscribing should be abandoned and retried when the peer never responds"
532 );
533 }
534
535 #[tokio::test(flavor = "current_thread", start_paused = true)]
536 async fn subscriber_stays_subscribed_when_stream_progresses_within_idle_timeout() {
537 let (context, _keys) = Context::new_for_test(4);
538 let context = Arc::new(context);
539 let authority_service = Arc::new(Mutex::new(TestService::new()));
540 let network_client = Arc::new(SubscriberTestClient::new_with_block_interval(
543 SUBSCRIPTION_TIMEOUT - Duration::from_secs(1),
544 ));
545 let store = Arc::new(MemStore::new());
546 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
547 let subscriber = Subscriber::new(
548 context.clone(),
549 network_client.clone(),
550 authority_service.clone(),
551 dag_state,
552 );
553
554 let peer = context.committee.to_authority_index(2).unwrap();
555 subscriber.subscribe(peer);
556
557 tokio::time::sleep(SUBSCRIPTION_TIMEOUT * 4).await;
558
559 assert_eq!(
560 network_client.subscribe_calls().len(),
561 1,
562 "a stream that keeps delivering blocks within the idle timeout should not be re-established"
563 );
564 assert!(
565 !authority_service.lock().handle_send_block.is_empty(),
566 "blocks from the slow stream should have been processed"
567 );
568 }
569
570 #[tokio::test(flavor = "current_thread", start_paused = true)]
574 async fn subscriber_recomputes_resume_round_on_reconnect() {
575 use crate::block::TestBlock;
576
577 let (context, _keys) = Context::new_for_test(4);
578 let context = Arc::new(context);
579 let store = Arc::new(MemStore::new());
580 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
581 let network_client = Arc::new(SubscriberTestClient::new());
582 let authority_service = Arc::new(Mutex::new(TestService::new()));
583 let subscriber = Subscriber::new(
584 context.clone(),
585 network_client.clone(),
586 authority_service,
587 dag_state.clone(),
588 );
589
590 let peer = context.committee.to_authority_index(2).unwrap();
591 subscriber.subscribe(peer);
592
593 tokio::time::sleep(Duration::from_secs(3)).await;
595 {
596 let recorded = network_client.subscribe_calls();
597 assert!(
598 !recorded.is_empty() && recorded.iter().all(|&r| r == 0),
599 "before a block is accepted, every reconnect should resume from round 0: {recorded:?}"
600 );
601 }
602
603 const RESUME_ROUND: Round = 10;
605 dag_state.write().accept_block(VerifiedBlock::new_for_test(
606 TestBlock::new(RESUME_ROUND, peer.value() as u32).build(),
607 ));
608
609 let mut observed_resume = false;
612 for _ in 0..10 {
613 tokio::time::sleep(Duration::from_secs(1)).await;
614 if network_client.subscribe_calls().last() == Some(&RESUME_ROUND) {
615 observed_resume = true;
616 break;
617 }
618 }
619 assert!(
620 observed_resume,
621 "after accepting a block at round {RESUME_ROUND}, the subscriber should resume from it; \
622 recorded resume rounds: {:?}",
623 network_client.subscribe_calls()
624 );
625 }
626}