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