1use std::{sync::Arc, time::Duration};
5
6use consensus_config::AuthorityIndex;
7use consensus_types::block::Round;
8use futures::StreamExt;
9use mysten_metrics::spawn_monitored_task;
10use parking_lot::{Mutex, RwLock};
11use tokio::{task::JoinHandle, time::sleep};
12use tracing::{debug, error, info};
13
14use crate::{
15 block::BlockAPI as _,
16 context::Context,
17 dag_state::DagState,
18 error::ConsensusError,
19 network::{ValidatorNetworkClient, ValidatorNetworkService},
20};
21
22pub(crate) struct Subscriber<C: ValidatorNetworkClient, S: ValidatorNetworkService> {
28 context: Arc<Context>,
29 network_client: Arc<C>,
30 authority_service: Arc<S>,
31 dag_state: Arc<RwLock<DagState>>,
32 subscriptions: Arc<Mutex<Box<[Option<JoinHandle<()>>]>>>,
33}
34
35impl<C: ValidatorNetworkClient, S: ValidatorNetworkService> Subscriber<C, S> {
36 pub(crate) fn new(
37 context: Arc<Context>,
38 network_client: Arc<C>,
39 authority_service: Arc<S>,
40 dag_state: Arc<RwLock<DagState>>,
41 ) -> Self {
42 let subscriptions = (0..context.committee.size())
43 .map(|_| None)
44 .collect::<Vec<_>>();
45 Self {
46 context,
47 network_client,
48 authority_service,
49 dag_state,
50 subscriptions: Arc::new(Mutex::new(subscriptions.into_boxed_slice())),
51 }
52 }
53
54 pub(crate) fn subscribe(&self, peer: AuthorityIndex) {
55 if peer == self.context.own_index {
56 error!("Attempt to subscribe to own validator {peer} is ignored!");
57 return;
58 }
59 let context = self.context.clone();
60 let network_client = self.network_client.clone();
61 let authority_service = self.authority_service.clone();
62 let dag_state = self.dag_state.clone();
63
64 let mut subscriptions = self.subscriptions.lock();
65 self.unsubscribe_locked(peer, &mut subscriptions[peer.value()]);
66 subscriptions[peer.value()] = Some(spawn_monitored_task!(Self::subscription_loop(
67 context,
68 network_client,
69 authority_service,
70 dag_state,
71 peer,
72 )));
73 }
74
75 pub(crate) fn stop(&self) {
76 let mut subscriptions = self.subscriptions.lock();
77 for (peer, _) in self.context.committee.authorities() {
78 self.unsubscribe_locked(peer, &mut subscriptions[peer.value()]);
79 }
80 }
81
82 fn unsubscribe_locked(&self, peer: AuthorityIndex, subscription: &mut Option<JoinHandle<()>>) {
83 let peer_hostname = &self.context.committee.authority(peer).hostname;
84 if let Some(subscription) = subscription.take() {
85 subscription.abort();
86 }
87 self.context
90 .metrics
91 .node_metrics
92 .subscribed_to
93 .with_label_values(&[peer_hostname])
94 .set(0);
95 }
96
97 async fn subscription_loop(
98 context: Arc<Context>,
99 network_client: Arc<C>,
100 authority_service: Arc<S>,
101 dag_state: Arc<RwLock<DagState>>,
102 peer: AuthorityIndex,
103 ) {
104 const IMMEDIATE_RETRIES: i64 = 3;
105 const MIN_TIMEOUT: Duration = Duration::from_millis(500);
106 let mut backoff = mysten_common::backoff::ExponentialBackoff::new(
108 Duration::from_millis(100),
109 Duration::from_secs(10),
110 );
111
112 let peer_hostname = &context.committee.authority(peer).hostname;
113 let mut retries: i64 = 0;
114 'subscription: loop {
115 context
116 .metrics
117 .node_metrics
118 .subscribed_to
119 .with_label_values(&[peer_hostname])
120 .set(0);
121
122 let mut delay = Duration::ZERO;
123 if retries > IMMEDIATE_RETRIES {
124 delay = backoff.next().unwrap();
125 debug!(
126 "Delaying retry {} of peer {} subscription, in {} seconds",
127 retries,
128 peer_hostname,
129 delay.as_secs_f32(),
130 );
131 sleep(delay).await;
132 } else if retries > 0 {
133 tokio::task::yield_now().await;
135 }
136 retries += 1;
137
138 let last_received: Round = {
142 let dag_state = dag_state.read();
143 let gc_round = dag_state.gc_round();
144 dag_state
145 .get_last_block_for_authority(peer)
146 .round()
147 .max(gc_round)
148 };
149
150 let request_timeout = MIN_TIMEOUT.max(delay);
152 let mut blocks = match network_client
153 .subscribe_blocks(peer, last_received, request_timeout)
154 .await
155 {
156 Ok(blocks) => {
157 debug!(
158 "Subscribed to peer {} {} after {} attempts",
159 peer, peer_hostname, retries
160 );
161 context
162 .metrics
163 .node_metrics
164 .subscriber_connection_attempts
165 .with_label_values(&[peer_hostname.as_str(), "success"])
166 .inc();
167 blocks
168 }
169 Err(e) => {
170 debug!(
171 "Failed to subscribe to blocks from peer {} {}: {}",
172 peer, peer_hostname, e
173 );
174 context
175 .metrics
176 .node_metrics
177 .subscriber_connection_attempts
178 .with_label_values(&[peer_hostname.as_str(), "failure"])
179 .inc();
180 continue 'subscription;
181 }
182 };
183
184 context
186 .metrics
187 .node_metrics
188 .subscribed_to
189 .with_label_values(&[peer_hostname])
190 .set(1);
191
192 'stream: loop {
193 match blocks.next().await {
194 Some(block) => {
195 context
196 .metrics
197 .node_metrics
198 .subscribed_blocks
199 .with_label_values(&[peer_hostname])
200 .inc();
201 let result = authority_service.handle_send_block(peer, block).await;
202 if let Err(e) = result {
203 match e {
204 ConsensusError::BlockRejected { block_ref, reason } => {
205 debug!(
206 "Failed to process block from peer {} {} for block {:?}: {}",
207 peer, peer_hostname, block_ref, reason
208 );
209 }
210 _ => {
211 info!(
212 "Invalid block received from peer {} {}: {}",
213 peer, peer_hostname, e
214 );
215 }
216 }
217 }
218 retries = 0;
222 backoff.reset();
223 }
224 None => {
225 debug!(
226 "Subscription to blocks from peer {} {} ended",
227 peer, peer_hostname
228 );
229 retries += 1;
230 break 'stream;
231 }
232 }
233 }
234 }
235 }
236}
237
238#[cfg(test)]
239mod test {
240 use async_trait::async_trait;
241 use bytes::Bytes;
242 use consensus_types::block::BlockRef;
243 use futures::stream;
244
245 use super::*;
246 use crate::{
247 VerifiedBlock,
248 commit::CommitRange,
249 error::ConsensusResult,
250 network::{BlockStream, ExtendedSerializedBlock, test_network::TestService},
251 storage::mem_store::MemStore,
252 };
253
254 struct SubscriberTestClient {
255 subscribe_calls: Mutex<Vec<Round>>,
257 }
258
259 impl SubscriberTestClient {
260 fn new() -> Self {
261 Self {
262 subscribe_calls: Mutex::new(Vec::new()),
263 }
264 }
265
266 fn subscribe_calls(&self) -> Vec<Round> {
267 self.subscribe_calls.lock().clone()
268 }
269 }
270
271 #[async_trait]
272 impl ValidatorNetworkClient for SubscriberTestClient {
273 async fn send_block(
274 &self,
275 _peer: AuthorityIndex,
276 _block: &VerifiedBlock,
277 _timeout: Duration,
278 ) -> ConsensusResult<()> {
279 unimplemented!("Unimplemented")
280 }
281
282 async fn subscribe_blocks(
283 &self,
284 _peer: AuthorityIndex,
285 last_received: Round,
286 _timeout: Duration,
287 ) -> ConsensusResult<BlockStream> {
288 self.subscribe_calls.lock().push(last_received);
289 let block_stream = stream::unfold((), |_| async {
290 sleep(Duration::from_millis(1)).await;
291 let block = ExtendedSerializedBlock {
292 block: Bytes::from(vec![1u8; 8]),
293 excluded_ancestors: vec![],
294 };
295 Some((block, ()))
296 })
297 .take(10);
298 Ok(Box::pin(block_stream))
299 }
300
301 async fn fetch_blocks(
302 &self,
303 _peer: AuthorityIndex,
304 _block_refs: Vec<BlockRef>,
305 _fetch_after_rounds: Vec<Round>,
306 _fetch_missing_ancestors: bool,
307 _timeout: Duration,
308 ) -> ConsensusResult<Vec<Bytes>> {
309 unimplemented!("Unimplemented")
310 }
311
312 async fn fetch_commits(
313 &self,
314 _peer: AuthorityIndex,
315 _commit_range: CommitRange,
316 _timeout: Duration,
317 ) -> ConsensusResult<(Vec<Bytes>, Vec<Bytes>)> {
318 unimplemented!("Unimplemented")
319 }
320
321 async fn fetch_latest_blocks(
322 &self,
323 _peer: AuthorityIndex,
324 _authorities: Vec<AuthorityIndex>,
325 _timeout: Duration,
326 ) -> ConsensusResult<Vec<Bytes>> {
327 unimplemented!("Unimplemented")
328 }
329
330 async fn get_latest_rounds(
331 &self,
332 _peer: AuthorityIndex,
333 _timeout: Duration,
334 ) -> ConsensusResult<(Vec<Round>, Vec<Round>)> {
335 unimplemented!("Unimplemented")
336 }
337 }
338
339 #[tokio::test(flavor = "current_thread", start_paused = true)]
340 async fn subscriber_retries() {
341 let (context, _keys) = Context::new_for_test(4);
342 let context = Arc::new(context);
343 let authority_service = Arc::new(Mutex::new(TestService::new()));
344 let network_client = Arc::new(SubscriberTestClient::new());
345 let store = Arc::new(MemStore::new());
346 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
347 let subscriber = Subscriber::new(
348 context.clone(),
349 network_client,
350 authority_service.clone(),
351 dag_state,
352 );
353
354 let peer = context.committee.to_authority_index(2).unwrap();
355 subscriber.subscribe(peer);
356
357 for _ in 0..10 {
359 tokio::time::sleep(Duration::from_secs(1)).await;
360 let service = authority_service.lock();
361 if service.handle_send_block.len() >= 100 {
362 break;
363 }
364 }
365
366 let service = authority_service.lock();
369 assert!(service.handle_send_block.len() >= 100);
370 for (p, block) in service.handle_send_block.iter() {
371 assert_eq!(*p, peer);
372 assert_eq!(
373 *block,
374 ExtendedSerializedBlock {
375 block: Bytes::from(vec![1u8; 8]),
376 excluded_ancestors: vec![]
377 }
378 );
379 }
380 }
381
382 #[tokio::test(flavor = "current_thread", start_paused = true)]
386 async fn subscriber_recomputes_resume_round_on_reconnect() {
387 use crate::block::TestBlock;
388
389 let (context, _keys) = Context::new_for_test(4);
390 let context = Arc::new(context);
391 let store = Arc::new(MemStore::new());
392 let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store)));
393 let network_client = Arc::new(SubscriberTestClient::new());
394 let authority_service = Arc::new(Mutex::new(TestService::new()));
395 let subscriber = Subscriber::new(
396 context.clone(),
397 network_client.clone(),
398 authority_service,
399 dag_state.clone(),
400 );
401
402 let peer = context.committee.to_authority_index(2).unwrap();
403 subscriber.subscribe(peer);
404
405 tokio::time::sleep(Duration::from_secs(3)).await;
407 {
408 let recorded = network_client.subscribe_calls();
409 assert!(
410 !recorded.is_empty() && recorded.iter().all(|&r| r == 0),
411 "before a block is accepted, every reconnect should resume from round 0: {recorded:?}"
412 );
413 }
414
415 const RESUME_ROUND: Round = 10;
417 dag_state.write().accept_block(VerifiedBlock::new_for_test(
418 TestBlock::new(RESUME_ROUND, peer.value() as u32).build(),
419 ));
420
421 let mut observed_resume = false;
424 for _ in 0..10 {
425 tokio::time::sleep(Duration::from_secs(1)).await;
426 if network_client.subscribe_calls().last() == Some(&RESUME_ROUND) {
427 observed_resume = true;
428 break;
429 }
430 }
431 assert!(
432 observed_resume,
433 "after accepting a block at round {RESUME_ROUND}, the subscriber should resume from it; \
434 recorded resume rounds: {:?}",
435 network_client.subscribe_calls()
436 );
437 }
438}