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