Skip to main content

consensus_core/
authority_service.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{collections::BTreeMap, pin::Pin, sync::Arc, time::Duration};
5
6use async_trait::async_trait;
7use bytes::Bytes;
8use consensus_config::AuthorityIndex;
9use consensus_types::block::{BlockRef, Round};
10use futures::{Stream, StreamExt, ready, stream, task};
11use mysten_metrics::spawn_monitored_task;
12use parking_lot::RwLock;
13use sui_macros::fail_point_async;
14use tap::TapFallible;
15use tokio::sync::broadcast;
16use tokio_util::sync::ReusableBoxFuture;
17use tracing::{debug, info, warn};
18
19use crate::{
20    block::{BlockAPI as _, ExtendedBlock, GENESIS_ROUND, SignedBlock, VerifiedBlock},
21    block_sync_service::BlockSyncService,
22    block_verifier::BlockVerifier,
23    commit::{CommitRange, TrustedCommit},
24    commit_vote_monitor::{CommitVoteMonitor, is_commit_lagging},
25    context::Context,
26    core_thread::CoreThreadDispatcher,
27    dag_state::DagState,
28    error::{ConsensusError, ConsensusResult},
29    network::{
30        BlockStream, ExtendedSerializedBlock, PeerId, SerializedBlockForm, ValidatorNetworkService,
31    },
32    round_tracker::RoundTracker,
33    synchronizer::SynchronizerHandle,
34    task::spawn_blocking,
35    transaction_vote_tracker::TransactionVoteTracker,
36};
37
38/// Authority's network service implementation, agnostic to the actual networking stack used.
39pub(crate) struct AuthorityService<C: CoreThreadDispatcher> {
40    context: Arc<Context>,
41    commit_vote_monitor: Arc<CommitVoteMonitor>,
42    block_verifier: Arc<dyn BlockVerifier>,
43    synchronizer: Arc<SynchronizerHandle>,
44    core_dispatcher: Arc<C>,
45    rx_block_broadcast: broadcast::Receiver<ExtendedBlock>,
46    subscription_counter: Arc<SubscriptionCounter>,
47    transaction_vote_tracker: TransactionVoteTracker,
48    dag_state: Arc<RwLock<DagState>>,
49    round_tracker: Arc<RwLock<RoundTracker>>,
50    block_sync_service: Arc<BlockSyncService>,
51}
52
53impl<C: CoreThreadDispatcher> AuthorityService<C> {
54    pub(crate) fn new(
55        context: Arc<Context>,
56        block_verifier: Arc<dyn BlockVerifier>,
57        commit_vote_monitor: Arc<CommitVoteMonitor>,
58        round_tracker: Arc<RwLock<RoundTracker>>,
59        synchronizer: Arc<SynchronizerHandle>,
60        core_dispatcher: Arc<C>,
61        rx_block_broadcast: broadcast::Receiver<ExtendedBlock>,
62        transaction_vote_tracker: TransactionVoteTracker,
63        dag_state: Arc<RwLock<DagState>>,
64        block_sync_service: Arc<BlockSyncService>,
65    ) -> Self {
66        let subscription_counter = Arc::new(SubscriptionCounter::new(context.clone()));
67        Self {
68            context,
69            block_verifier,
70            commit_vote_monitor,
71            synchronizer,
72            core_dispatcher,
73            rx_block_broadcast,
74            subscription_counter,
75            transaction_vote_tracker,
76            dag_state,
77            round_tracker,
78            block_sync_service,
79        }
80    }
81
82    // Parses and validates serialized excluded ancestors.
83    fn parse_excluded_ancestors(
84        &self,
85        peer: AuthorityIndex,
86        block: &VerifiedBlock,
87        mut excluded_ancestors: Vec<Vec<u8>>,
88    ) -> ConsensusResult<Vec<BlockRef>> {
89        let peer_hostname = &self.context.committee.authority(peer).hostname;
90
91        let excluded_ancestors_limit = self.context.committee.size() * 2;
92        if excluded_ancestors.len() > excluded_ancestors_limit {
93            debug!(
94                "Dropping {} excluded ancestor(s) from {} {} due to size limit",
95                excluded_ancestors.len() - excluded_ancestors_limit,
96                peer,
97                peer_hostname,
98            );
99            excluded_ancestors.truncate(excluded_ancestors_limit);
100        }
101
102        let excluded_ancestors = excluded_ancestors
103            .into_iter()
104            .map(|serialized| {
105                let block_ref: BlockRef =
106                    bcs::from_bytes(&serialized).map_err(ConsensusError::MalformedBlock)?;
107                if !self.context.committee.is_valid_index(block_ref.author) {
108                    return Err(ConsensusError::InvalidAuthorityIndex {
109                        loc: format!("excluded ancestor {}", block_ref),
110                        index: block_ref.author,
111                        max: self.context.committee.size() - 1,
112                    });
113                }
114                if block_ref.round >= block.round() {
115                    return Err(ConsensusError::InvalidAncestorRound {
116                        ancestor: block_ref.round,
117                        block: block.round(),
118                    });
119                }
120                Ok(block_ref)
121            })
122            .collect::<ConsensusResult<Vec<BlockRef>>>()?;
123
124        for excluded_ancestor in &excluded_ancestors {
125            let excluded_ancestor_hostname = &self
126                .context
127                .committee
128                .authority(excluded_ancestor.author)
129                .hostname;
130            self.context
131                .metrics
132                .node_metrics
133                .network_excluded_ancestors_count_by_authority
134                .with_label_values(&[excluded_ancestor_hostname])
135                .inc();
136        }
137        self.context
138            .metrics
139            .node_metrics
140            .network_received_excluded_ancestors_from_authority
141            .with_label_values(&[peer_hostname])
142            .inc_by(excluded_ancestors.len() as u64);
143
144        Ok(excluded_ancestors)
145    }
146}
147
148#[async_trait]
149impl<C: CoreThreadDispatcher> ValidatorNetworkService for AuthorityService<C> {
150    async fn handle_send_block(
151        &self,
152        peer: AuthorityIndex,
153        serialized_block: ExtendedSerializedBlock,
154    ) -> ConsensusResult<()> {
155        fail_point_async!("consensus-rpc-response");
156
157        let peer_hostname = &self.context.committee.authority(peer).hostname;
158
159        // TODO: dedup block verifications, here and with fetched blocks.
160        // Only the full form reaches this service: a slim payload is decoded back to
161        // full upstream (or dropped, before the decoder exists).
162        let SerializedBlockForm::Full(serialized_bytes) = serialized_block.block else {
163            return Err(ConsensusError::UnexpectedBlockForm);
164        };
165        let signed_block: SignedBlock =
166            bcs::from_bytes(&serialized_bytes).map_err(ConsensusError::MalformedBlock)?;
167
168        // Reject blocks not produced by the peer.
169        if peer != signed_block.author() {
170            self.context
171                .metrics
172                .node_metrics
173                .invalid_blocks
174                .with_label_values(&[
175                    peer_hostname.as_str(),
176                    "handle_send_block",
177                    "UnexpectedAuthority",
178                ])
179                .inc();
180            let e = ConsensusError::UnexpectedAuthority(signed_block.author(), peer);
181            info!("Block with wrong authority from {}: {}", peer, e);
182            return Err(e);
183        }
184
185        // Reject blocks failing parsing and validations.
186        let block_verifier = self.block_verifier.clone();
187        let serialized = serialized_bytes.clone();
188        let (verified_block, reject_txn_votes) =
189            spawn_blocking(move || block_verifier.verify_and_vote(signed_block, serialized))
190                .await?
191                .tap_err(|e| {
192                    self.context
193                        .metrics
194                        .node_metrics
195                        .invalid_blocks
196                        .with_label_values(&[peer_hostname.as_str(), "handle_send_block", e.name()])
197                        .inc();
198                    info!("Invalid block from {}: {}", peer, e);
199                })?;
200        let excluded_ancestors = self
201            .parse_excluded_ancestors(peer, &verified_block, serialized_block.excluded_ancestors)
202            .tap_err(|e| {
203                debug!("Failed to parse excluded ancestors from {peer} {peer_hostname}: {e}");
204                self.context
205                    .metrics
206                    .node_metrics
207                    .invalid_blocks
208                    .with_label_values(&[peer_hostname.as_str(), "handle_send_block", e.name()])
209                    .inc();
210            })?;
211
212        let block_ref = verified_block.reference();
213        debug!("Received block {} via send block.", block_ref);
214
215        self.context
216            .metrics
217            .node_metrics
218            .verified_blocks
219            .with_label_values(&[peer_hostname])
220            .inc();
221
222        let now = self.context.clock.timestamp_utc_ms();
223        let forward_time_drift =
224            Duration::from_millis(verified_block.timestamp_ms().saturating_sub(now));
225
226        self.context
227            .metrics
228            .node_metrics
229            .block_timestamp_drift_ms
230            .with_label_values(&[peer_hostname.as_str(), "handle_send_block"])
231            .inc_by(forward_time_drift.as_millis() as u64);
232
233        // Observe the block for the commit votes. When local commit is lagging too much,
234        // commit sync loop will trigger fetching.
235        self.commit_vote_monitor.observe_block(&verified_block);
236
237        // Update own received rounds and peer accepted rounds from this verified block.
238        self.round_tracker
239            .write()
240            .update_from_verified_block(&ExtendedBlock {
241                block: verified_block.clone(),
242                excluded_ancestors: excluded_ancestors.clone(),
243            });
244
245        // Reject blocks when local commit index is lagging too far from quorum commit index,
246        // to avoid the memory overhead from suspended blocks.
247        //
248        // IMPORTANT: this must be done after observing votes from the block, otherwise
249        // observed quorum commit will no longer progress.
250        //
251        // Since the main issue with too many suspended blocks is memory usage not CPU,
252        // it is ok to reject after block verifications instead of before.
253        let last_commit_index = self.dag_state.read().last_commit_index();
254        let quorum_commit_index = self.commit_vote_monitor.quorum_commit_index();
255        if is_commit_lagging(
256            self.context.as_ref(),
257            last_commit_index,
258            quorum_commit_index,
259        ) {
260            self.context
261                .metrics
262                .node_metrics
263                .rejected_blocks
264                .with_label_values(&["commit_lagging"])
265                .inc();
266            debug!(
267                "Block {:?} is rejected because last commit index is lagging quorum commit index too much ({} < {})",
268                block_ref, last_commit_index, quorum_commit_index,
269            );
270            return Err(ConsensusError::BlockRejected {
271                block_ref,
272                reason: format!(
273                    "Last commit index is lagging quorum commit index too much ({} < {})",
274                    last_commit_index, quorum_commit_index,
275                ),
276            });
277        }
278
279        // The block is verified and current, so record own votes on the block
280        // before sending the block to Core.
281        if self.context.protocol_config.transaction_voting_enabled() {
282            self.transaction_vote_tracker
283                .add_voted_blocks(vec![(verified_block.clone(), reject_txn_votes)]);
284        }
285
286        // Send the block to Core to try accepting it into the DAG.
287        let missing_ancestors = self
288            .core_dispatcher
289            .add_blocks(vec![verified_block.clone()])
290            .await
291            .map_err(|_| ConsensusError::Shutdown)?;
292
293        // Schedule fetching missing ancestors from this peer in the background.
294        if !missing_ancestors.is_empty() {
295            self.context
296                .metrics
297                .node_metrics
298                .handler_received_block_missing_ancestors
299                .with_label_values(&[peer_hostname])
300                .inc_by(missing_ancestors.len() as u64);
301            let synchronizer = self.synchronizer.clone();
302            spawn_monitored_task!(async move {
303                // This does not wait for the fetch request to complete.
304                // It only waits for synchronizer to queue the request to a peer.
305                // When this fails, it usually means the queue is full.
306                // The fetch will retry from other peers via live and periodic syncs.
307                if let Err(err) = synchronizer
308                    .fetch_blocks(missing_ancestors, PeerId::Validator(peer))
309                    .await
310                {
311                    debug!("Failed to fetch missing ancestors via synchronizer: {err}");
312                }
313            });
314        }
315
316        // Schedule fetching missing soft links from this peer in the background.
317        let missing_excluded_ancestors = self
318            .core_dispatcher
319            .check_block_refs(excluded_ancestors)
320            .await
321            .map_err(|_| ConsensusError::Shutdown)?;
322        if !missing_excluded_ancestors.is_empty() {
323            self.context
324                .metrics
325                .node_metrics
326                .network_excluded_ancestors_sent_to_fetch
327                .with_label_values(&[peer_hostname])
328                .inc_by(missing_excluded_ancestors.len() as u64);
329
330            let synchronizer = self.synchronizer.clone();
331            spawn_monitored_task!(async move {
332                if let Err(err) = synchronizer
333                    .fetch_blocks(missing_excluded_ancestors, PeerId::Validator(peer))
334                    .await
335                {
336                    debug!("Failed to fetch excluded ancestors via synchronizer: {err}");
337                }
338            });
339        }
340
341        Ok(())
342    }
343
344    async fn handle_subscribe_blocks(
345        &self,
346        peer: AuthorityIndex,
347        last_received: Round,
348    ) -> ConsensusResult<BlockStream> {
349        fail_point_async!("consensus-rpc-response");
350
351        // Subscribe before snapshotting past blocks below. This can duplicate
352        // a block in both the subscription stream and snapshot, which is fine.
353        // Otherwise, it is possible to miss a block if it is broadcasted after snapshotting
354        // but before subscribing.
355        let broadcast_rx = self.rx_block_broadcast.resubscribe();
356
357        // Find past proposed blocks as the initial blocks to send to the peer.
358        //
359        // If there are cached blocks in the range which the peer requested, send all of them.
360        // The size is bounded by the local GC round and DagState cache size.
361        //
362        // Otherwise if there is no cached block in the range which the peer requested,
363        // and this node has proposed blocks before, at least one block should be sent to the peer
364        // to help with liveness.
365        let past_proposed_blocks = {
366            let dag_state = self.dag_state.read();
367
368            // Saturate so an out-of-range round from the peer cannot wrap to 0 and
369            // replay the entire block cache.
370            let mut proposed_blocks = dag_state
371                .get_cached_blocks(self.context.own_index, last_received.saturating_add(1));
372            if proposed_blocks.is_empty() {
373                let last_proposed_block = dag_state
374                    .get_last_proposed_block()
375                    .expect("Last proposed block should be returned on validators");
376                proposed_blocks = if last_proposed_block.round() > GENESIS_ROUND {
377                    vec![last_proposed_block]
378                } else {
379                    vec![]
380                };
381            }
382            stream::iter(
383                proposed_blocks
384                    .into_iter()
385                    .map(|block| ExtendedSerializedBlock {
386                        block: SerializedBlockForm::Full(block.serialized().clone()),
387                        excluded_ancestors: vec![],
388                    }),
389            )
390        };
391
392        // Ok to not batch own proposed blocks, which is < 20/s.
393        const MAX_BLOCKS_PER_POLL: usize = 1;
394        let broadcasted_blocks = BroadcastedBlockStream::new(
395            PeerId::Validator(peer),
396            broadcast_rx,
397            MAX_BLOCKS_PER_POLL,
398            self.subscription_counter.clone(),
399        );
400
401        // Return a stream of blocks that first yields missed blocks as requested, then new blocks.
402        Ok(Box::pin(past_proposed_blocks.chain(
403            broadcasted_blocks.flat_map(|items| {
404                debug_assert!(
405                    items.len() <= MAX_BLOCKS_PER_POLL,
406                    "Too many blocks received from broadcast"
407                );
408                stream::iter(items.into_iter().map(ExtendedSerializedBlock::from))
409            }),
410        )))
411    }
412
413    // Handles 3 types of requests:
414    // 1. Live sync:
415    //    - Both missing block refs and highest accepted rounds are specified.
416    //    - fetch_missing_ancestors is true.
417    //    - response returns max_blocks_per_sync blocks.
418    // 2. Periodic sync:
419    //    - Highest accepted rounds must be specified.
420    //    - Missing block refs are optional.
421    //    - fetch_missing_ancestors is false (default).
422    //    - response returns max_blocks_per_fetch blocks.
423    // 3. Commit sync:
424    //    - Missing block refs are specified.
425    //    - Highest accepted rounds are empty.
426    //    - fetch_missing_ancestors is false (default).
427    //    - response returns max_blocks_per_fetch blocks.
428    async fn handle_fetch_blocks(
429        &self,
430        _peer: AuthorityIndex,
431        block_refs: Vec<BlockRef>,
432        fetch_after_rounds: Vec<Round>,
433        fetch_missing_ancestors: bool,
434    ) -> ConsensusResult<Vec<Bytes>> {
435        fail_point_async!("consensus-rpc-response");
436
437        // Delegate to BlockSyncService
438        self.block_sync_service
439            .fetch_blocks(block_refs, fetch_after_rounds, fetch_missing_ancestors)
440            .await
441    }
442
443    async fn handle_fetch_commits(
444        &self,
445        _peer: AuthorityIndex,
446        commit_range: CommitRange,
447    ) -> ConsensusResult<(Vec<TrustedCommit>, Vec<VerifiedBlock>)> {
448        fail_point_async!("consensus-rpc-response");
449
450        // Delegate to BlockSyncService
451        self.block_sync_service.fetch_commits(commit_range).await
452    }
453
454    async fn handle_fetch_latest_blocks(
455        &self,
456        peer: AuthorityIndex,
457        authorities: Vec<AuthorityIndex>,
458    ) -> ConsensusResult<Vec<Bytes>> {
459        fail_point_async!("consensus-rpc-response");
460
461        // Delegate to BlockSyncService
462        self.block_sync_service
463            .fetch_latest_blocks(peer, authorities)
464            .await
465    }
466
467    async fn handle_get_latest_rounds(
468        &self,
469        _peer: AuthorityIndex,
470    ) -> ConsensusResult<(Vec<Round>, Vec<Round>)> {
471        fail_point_async!("consensus-rpc-response");
472
473        let highest_received_rounds = self.round_tracker.read().local_highest_received_rounds();
474
475        let blocks = self
476            .dag_state
477            .read()
478            .get_last_cached_block_per_authority(Round::MAX);
479        let highest_accepted_rounds = blocks
480            .into_iter()
481            .map(|(block, _)| block.round())
482            .collect::<Vec<_>>();
483
484        Ok((highest_received_rounds, highest_accepted_rounds))
485    }
486}
487
488struct Counter {
489    count: usize,
490    subscriptions_by_peer: BTreeMap<PeerId, usize>,
491}
492
493/// Atomically counts the number of active subscriptions to the block broadcast stream.
494pub(crate) struct SubscriptionCounter {
495    context: Arc<Context>,
496    counter: parking_lot::Mutex<Counter>,
497}
498
499impl SubscriptionCounter {
500    pub(crate) fn new(context: Arc<Context>) -> Self {
501        // Set the subscribed peers by default to 0
502        for (_, authority) in context.committee.authorities() {
503            context
504                .metrics
505                .node_metrics
506                .subscribed_by
507                .with_label_values(&[authority.hostname.as_str()])
508                .set(0);
509        }
510
511        Self {
512            counter: parking_lot::Mutex::new(Counter {
513                count: 0,
514                subscriptions_by_peer: BTreeMap::new(),
515            }),
516            context,
517        }
518    }
519
520    fn increment(&self, peer: &PeerId) {
521        let mut counter = self.counter.lock();
522        counter.count += 1;
523        let peer_count = {
524            let count = counter
525                .subscriptions_by_peer
526                .entry(peer.clone())
527                .or_default();
528            *count += 1;
529            *count
530        };
531
532        match peer {
533            PeerId::Validator(authority) => {
534                let peer_hostname = &self.context.committee.authority(*authority).hostname;
535                self.context
536                    .metrics
537                    .node_metrics
538                    .subscribed_by
539                    .with_label_values(&[peer_hostname])
540                    .set(1);
541            }
542            PeerId::Observer(_) => {
543                // Only count the first subscription from each peer.
544                if peer_count == 1 {
545                    self.context
546                        .metrics
547                        .node_metrics
548                        .subscribed_by
549                        .with_label_values(&["observer"])
550                        .inc();
551                }
552            }
553        }
554    }
555
556    fn decrement(&self, peer: &PeerId) {
557        let mut counter = self.counter.lock();
558        counter.count = counter.count.saturating_sub(1);
559        let peer_count = counter
560            .subscriptions_by_peer
561            .entry(peer.clone())
562            .or_default();
563        *peer_count = peer_count.saturating_sub(1);
564
565        if *peer_count == 0 {
566            match peer {
567                PeerId::Validator(authority) => {
568                    let peer_hostname = &self.context.committee.authority(*authority).hostname;
569                    self.context
570                        .metrics
571                        .node_metrics
572                        .subscribed_by
573                        .with_label_values(&[peer_hostname])
574                        .set(0);
575                }
576                PeerId::Observer(_) => {
577                    self.context
578                        .metrics
579                        .node_metrics
580                        .subscribed_by
581                        .with_label_values(&["observer"])
582                        .dec();
583                }
584            }
585        }
586    }
587}
588
589/// Each broadcasted block stream wraps a broadcast receiver for blocks.
590/// It yields blocks that are broadcasted after the stream is created.
591type BroadcastedBlockStream = BroadcastStream<ExtendedBlock>;
592
593/// Adapted from `tokio_stream::wrappers::BroadcastStream`. The main difference is that
594/// this tolerates lags with only logging, without yielding errors.
595pub(crate) struct BroadcastStream<T> {
596    peer: Option<PeerId>,
597    // Stores the receiver across poll_next() calls.
598    inner: ReusableBoxFuture<
599        'static,
600        (
601            Result<T, broadcast::error::RecvError>,
602            broadcast::Receiver<T>,
603        ),
604    >,
605    // Maximum number of items to return per poll.
606    max_items_per_poll: usize,
607    // Counts total subscriptions / active BroadcastStreams.
608    subscription_counter: Option<Arc<SubscriptionCounter>>,
609}
610
611impl<T: 'static + Clone + Send> BroadcastStream<T> {
612    pub fn new(
613        peer: PeerId,
614        rx: broadcast::Receiver<T>,
615        max_items_per_poll: usize,
616        subscription_counter: Arc<SubscriptionCounter>,
617    ) -> Self {
618        assert!(max_items_per_poll > 0, "max_items_per_poll must be > 0");
619        subscription_counter.increment(&peer);
620        Self {
621            peer: Some(peer),
622            inner: ReusableBoxFuture::new(make_recv_future(rx)),
623            max_items_per_poll,
624            subscription_counter: Some(subscription_counter),
625        }
626    }
627
628    /// Creates a stream without subscription tracking.
629    pub fn new_untracked(rx: broadcast::Receiver<T>, max_items_per_poll: usize) -> Self {
630        assert!(max_items_per_poll > 0, "max_items_per_poll must be > 0");
631        Self {
632            peer: None,
633            inner: ReusableBoxFuture::new(make_recv_future(rx)),
634            max_items_per_poll,
635            subscription_counter: None,
636        }
637    }
638}
639
640impl<T: 'static + Clone + Send> Stream for BroadcastStream<T> {
641    type Item = Vec<T>;
642
643    fn poll_next(
644        mut self: Pin<&mut Self>,
645        cx: &mut task::Context<'_>,
646    ) -> task::Poll<Option<Self::Item>> {
647        loop {
648            let (result, mut rx) = ready!(self.inner.poll(cx));
649
650            match result {
651                Ok(item) => {
652                    let mut items = Vec::new();
653                    items.push(item);
654
655                    // Drain any additional items that are already available, up to the cap.
656                    while items.len() < self.max_items_per_poll {
657                        match rx.try_recv() {
658                            Ok(item) => items.push(item),
659                            Err(broadcast::error::TryRecvError::Empty) => break,
660                            Err(broadcast::error::TryRecvError::Closed) => break,
661                            Err(broadcast::error::TryRecvError::Lagged(n)) => {
662                                warn!("BroadcastStream {:?} lagged by {} messages", self.peer, n);
663                                break;
664                            }
665                        }
666                    }
667
668                    self.inner.set(make_recv_future(rx));
669                    return task::Poll::Ready(Some(items));
670                }
671                Err(broadcast::error::RecvError::Closed) => {
672                    info!("BroadcastStream {:?} closed", self.peer);
673                    return task::Poll::Ready(None);
674                }
675                Err(broadcast::error::RecvError::Lagged(n)) => {
676                    warn!("BroadcastStream {:?} lagged by {} messages", self.peer, n);
677                    // Re-arm the future and loop to await the next item.
678                    self.inner.set(make_recv_future(rx));
679                    continue;
680                }
681            }
682        }
683    }
684}
685
686impl<T> Drop for BroadcastStream<T> {
687    fn drop(&mut self) {
688        if let (Some(counter), Some(peer)) = (&self.subscription_counter, &self.peer) {
689            counter.decrement(peer);
690        }
691    }
692}
693
694async fn make_recv_future<T: Clone>(
695    mut rx: broadcast::Receiver<T>,
696) -> (
697    Result<T, broadcast::error::RecvError>,
698    broadcast::Receiver<T>,
699) {
700    let result = rx.recv().await;
701    (result, rx)
702}
703
704#[cfg(test)]
705mod tests {
706    use std::{
707        collections::{BTreeMap, BTreeSet},
708        sync::Arc,
709        time::Duration,
710    };
711
712    use async_trait::async_trait;
713    use bytes::Bytes;
714    use consensus_config::AuthorityIndex;
715    use consensus_types::block::{BlockDigest, BlockRef, Round};
716    use parking_lot::{Mutex, RwLock};
717    use tokio::{sync::broadcast, time::sleep};
718
719    use futures::StreamExt as _;
720
721    /// Every wire payload in these tests is the full form.
722    fn expect_full(form: &SerializedBlockForm) -> &[u8] {
723        match form {
724            SerializedBlockForm::Full(bytes) => bytes,
725            SerializedBlockForm::Slim(_) => panic!("expected a full block"),
726        }
727    }
728
729    use crate::{
730        authority_service::AuthorityService,
731        block::{BlockAPI, SignedBlock, TestBlock, VerifiedBlock},
732        block_sync_service::BlockSyncService,
733        commit::{CertifiedCommits, CommitRange},
734        commit_vote_monitor::CommitVoteMonitor,
735        context::Context,
736        core_thread::{CoreError, CoreThreadDispatcher},
737        dag_state::DagState,
738        error::ConsensusResult,
739        network::{
740            BlockStream, ExtendedSerializedBlock, ObserverNetworkClient, SerializedBlockForm,
741            SynchronizerClient, ValidatorNetworkClient, ValidatorNetworkService,
742        },
743        peers_pool::PeersPool,
744        round_tracker::RoundTracker,
745        storage::mem_store::MemStore,
746        synchronizer::Synchronizer,
747        test_dag_builder::DagBuilder,
748        transaction_vote_tracker::TransactionVoteTracker,
749    };
750    struct FakeCoreThreadDispatcher {
751        blocks: Mutex<Vec<VerifiedBlock>>,
752    }
753
754    impl FakeCoreThreadDispatcher {
755        fn new() -> Self {
756            Self {
757                blocks: Mutex::new(vec![]),
758            }
759        }
760
761        fn get_blocks(&self) -> Vec<VerifiedBlock> {
762            self.blocks.lock().clone()
763        }
764    }
765
766    #[async_trait]
767    impl CoreThreadDispatcher for FakeCoreThreadDispatcher {
768        async fn add_blocks(
769            &self,
770            blocks: Vec<VerifiedBlock>,
771        ) -> Result<BTreeSet<BlockRef>, CoreError> {
772            let block_refs = blocks.iter().map(|b| b.reference()).collect();
773            self.blocks.lock().extend(blocks);
774            Ok(block_refs)
775        }
776
777        async fn check_block_refs(
778            &self,
779            _block_refs: Vec<BlockRef>,
780        ) -> Result<BTreeSet<BlockRef>, CoreError> {
781            Ok(BTreeSet::new())
782        }
783
784        async fn add_certified_commits(
785            &self,
786            _commits: CertifiedCommits,
787        ) -> Result<BTreeSet<BlockRef>, CoreError> {
788            todo!()
789        }
790
791        async fn new_block(&self, _round: Round, _force: bool) -> Result<(), CoreError> {
792            Ok(())
793        }
794
795        async fn get_missing_blocks(&self) -> Result<BTreeSet<BlockRef>, CoreError> {
796            Ok(Default::default())
797        }
798
799        fn set_propagation_delay(&self, _propagation_delay: Round) -> Result<(), CoreError> {
800            todo!()
801        }
802
803        fn set_last_known_proposed_round(&self, _round: Round) -> Result<(), CoreError> {
804            todo!()
805        }
806    }
807
808    #[derive(Default)]
809    struct FakeNetworkClient {}
810
811    #[async_trait]
812    impl ValidatorNetworkClient for FakeNetworkClient {
813        #[cfg(not(msim))]
814        async fn send_block(
815            &self,
816            _peer: AuthorityIndex,
817            _block: &VerifiedBlock,
818            _timeout: Duration,
819        ) -> ConsensusResult<()> {
820            unimplemented!("Unimplemented")
821        }
822
823        async fn subscribe_blocks(
824            &self,
825            _peer: AuthorityIndex,
826            _last_received: Round,
827            _timeout: Duration,
828        ) -> ConsensusResult<BlockStream> {
829            unimplemented!("Unimplemented")
830        }
831
832        async fn fetch_blocks(
833            &self,
834            _peer: AuthorityIndex,
835            _block_refs: Vec<BlockRef>,
836            _fetch_after_rounds: Vec<Round>,
837            _fetch_missing_ancestors: bool,
838            _timeout: Duration,
839        ) -> ConsensusResult<Vec<Bytes>> {
840            unimplemented!("Unimplemented")
841        }
842
843        async fn fetch_commits(
844            &self,
845            _peer: AuthorityIndex,
846            _commit_range: CommitRange,
847            _timeout: Duration,
848        ) -> ConsensusResult<(Vec<Bytes>, Vec<Bytes>)> {
849            unimplemented!("Unimplemented")
850        }
851
852        async fn fetch_latest_blocks(
853            &self,
854            _peer: AuthorityIndex,
855            _authorities: Vec<AuthorityIndex>,
856            _timeout: Duration,
857        ) -> ConsensusResult<Vec<Bytes>> {
858            unimplemented!("Unimplemented")
859        }
860
861        async fn get_latest_rounds(
862            &self,
863            _peer: AuthorityIndex,
864            _timeout: Duration,
865        ) -> ConsensusResult<(Vec<Round>, Vec<Round>)> {
866            unimplemented!("Unimplemented")
867        }
868    }
869
870    #[async_trait]
871    impl ObserverNetworkClient for FakeNetworkClient {
872        async fn stream_blocks(
873            &self,
874            _peer: crate::network::PeerId,
875            _highest_round_per_authority: Vec<Round>,
876            _filter: crate::network::BlockStreamFilter,
877            _timeout: Duration,
878        ) -> ConsensusResult<crate::network::ObserverBlockStream> {
879            unimplemented!("Unimplemented")
880        }
881
882        async fn fetch_blocks(
883            &self,
884            _peer: crate::network::PeerId,
885            _block_refs: Vec<BlockRef>,
886            _fetch_after_rounds: Vec<Round>,
887            _fetch_missing_ancestors: bool,
888            _timeout: Duration,
889        ) -> ConsensusResult<Vec<Bytes>> {
890            unimplemented!("Unimplemented")
891        }
892
893        async fn fetch_commits(
894            &self,
895            _peer: crate::network::PeerId,
896            _commit_range: CommitRange,
897            _timeout: Duration,
898        ) -> ConsensusResult<(Vec<Bytes>, Vec<Bytes>)> {
899            unimplemented!("Unimplemented")
900        }
901    }
902
903    #[tokio::test(flavor = "current_thread", start_paused = true)]
904    async fn test_handle_send_block() {
905        let (context, _keys) = Context::new_for_test(4);
906        let context = Arc::new(context);
907        let block_verifier = Arc::new(crate::block_verifier::NoopBlockVerifier {});
908        let commit_vote_monitor = Arc::new(CommitVoteMonitor::new(context.clone()));
909        let core_dispatcher = Arc::new(FakeCoreThreadDispatcher::new());
910        let (_tx_block_broadcast, rx_block_broadcast) = broadcast::channel(100);
911        let fake_client = Arc::new(FakeNetworkClient::default());
912        let network_client = Arc::new(SynchronizerClient::new(
913            context.clone(),
914            Some(fake_client.clone()),
915            Some(fake_client.clone()),
916        ));
917        let store = Arc::new(MemStore::new());
918        let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
919        let transaction_vote_tracker =
920            TransactionVoteTracker::new(context.clone(), block_verifier.clone(), dag_state.clone());
921        let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
922        let peers_pool = Arc::new(PeersPool::new(context.clone()));
923        let synchronizer = Synchronizer::start(
924            network_client,
925            context.clone(),
926            core_dispatcher.clone(),
927            commit_vote_monitor.clone(),
928            block_verifier.clone(),
929            transaction_vote_tracker.clone(),
930            round_tracker.clone(),
931            dag_state.clone(),
932            peers_pool.clone(),
933            false,
934        );
935        let block_sync_service = Arc::new(BlockSyncService::new(
936            context.clone(),
937            dag_state.clone(),
938            store.clone(),
939        ));
940        let authority_service = Arc::new(AuthorityService::new(
941            context.clone(),
942            block_verifier,
943            commit_vote_monitor,
944            round_tracker,
945            synchronizer,
946            core_dispatcher.clone(),
947            rx_block_broadcast,
948            transaction_vote_tracker,
949            dag_state,
950            block_sync_service,
951        ));
952
953        // Test delaying blocks with time drift.
954        let now = context.clock.timestamp_utc_ms();
955        let max_drift = context.parameters.max_forward_time_drift;
956        let input_block = VerifiedBlock::new_for_test(
957            TestBlock::new(9, 0)
958                .set_timestamp_ms(now + max_drift.as_millis() as u64)
959                .build(),
960        );
961
962        let service = authority_service.clone();
963        let serialized = ExtendedSerializedBlock {
964            block: SerializedBlockForm::Full(input_block.serialized().clone()),
965            excluded_ancestors: vec![],
966        };
967
968        tokio::spawn({
969            let service = service.clone();
970            let context = context.clone();
971            async move {
972                service
973                    .handle_send_block(context.committee.to_authority_index(0).unwrap(), serialized)
974                    .await
975                    .unwrap();
976            }
977        });
978
979        sleep(max_drift / 2).await;
980
981        let blocks = core_dispatcher.get_blocks();
982        assert_eq!(blocks.len(), 1);
983        assert_eq!(blocks[0], input_block);
984
985        // Test invalid block.
986        let invalid_block =
987            VerifiedBlock::new_for_test(TestBlock::new(10, 1000).set_timestamp_ms(10).build());
988        let extended_block = ExtendedSerializedBlock {
989            block: SerializedBlockForm::Full(invalid_block.serialized().clone()),
990            excluded_ancestors: vec![],
991        };
992        service
993            .handle_send_block(
994                context.committee.to_authority_index(0).unwrap(),
995                extended_block,
996            )
997            .await
998            .unwrap_err();
999
1000        // A slim payload that reaches the service is a bug upstream (the subscriber
1001        // drops them until the codec lands); it must be rejected, not parsed.
1002        let slim_block = ExtendedSerializedBlock {
1003            block: SerializedBlockForm::Slim(Bytes::from_static(b"slim")),
1004            excluded_ancestors: vec![],
1005        };
1006        let result = service
1007            .handle_send_block(context.committee.to_authority_index(0).unwrap(), slim_block)
1008            .await;
1009        assert!(matches!(
1010            result,
1011            Err(crate::error::ConsensusError::UnexpectedBlockForm)
1012        ));
1013
1014        // Test invalid excluded ancestors.
1015        let invalid_excluded_ancestors = vec![
1016            bcs::to_bytes(&BlockRef::new(
1017                10,
1018                AuthorityIndex::new_for_test(1000),
1019                BlockDigest::MIN,
1020            ))
1021            .unwrap(),
1022            vec![3u8; 40],
1023            bcs::to_bytes(&invalid_block.reference()).unwrap(),
1024        ];
1025        let extended_block = ExtendedSerializedBlock {
1026            block: SerializedBlockForm::Full(input_block.serialized().clone()),
1027            excluded_ancestors: invalid_excluded_ancestors,
1028        };
1029        service
1030            .handle_send_block(
1031                context.committee.to_authority_index(0).unwrap(),
1032                extended_block,
1033            )
1034            .await
1035            .unwrap_err();
1036    }
1037
1038    #[tokio::test(flavor = "current_thread", start_paused = true)]
1039    async fn test_handle_fetch_blocks() {
1040        // GIVEN
1041        // Use NUM_AUTHORITIES and NUM_ROUNDS higher than max_blocks_per_sync to test limits.
1042        const NUM_AUTHORITIES: usize = 40;
1043        const NUM_ROUNDS: usize = 40;
1044        let (mut context, _keys) = Context::new_for_test(NUM_AUTHORITIES);
1045        context.parameters.max_blocks_per_fetch = 50;
1046        let context = Arc::new(context);
1047        let block_verifier = Arc::new(crate::block_verifier::NoopBlockVerifier {});
1048        let commit_vote_monitor = Arc::new(CommitVoteMonitor::new(context.clone()));
1049        let core_dispatcher = Arc::new(FakeCoreThreadDispatcher::new());
1050        let (_tx_block_broadcast, rx_block_broadcast) = broadcast::channel(100);
1051        let fake_client = Arc::new(FakeNetworkClient::default());
1052        let network_client = Arc::new(SynchronizerClient::new(
1053            context.clone(),
1054            Some(fake_client.clone()),
1055            Some(fake_client.clone()),
1056        ));
1057        let store = Arc::new(MemStore::new());
1058        let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
1059        let transaction_vote_tracker =
1060            TransactionVoteTracker::new(context.clone(), block_verifier.clone(), dag_state.clone());
1061        let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
1062        let peers_pool = Arc::new(PeersPool::new(context.clone()));
1063        let synchronizer = Synchronizer::start(
1064            network_client,
1065            context.clone(),
1066            core_dispatcher.clone(),
1067            commit_vote_monitor.clone(),
1068            block_verifier.clone(),
1069            transaction_vote_tracker.clone(),
1070            round_tracker.clone(),
1071            dag_state.clone(),
1072            peers_pool.clone(),
1073            false,
1074        );
1075        let block_sync_service = Arc::new(BlockSyncService::new(
1076            context.clone(),
1077            dag_state.clone(),
1078            store.clone(),
1079        ));
1080        let authority_service = Arc::new(AuthorityService::new(
1081            context.clone(),
1082            block_verifier,
1083            commit_vote_monitor,
1084            round_tracker,
1085            synchronizer,
1086            core_dispatcher.clone(),
1087            rx_block_broadcast,
1088            transaction_vote_tracker,
1089            dag_state.clone(),
1090            block_sync_service,
1091        ));
1092
1093        // GIVEN: 40 rounds of blocks in the dag state.
1094        let mut dag_builder = DagBuilder::new(context.clone());
1095        dag_builder
1096            .layers(1..=(NUM_ROUNDS as u32))
1097            .build()
1098            .persist_layers(dag_state.clone());
1099        dag_state.write().flush();
1100        let all_blocks = dag_builder.all_blocks();
1101
1102        // WHEN: Request 2 blocks from round 40, fetch missing ancestors enabled.
1103        let missing_block_refs: Vec<BlockRef> = all_blocks
1104            .iter()
1105            .rev()
1106            .take(2)
1107            .map(|b| b.reference())
1108            .collect();
1109        let highest_accepted_rounds: Vec<Round> = vec![1; NUM_AUTHORITIES];
1110        let results = authority_service
1111            .handle_fetch_blocks(
1112                AuthorityIndex::new_for_test(0),
1113                missing_block_refs.clone(),
1114                highest_accepted_rounds,
1115                true,
1116            )
1117            .await
1118            .unwrap();
1119
1120        // THEN: the expected number of unique blocks are returned.
1121        let blocks: BTreeMap<BlockRef, VerifiedBlock> = results
1122            .iter()
1123            .map(|b| {
1124                let signed = bcs::from_bytes(b).unwrap();
1125                let block = VerifiedBlock::new_verified(signed, b.clone());
1126                (block.reference(), block)
1127            })
1128            .collect();
1129        assert_eq!(blocks.len(), context.parameters.max_blocks_per_sync);
1130        // All missing blocks are returned.
1131        for b in &missing_block_refs {
1132            assert!(blocks.contains_key(b));
1133        }
1134        let num_missing_ancestors = blocks
1135            .keys()
1136            .filter(|b| b.round == NUM_ROUNDS as Round - 1)
1137            .count();
1138        assert_eq!(
1139            num_missing_ancestors,
1140            context.parameters.max_blocks_per_sync - missing_block_refs.len()
1141        );
1142
1143        // WHEN: Request 2 blocks from round 37, fetch missing ancestors disabled.
1144        let missing_round = NUM_ROUNDS as Round - 3;
1145        let missing_block_refs: Vec<BlockRef> = all_blocks
1146            .iter()
1147            .filter(|b| b.reference().round == missing_round)
1148            .map(|b| b.reference())
1149            .take(2)
1150            .collect();
1151        let mut highest_accepted_rounds: Vec<Round> = vec![1; NUM_AUTHORITIES];
1152        // Try to fill up the blocks from the 1st authority in missing_block_refs.
1153        highest_accepted_rounds[missing_block_refs[0].author] = missing_round - 5;
1154        let results = authority_service
1155            .handle_fetch_blocks(
1156                AuthorityIndex::new_for_test(0),
1157                missing_block_refs.clone(),
1158                highest_accepted_rounds,
1159                false,
1160            )
1161            .await
1162            .unwrap();
1163
1164        // THEN: the expected number of unique blocks are returned.
1165        let blocks: BTreeMap<BlockRef, VerifiedBlock> = results
1166            .iter()
1167            .map(|b| {
1168                let signed = bcs::from_bytes(b).unwrap();
1169                let block = VerifiedBlock::new_verified(signed, b.clone());
1170                (block.reference(), block)
1171            })
1172            .collect();
1173        assert_eq!(blocks.len(), context.parameters.max_blocks_per_sync);
1174        // All missing blocks are returned.
1175        for b in &missing_block_refs {
1176            assert!(blocks.contains_key(b));
1177        }
1178        // Ancestor blocks are from the expected rounds and authorities.
1179        let expected_authors = [missing_block_refs[0].author, missing_block_refs[1].author];
1180        for b in blocks.keys() {
1181            assert!(b.round <= missing_round);
1182            assert!(expected_authors.contains(&b.author));
1183        }
1184
1185        // WHEN: Request with empty block_refs, fetch missing ancestors disabled.
1186        let mut highest_accepted_rounds: Vec<Round> = vec![1; NUM_AUTHORITIES];
1187        // Set a few authorities to higher accepted rounds.
1188        highest_accepted_rounds[0] = (NUM_ROUNDS as Round) - 5;
1189        highest_accepted_rounds[1] = (NUM_ROUNDS as Round) - 3;
1190        let results = authority_service
1191            .handle_fetch_blocks(
1192                AuthorityIndex::new_for_test(0),
1193                vec![],
1194                highest_accepted_rounds.clone(),
1195                false,
1196            )
1197            .await
1198            .unwrap();
1199
1200        // THEN: the expected number of unique blocks are returned.
1201        let blocks: BTreeMap<BlockRef, VerifiedBlock> = results
1202            .iter()
1203            .map(|b| {
1204                let signed = bcs::from_bytes(b).unwrap();
1205                let block = VerifiedBlock::new_verified(signed, b.clone());
1206                (block.reference(), block)
1207            })
1208            .collect();
1209        assert_eq!(blocks.len(), context.parameters.max_blocks_per_fetch);
1210        // Blocks should be from all authorities, within the expected round range.
1211        for block_ref in blocks.keys() {
1212            let accepted = highest_accepted_rounds[block_ref.author];
1213            assert!(block_ref.round > accepted);
1214        }
1215        // Blocks should be fetched in ascending round order across authorities,
1216        // so blocks should have low rounds near the accepted rounds.
1217        let max_round_in_result = blocks.keys().map(|b| b.round).max().unwrap();
1218        // With 40 authorities mostly at accepted round 1 and max_blocks_per_fetch=50,
1219        // the min-heap fills ~1-2 rounds per authority.
1220        assert!(
1221            max_round_in_result <= 4,
1222            "Expected low rounds from fair round-order fetching, got max round {}",
1223            max_round_in_result
1224        );
1225
1226        // WHEN: Request 5 block from round 40, not getting ancestors.
1227        let missing_block_refs: Vec<BlockRef> = all_blocks
1228            .iter()
1229            .filter(|b| b.reference().round == NUM_ROUNDS as Round - 10)
1230            .map(|b| b.reference())
1231            .take(5)
1232            .collect();
1233        let results = authority_service
1234            .handle_fetch_blocks(
1235                AuthorityIndex::new_for_test(0),
1236                missing_block_refs.clone(),
1237                vec![],
1238                false,
1239            )
1240            .await
1241            .unwrap();
1242
1243        // THEN: the expected number of unique blocks are returned.
1244        let blocks: BTreeMap<BlockRef, VerifiedBlock> = results
1245            .iter()
1246            .map(|b| {
1247                let signed = bcs::from_bytes(b).unwrap();
1248                let block = VerifiedBlock::new_verified(signed, b.clone());
1249                (block.reference(), block)
1250            })
1251            .collect();
1252        assert_eq!(blocks.len(), 5);
1253        for b in &missing_block_refs {
1254            assert!(blocks.contains_key(b));
1255        }
1256    }
1257
1258    #[tokio::test(flavor = "current_thread", start_paused = true)]
1259    async fn test_handle_fetch_latest_blocks() {
1260        // GIVEN
1261        let (context, _keys) = Context::new_for_test(4);
1262        let context = Arc::new(context);
1263        let block_verifier = Arc::new(crate::block_verifier::NoopBlockVerifier {});
1264        let commit_vote_monitor = Arc::new(CommitVoteMonitor::new(context.clone()));
1265        let core_dispatcher = Arc::new(FakeCoreThreadDispatcher::new());
1266        let (_tx_block_broadcast, rx_block_broadcast) = broadcast::channel(100);
1267        let fake_client = Arc::new(FakeNetworkClient::default());
1268        let network_client = Arc::new(SynchronizerClient::new(
1269            context.clone(),
1270            Some(fake_client.clone()),
1271            Some(fake_client.clone()),
1272        ));
1273        let store = Arc::new(MemStore::new());
1274        let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
1275        let transaction_vote_tracker =
1276            TransactionVoteTracker::new(context.clone(), block_verifier.clone(), dag_state.clone());
1277        let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
1278        let peers_pool = Arc::new(PeersPool::new(context.clone()));
1279        let synchronizer = Synchronizer::start(
1280            network_client,
1281            context.clone(),
1282            core_dispatcher.clone(),
1283            commit_vote_monitor.clone(),
1284            block_verifier.clone(),
1285            transaction_vote_tracker.clone(),
1286            round_tracker.clone(),
1287            dag_state.clone(),
1288            peers_pool.clone(),
1289            true,
1290        );
1291        let block_sync_service = Arc::new(BlockSyncService::new(
1292            context.clone(),
1293            dag_state.clone(),
1294            store.clone(),
1295        ));
1296        let authority_service = Arc::new(AuthorityService::new(
1297            context.clone(),
1298            block_verifier,
1299            commit_vote_monitor,
1300            round_tracker,
1301            synchronizer,
1302            core_dispatcher.clone(),
1303            rx_block_broadcast,
1304            transaction_vote_tracker,
1305            dag_state.clone(),
1306            block_sync_service,
1307        ));
1308
1309        // Create some blocks for a few authorities. Create some equivocations as well and store in dag state.
1310        let mut dag_builder = DagBuilder::new(context.clone());
1311        dag_builder
1312            .layers(1..=10)
1313            .authorities(vec![AuthorityIndex::new_for_test(2)])
1314            .equivocate(1)
1315            .build()
1316            .persist_layers(dag_state);
1317
1318        // WHEN
1319        let authorities_to_request = vec![
1320            AuthorityIndex::new_for_test(1),
1321            AuthorityIndex::new_for_test(2),
1322        ];
1323        let results = authority_service
1324            .handle_fetch_latest_blocks(AuthorityIndex::new_for_test(1), authorities_to_request)
1325            .await;
1326
1327        // THEN
1328        let serialised_blocks = results.unwrap();
1329        for serialised_block in serialised_blocks {
1330            let signed_block: SignedBlock =
1331                bcs::from_bytes(&serialised_block).expect("Error while deserialising block");
1332            let verified_block = VerifiedBlock::new_verified(signed_block, serialised_block);
1333
1334            assert_eq!(verified_block.round(), 10);
1335        }
1336    }
1337
1338    #[tokio::test(flavor = "current_thread", start_paused = true)]
1339    async fn test_handle_subscribe_blocks() {
1340        let (context, _keys) = Context::new_for_test(4);
1341        let context = Arc::new(context);
1342        let block_verifier = Arc::new(crate::block_verifier::NoopBlockVerifier {});
1343        let commit_vote_monitor = Arc::new(CommitVoteMonitor::new(context.clone()));
1344        let core_dispatcher = Arc::new(FakeCoreThreadDispatcher::new());
1345        let (_tx_block_broadcast, rx_block_broadcast) = broadcast::channel(100);
1346        let fake_client = Arc::new(FakeNetworkClient::default());
1347        let network_client = Arc::new(SynchronizerClient::new(
1348            context.clone(),
1349            Some(fake_client.clone()),
1350            Some(fake_client.clone()),
1351        ));
1352        let store = Arc::new(MemStore::new());
1353        let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
1354        let transaction_vote_tracker =
1355            TransactionVoteTracker::new(context.clone(), block_verifier.clone(), dag_state.clone());
1356        let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
1357        let peers_pool = Arc::new(PeersPool::new(context.clone()));
1358        let synchronizer = Synchronizer::start(
1359            network_client,
1360            context.clone(),
1361            core_dispatcher.clone(),
1362            commit_vote_monitor.clone(),
1363            block_verifier.clone(),
1364            transaction_vote_tracker.clone(),
1365            round_tracker.clone(),
1366            dag_state.clone(),
1367            peers_pool.clone(),
1368            false,
1369        );
1370        let block_sync_service = Arc::new(BlockSyncService::new(
1371            context.clone(),
1372            dag_state.clone(),
1373            store.clone(),
1374        ));
1375
1376        // Create 3 proposed blocks at rounds 5, 10, 15 for own authority (index 0)
1377        dag_state
1378            .write()
1379            .accept_block(VerifiedBlock::new_for_test(TestBlock::new(5, 0).build()));
1380        dag_state
1381            .write()
1382            .accept_block(VerifiedBlock::new_for_test(TestBlock::new(10, 0).build()));
1383        dag_state
1384            .write()
1385            .accept_block(VerifiedBlock::new_for_test(TestBlock::new(15, 0).build()));
1386
1387        let authority_service = Arc::new(AuthorityService::new(
1388            context.clone(),
1389            block_verifier,
1390            commit_vote_monitor,
1391            round_tracker,
1392            synchronizer,
1393            core_dispatcher.clone(),
1394            rx_block_broadcast,
1395            transaction_vote_tracker,
1396            dag_state.clone(),
1397            block_sync_service,
1398        ));
1399
1400        let peer = context.committee.to_authority_index(1).unwrap();
1401
1402        // Case A: Subscribe with last_received = 100 (after all proposed blocks)
1403        // Should return last proposed block (round 15) as fallback
1404        {
1405            let mut stream = authority_service
1406                .handle_subscribe_blocks(peer, 100)
1407                .await
1408                .unwrap();
1409            let block: SignedBlock =
1410                bcs::from_bytes(expect_full(&stream.next().await.unwrap().block)).unwrap();
1411            assert_eq!(
1412                block.round(),
1413                15,
1414                "Should return last proposed block as fallback"
1415            );
1416            assert_eq!(block.author().value(), 0);
1417        }
1418
1419        // Case B: Subscribe with last_received = 7 (includes rounds 10, 15)
1420        // Should return cached blocks from round 8+
1421        {
1422            let mut stream = authority_service
1423                .handle_subscribe_blocks(peer, 7)
1424                .await
1425                .unwrap();
1426
1427            let block1: SignedBlock =
1428                bcs::from_bytes(expect_full(&stream.next().await.unwrap().block)).unwrap();
1429            assert_eq!(block1.round(), 10, "Should return block at round 10");
1430
1431            let block2: SignedBlock =
1432                bcs::from_bytes(expect_full(&stream.next().await.unwrap().block)).unwrap();
1433            assert_eq!(block2.round(), 15, "Should return block at round 15");
1434        }
1435    }
1436
1437    #[tokio::test(flavor = "current_thread", start_paused = true)]
1438    async fn test_handle_subscribe_blocks_not_proposed() {
1439        let (context, _keys) = Context::new_for_test(4);
1440        let context = Arc::new(context);
1441        let block_verifier = Arc::new(crate::block_verifier::NoopBlockVerifier {});
1442        let commit_vote_monitor = Arc::new(CommitVoteMonitor::new(context.clone()));
1443        let core_dispatcher = Arc::new(FakeCoreThreadDispatcher::new());
1444        let (_tx_block_broadcast, rx_block_broadcast) = broadcast::channel(100);
1445        let fake_client = Arc::new(FakeNetworkClient::default());
1446        let network_client = Arc::new(SynchronizerClient::new(
1447            context.clone(),
1448            Some(fake_client.clone()),
1449            Some(fake_client.clone()),
1450        ));
1451        let store = Arc::new(MemStore::new());
1452        let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
1453        let transaction_vote_tracker =
1454            TransactionVoteTracker::new(context.clone(), block_verifier.clone(), dag_state.clone());
1455        let round_tracker = Arc::new(RwLock::new(RoundTracker::new(context.clone(), vec![])));
1456        let peers_pool = Arc::new(PeersPool::new(context.clone()));
1457        let synchronizer = Synchronizer::start(
1458            network_client,
1459            context.clone(),
1460            core_dispatcher.clone(),
1461            commit_vote_monitor.clone(),
1462            block_verifier.clone(),
1463            transaction_vote_tracker.clone(),
1464            round_tracker.clone(),
1465            dag_state.clone(),
1466            peers_pool.clone(),
1467            false,
1468        );
1469        let block_sync_service = Arc::new(BlockSyncService::new(
1470            context.clone(),
1471            dag_state.clone(),
1472            store.clone(),
1473        ));
1474
1475        // No blocks added to DagState - only genesis exists
1476
1477        let authority_service = Arc::new(AuthorityService::new(
1478            context.clone(),
1479            block_verifier,
1480            commit_vote_monitor,
1481            round_tracker,
1482            synchronizer,
1483            core_dispatcher.clone(),
1484            rx_block_broadcast,
1485            transaction_vote_tracker,
1486            dag_state.clone(),
1487            block_sync_service,
1488        ));
1489
1490        let peer = context.committee.to_authority_index(1).unwrap();
1491
1492        // Subscribe - no blocks have been proposed yet (only genesis exists)
1493        let mut stream = authority_service
1494            .handle_subscribe_blocks(peer, 0)
1495            .await
1496            .unwrap();
1497
1498        // Should NOT receive any block (genesis must not be returned)
1499        use futures::poll;
1500        use std::task::Poll;
1501        let poll_result = poll!(stream.next());
1502        assert!(
1503            matches!(poll_result, Poll::Pending),
1504            "Should not receive genesis block on subscription stream"
1505        );
1506    }
1507}