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