Skip to main content

consensus_core/
authority_node.rs

1// Copyright (c) Mysten Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4use std::{
5    sync::{Arc, Weak},
6    time::{Duration, Instant},
7};
8
9use consensus_config::{
10    Committee, ConsensusProtocolConfig, NetworkKeyPair, NetworkPublicKey, Parameters,
11    ProtocolKeyPair,
12};
13use consensus_types::block::Round;
14use itertools::Itertools;
15use mysten_common::debug_fatal;
16use mysten_network::Multiaddr;
17use parking_lot::RwLock;
18use prometheus::Registry;
19use tracing::{info, warn};
20
21use crate::{
22    BlockAPI as _, CommitConsumerArgs, RandomnessSignatureHandler,
23    authority_service::AuthorityService,
24    block_manager::BlockManager,
25    block_sync_service::BlockSyncService,
26    block_verifier::SignedBlockVerifier,
27    commit_observer::CommitObserver,
28    commit_syncer::{CommitSyncer, CommitSyncerHandle},
29    commit_vote_monitor::CommitVoteMonitor,
30    context::{Clock, Context},
31    core::{Core, CoreSignals},
32    core_thread::{ChannelCoreThreadDispatcher, CoreThreadHandle},
33    dag_state::DagState,
34    leader_schedule::LeaderSchedule,
35    leader_timeout::{LeaderTimeoutTask, LeaderTimeoutTaskHandle},
36    metrics::initialise_metrics,
37    network::{
38        CommitSyncerClient, NetworkManager, PeerId, SynchronizerClient, tonic_network::TonicManager,
39    },
40    observer_service::ObserverService,
41    observer_subscriber::ObserverSubscriber,
42    peers_pool::PeersPool,
43    round_prober::{RoundProber, RoundProberHandle},
44    round_tracker::RoundTracker,
45    storage::rocksdb_store::RocksDBStore,
46    subscriber::Subscriber,
47    synchronizer::{Synchronizer, SynchronizerHandle},
48    transaction::{
49        TransactionClient, TransactionConsumer, TransactionConsumerPool, TransactionPool,
50        TransactionVerifier,
51    },
52    transaction_vote_tracker::TransactionVoteTracker,
53};
54
55/// ConsensusAuthority is used by Sui to manage the lifetime of AuthorityNode.
56/// It hides the details of the implementation from the caller, MysticetiManager.
57#[allow(private_interfaces)]
58pub enum ConsensusAuthority {
59    WithTonic(AuthorityNode<TonicManager>),
60}
61
62impl ConsensusAuthority {
63    pub async fn start(
64        network_type: NetworkType,
65        epoch_start_timestamp_ms: u64,
66        committee: Committee,
67        parameters: Parameters,
68        protocol_config: ConsensusProtocolConfig,
69        // Only required for validator nodes. Observer nodes don't have a protocol keypair.
70        protocol_keypair: Option<ProtocolKeyPair>,
71        network_keypair: NetworkKeyPair,
72        clock: Arc<Clock>,
73        transaction_verifier: Arc<dyn TransactionVerifier>,
74        // When provided, the proposer takes transactions from this pool and the
75        // `TransactionClient` submission path is unused. Only relevant for validator nodes.
76        transaction_pool: Option<Arc<dyn TransactionPool>>,
77        commit_consumer: CommitConsumerArgs,
78        registry: Registry,
79        // A counter that keeps track of how many times the consensus authority has been booted while the process
80        // has been running. It's useful for making decisions on whether amnesia recovery should run.
81        // When `boot_counter` is 0, `ConsensusAuthority` will initiate the process of amnesia recovery if that's enabled in the parameters.
82        boot_counter: u64,
83        randomness_signature_handler: Option<Arc<dyn RandomnessSignatureHandler>>,
84    ) -> Self {
85        match network_type {
86            NetworkType::Tonic => {
87                let authority = AuthorityNode::start(
88                    epoch_start_timestamp_ms,
89                    committee,
90                    parameters,
91                    protocol_config,
92                    protocol_keypair,
93                    network_keypair,
94                    clock,
95                    transaction_verifier,
96                    transaction_pool,
97                    commit_consumer,
98                    registry,
99                    boot_counter,
100                    randomness_signature_handler,
101                )
102                .await;
103                Self::WithTonic(authority)
104            }
105        }
106    }
107
108    pub async fn stop(self) {
109        match self {
110            Self::WithTonic(authority) => authority.stop().await,
111        }
112    }
113
114    pub fn update_peer_address(
115        &self,
116        network_pubkey: NetworkPublicKey,
117        address: Option<Multiaddr>,
118    ) {
119        match self {
120            Self::WithTonic(authority) => authority.update_peer_address(network_pubkey, address),
121        }
122    }
123
124    pub fn transaction_client(&self) -> Arc<TransactionClient> {
125        match self {
126            Self::WithTonic(authority) => authority.transaction_client(),
127        }
128    }
129
130    pub fn store(&self) -> Arc<RocksDBStore> {
131        match self {
132            Self::WithTonic(authority) => authority.store(),
133        }
134    }
135
136    #[cfg(test)]
137    fn context(&self) -> &Arc<Context> {
138        match self {
139            Self::WithTonic(authority) => &authority.context,
140        }
141    }
142}
143
144#[derive(Clone, Copy, PartialEq, Eq, Debug)]
145pub enum NetworkType {
146    Tonic,
147}
148
149/// Enum to handle different subscriber types based on whether the node is a validator or observer
150enum SubscriberType<N: NetworkManager> {
151    Validator(Subscriber<N::ValidatorClient, AuthorityService<ChannelCoreThreadDispatcher>>),
152    Observer(ObserverSubscriber<N::ObserverClient, ObserverService>),
153}
154
155impl<N: NetworkManager> SubscriberType<N> {
156    async fn stop(&self) {
157        match self {
158            SubscriberType::Validator(subscriber) => subscriber.stop().await,
159            SubscriberType::Observer(subscriber) => subscriber.stop().await,
160        }
161    }
162}
163
164pub(crate) struct AuthorityNode<N>
165where
166    N: NetworkManager,
167{
168    context: Arc<Context>,
169    start_time: Instant,
170    transaction_client: Arc<TransactionClient>,
171    synchronizer: Arc<SynchronizerHandle>,
172    store: Arc<RocksDBStore>,
173    // Only use for verification and logging during shutdown.
174    // To avoid keeping the DagState alive at the end of shutdown, this is only a weak reference.
175    dag_state: Weak<RwLock<DagState>>,
176
177    commit_syncer_handle: CommitSyncerHandle,
178    round_prober_handle: Option<RoundProberHandle>,
179    leader_timeout_handle: LeaderTimeoutTaskHandle,
180    core_thread_handle: CoreThreadHandle,
181    subscriber: SubscriberType<N>,
182    // Network proxies hold ObserverService weakly, so AuthorityNode keeps the service alive until
183    // the network servers have stopped.
184    observer_service: Option<Arc<ObserverService>>,
185    network_manager: N,
186}
187
188impl<N> AuthorityNode<N>
189where
190    N: NetworkManager,
191{
192    // See comments above ConsensusAuthority::start() for details on the input.
193    pub(crate) async fn start(
194        epoch_start_timestamp_ms: u64,
195        committee: Committee,
196        parameters: Parameters,
197        protocol_config: ConsensusProtocolConfig,
198        protocol_keypair: Option<ProtocolKeyPair>,
199        network_keypair: NetworkKeyPair,
200        clock: Arc<Clock>,
201        transaction_verifier: Arc<dyn TransactionVerifier>,
202        transaction_pool: Option<Arc<dyn TransactionPool>>,
203        commit_consumer: CommitConsumerArgs,
204        registry: Registry,
205        boot_counter: u64,
206        randomness_signature_handler: Option<Arc<dyn RandomnessSignatureHandler>>,
207    ) -> Self {
208        let metrics = initialise_metrics(registry);
209
210        // If a protocol key pair is provided, then this is a validator node.
211        let own_index = if let Some(protocol_keypair) = &protocol_keypair {
212            let (own_index, _) = committee
213                .authorities()
214                .find(|(_, a)| a.protocol_key == protocol_keypair.public())
215                .expect("Own authority should be among the consensus authorities!");
216
217            let own_hostname = committee.authority(own_index).hostname.clone();
218            info!(
219                "Starting consensus validator authority {} {}, {:?}, epoch start timestamp {}, boot counter {}, replaying after commit index {}, consumer last processed commit index {}",
220                own_index,
221                own_hostname,
222                protocol_config.protocol_version(),
223                epoch_start_timestamp_ms,
224                boot_counter,
225                commit_consumer.replay_after_commit_index,
226                commit_consumer.consumer_last_processed_commit_index
227            );
228
229            metrics
230                .node_metrics
231                .authority_index
232                .with_label_values(&[&own_hostname])
233                .set(own_index.value() as i64);
234            Some(own_index)
235        } else {
236            // Otherwise this is an observer node and no index exists for it.
237            info!(
238                "Starting consensus observer authority, {:?}, epoch start timestamp {}, boot counter {}, replaying after commit index {}, consumer last processed commit index {}",
239                protocol_config.protocol_version(),
240                epoch_start_timestamp_ms,
241                boot_counter,
242                commit_consumer.replay_after_commit_index,
243                commit_consumer.consumer_last_processed_commit_index
244            );
245            None
246        };
247
248        info!(
249            "Consensus authorities: {}",
250            committee
251                .authorities()
252                .map(|(i, a)| format!("{}: {}", i, a.hostname))
253                .join(", ")
254        );
255        info!("Consensus parameters: {:?}", parameters);
256        info!("Consensus committee: {:?}", committee);
257        let context = Arc::new(Context::new(
258            epoch_start_timestamp_ms,
259            own_index,
260            committee,
261            parameters,
262            protocol_config,
263            metrics,
264            clock,
265        ));
266        let start_time = Instant::now();
267
268        context
269            .metrics
270            .node_metrics
271            .protocol_version
272            .set(context.protocol_config.protocol_version() as i64);
273
274        let (tx_client, tx_receiver, priority_tx_receiver) =
275            TransactionClient::new(context.clone());
276        let transaction_pool: Arc<dyn TransactionPool> = match transaction_pool {
277            // With an external pool, the TransactionClient path is unused. The channel
278            // receiver is dropped so accidental client submissions fail fast instead of
279            // hanging.
280            Some(pool) => {
281                drop(tx_receiver);
282                drop(priority_tx_receiver);
283                pool
284            }
285            None => Arc::new(TransactionConsumerPool::new(TransactionConsumer::new(
286                tx_receiver,
287                priority_tx_receiver,
288                context.clone(),
289            ))),
290        };
291
292        let (core_signals, signals_receivers) = CoreSignals::new(context.clone());
293
294        let mut network_manager = N::new(context.clone(), network_keypair);
295        let validator_client = network_manager.validator_client();
296        let observer_client = network_manager.observer_client();
297
298        let synchronizer_client = Arc::new(SynchronizerClient::<
299            N::ValidatorClient,
300            N::ObserverClient,
301        >::new(
302            context.clone(),
303            Some(validator_client.clone()),
304            Some(observer_client.clone()),
305        ));
306        let commit_syncer_client = Arc::new(CommitSyncerClient::<
307            N::ValidatorClient,
308            N::ObserverClient,
309        >::new(
310            context.clone(),
311            Some(validator_client.clone()),
312            Some(observer_client.clone()),
313        ));
314
315        let store_path = context.parameters.db_path.as_path().to_str().unwrap();
316        let store = Arc::new(RocksDBStore::new(store_path));
317        let dag_state = Arc::new(RwLock::new(DagState::new(context.clone(), store.clone())));
318
319        let block_verifier = Arc::new(SignedBlockVerifier::new(
320            context.clone(),
321            transaction_verifier,
322        ));
323
324        let transaction_vote_tracker =
325            TransactionVoteTracker::new(context.clone(), block_verifier.clone(), dag_state.clone());
326
327        // Only sync last known own block if we are a validator and it's the first boot.
328        let sync_last_known_own_block = boot_counter == 0
329            && !context
330                .parameters
331                .sync_last_known_own_block_timeout
332                .is_zero()
333            && context.is_validator();
334        info!(
335            "Sync last known own block: {}. Boot count: {}. Timeout: {:?}.",
336            sync_last_known_own_block,
337            boot_counter,
338            context.parameters.sync_last_known_own_block_timeout
339        );
340
341        let block_manager = BlockManager::new(context.clone(), dag_state.clone());
342
343        let leader_schedule = Arc::new(LeaderSchedule::from_store(
344            context.clone(),
345            dag_state.clone(),
346        ));
347
348        let commit_consumer_monitor = commit_consumer.monitor();
349        let commit_observer = CommitObserver::new(
350            context.clone(),
351            commit_consumer,
352            dag_state.clone(),
353            transaction_vote_tracker.clone(),
354        )
355        .await;
356
357        let initial_received_rounds = dag_state
358            .read()
359            .get_last_cached_block_per_authority(Round::MAX)
360            .into_iter()
361            .map(|(block, _)| block.round())
362            .collect::<Vec<_>>();
363        let round_tracker = Arc::new(RwLock::new(RoundTracker::new(
364            context.clone(),
365            initial_received_rounds,
366        )));
367
368        // To avoid accidentally leaking the private key, the protocol key pair should only be
369        // kept in Core.
370        let core = if context.is_validator() {
371            Core::new_validator(
372                context.clone(),
373                leader_schedule,
374                transaction_pool,
375                transaction_vote_tracker.clone(),
376                block_manager,
377                commit_observer,
378                core_signals,
379                protocol_keypair.expect("protocol keypair is required when running as validator"),
380                dag_state.clone(),
381                sync_last_known_own_block,
382                round_tracker.clone(),
383            )
384        } else {
385            Core::new_observer(
386                context.clone(),
387                leader_schedule,
388                block_manager,
389                commit_observer,
390                core_signals,
391                dag_state.clone(),
392            )
393        };
394
395        let (core_dispatcher, core_thread_handle) =
396            ChannelCoreThreadDispatcher::start(context.clone(), &dag_state, core);
397        let core_dispatcher = Arc::new(core_dispatcher);
398        let leader_timeout_handle =
399            LeaderTimeoutTask::start(core_dispatcher.clone(), &signals_receivers, context.clone());
400
401        let commit_vote_monitor = Arc::new(CommitVoteMonitor::new(context.clone()));
402
403        // Create the PeersPool
404        let peers_pool = Arc::new(PeersPool::new(context.clone()));
405
406        let synchronizer = Synchronizer::start(
407            synchronizer_client.clone(),
408            context.clone(),
409            core_dispatcher.clone(),
410            commit_vote_monitor.clone(),
411            block_verifier.clone(),
412            transaction_vote_tracker.clone(),
413            round_tracker.clone(),
414            dag_state.clone(),
415            peers_pool.clone(),
416            sync_last_known_own_block,
417        );
418
419        let commit_syncer_handle = CommitSyncer::new(
420            context.clone(),
421            core_dispatcher.clone(),
422            commit_vote_monitor.clone(),
423            commit_consumer_monitor.clone(),
424            block_verifier.clone(),
425            transaction_vote_tracker.clone(),
426            round_tracker.clone(),
427            commit_syncer_client.clone(),
428            dag_state.clone(),
429            peers_pool.clone(),
430        )
431        .start();
432
433        // Create BlockSyncService that will be shared by both AuthorityService and ObserverService
434        let block_sync_service = Arc::new(BlockSyncService::new(
435            context.clone(),
436            dag_state.clone(),
437            store.clone(),
438        ));
439
440        let (subscriber, round_prober_handle, observer_service) = if context.is_validator() {
441            let authority_service = Arc::new(AuthorityService::new(
442                context.clone(),
443                block_verifier.clone(),
444                commit_vote_monitor.clone(),
445                round_tracker.clone(),
446                synchronizer.clone(),
447                core_dispatcher.clone(),
448                signals_receivers.block_broadcast_receiver(),
449                transaction_vote_tracker.clone(),
450                dag_state.clone(),
451                block_sync_service.clone(),
452            ));
453
454            // Start the validator server if this is a validator node.
455            network_manager
456                .start_validator_server(authority_service.clone())
457                .await;
458
459            // Validator node: subscribe to all other validators
460            let s = Subscriber::new(
461                context.clone(),
462                validator_client.clone(),
463                authority_service.clone(),
464                dag_state.clone(),
465            );
466            for (peer, _) in context.committee.authorities() {
467                if peer != context.own_index {
468                    s.subscribe(peer);
469                }
470            }
471
472            // Start the round prober
473            let round_prober_handle = Some(
474                RoundProber::new(
475                    context.clone(),
476                    core_dispatcher.clone(),
477                    round_tracker.clone(),
478                    dag_state.clone(),
479                    validator_client,
480                )
481                .start(),
482            );
483
484            // Start the observer server if the observer server is enabled in the parameters.
485            let observer_service = if context.parameters.observer.is_server_enabled() {
486                let observer_service = Arc::new(ObserverService::new(
487                    context.clone(),
488                    core_dispatcher.clone(),
489                    dag_state.clone(),
490                    signals_receivers.accepted_block_broadcast_receiver(),
491                    block_verifier,
492                    commit_vote_monitor.clone(),
493                    transaction_vote_tracker.clone(),
494                    synchronizer.clone(),
495                    block_sync_service.clone(),
496                    randomness_signature_handler.clone(),
497                ));
498                network_manager
499                    .start_observer_server(observer_service.clone())
500                    .await;
501                Some(observer_service)
502            } else {
503                None
504            };
505
506            (
507                SubscriberType::Validator(s),
508                round_prober_handle,
509                observer_service,
510            )
511        } else {
512            // Observer node: subscribe to specified peer(s) using ObserverSubscriber
513            let observer_client = network_manager.observer_client();
514            let observer_service = Arc::new(ObserverService::new(
515                context.clone(),
516                core_dispatcher.clone(),
517                dag_state.clone(),
518                signals_receivers.accepted_block_broadcast_receiver(),
519                block_verifier,
520                commit_vote_monitor.clone(),
521                transaction_vote_tracker.clone(),
522                synchronizer.clone(),
523                block_sync_service.clone(),
524                randomness_signature_handler.clone(),
525            ));
526
527            let observer_subscriber = ObserverSubscriber::new(
528                context.clone(),
529                observer_client,
530                observer_service.clone(),
531                commit_vote_monitor.clone(),
532                dag_state.clone(),
533                randomness_signature_handler,
534            );
535
536            network_manager
537                .start_observer_server(observer_service.clone())
538                .await;
539
540            // Subscribe to peers specified in the configuration
541            // For now get the first peer from the list to connect to.
542            // TODO: support multiple peers - as in choose/detect which one to connect to.
543            for peer_record in context.parameters.observer.peers.iter().take(1) {
544                let peer_id = if let Some((index, _)) = context
545                    .committee
546                    .authorities()
547                    .find(|(_, authority)| authority.network_key == peer_record.public_key)
548                {
549                    PeerId::Validator(index)
550                } else {
551                    PeerId::Observer(Box::new(peer_record.public_key.clone()))
552                };
553
554                info!("Observer subscribing to peer: {:?}", peer_id);
555                observer_subscriber.subscribe(peer_id);
556            }
557
558            (
559                SubscriberType::Observer(observer_subscriber),
560                None,
561                Some(observer_service),
562            )
563        };
564
565        info!(
566            "Consensus authority started, took {:?}",
567            start_time.elapsed()
568        );
569
570        Self {
571            context,
572            start_time,
573            transaction_client: Arc::new(tx_client),
574            synchronizer,
575            store,
576            dag_state: Arc::downgrade(&dag_state),
577            commit_syncer_handle,
578            round_prober_handle,
579            leader_timeout_handle,
580            core_thread_handle,
581            subscriber,
582            observer_service,
583            network_manager,
584        }
585    }
586
587    pub(crate) async fn stop(self) {
588        let Self {
589            context,
590            start_time,
591            transaction_client,
592            synchronizer,
593            store,
594            dag_state,
595            commit_syncer_handle,
596            round_prober_handle,
597            leader_timeout_handle,
598            core_thread_handle,
599            subscriber,
600            observer_service,
601            mut network_manager,
602        } = self;
603
604        info!(
605            "Stopping authority. Total run time: {:?}",
606            start_time.elapsed()
607        );
608
609        // First shutdown components calling into Core.
610        synchronizer.stop().await;
611        commit_syncer_handle.stop().await;
612        if let Some(round_prober_handle) = round_prober_handle {
613            round_prober_handle.stop().await;
614        }
615        leader_timeout_handle.stop().await;
616        // Shutdown Core to stop block productions and broadcast.
617        core_thread_handle.stop().await;
618        // Stop block subscriptions before stopping network server.
619        subscriber.stop().await;
620        network_manager.stop().await;
621
622        context
623            .metrics
624            .node_metrics
625            .uptime
626            .observe(start_time.elapsed().as_secs_f64());
627
628        drop((
629            context,
630            transaction_client,
631            synchronizer,
632            store,
633            subscriber,
634            observer_service,
635            network_manager,
636        ));
637
638        // Canceled tasks should wait to report cancellation on next poll, but they report from
639        // their JoinHandles immediately under msim. Their futures and captured references are
640        // only dropped when the executor next processes the canceled tasks. Sleeping (instead
641        // of yielding) ensures this task resumes only after the pending drops have run.
642        // In production tokio, a canceled task's future is guaranteed to have been dropped when
643        // its JoinHandle resolves, so the loop exits on the first check.
644        let mut dag_state_owners = dag_state.strong_count();
645        for _ in 0..5 {
646            if dag_state_owners == 0 {
647                break;
648            }
649            tokio::time::sleep(Duration::from_millis(1)).await;
650            dag_state_owners = dag_state.strong_count();
651        }
652        if dag_state_owners != 0 {
653            debug_fatal!(
654                "DagState still has {} owner(s) after stopping ConsensusAuthority",
655                dag_state_owners
656            );
657        }
658    }
659
660    pub(crate) fn transaction_client(&self) -> Arc<TransactionClient> {
661        self.transaction_client.clone()
662    }
663
664    pub(crate) fn store(&self) -> Arc<RocksDBStore> {
665        self.store.clone()
666    }
667
668    pub(crate) fn update_peer_address(
669        &self,
670        network_pubkey: NetworkPublicKey,
671        address: Option<Multiaddr>,
672    ) {
673        // Find the peer index for this network key
674        let Some(peer) = self
675            .context
676            .committee
677            .authorities()
678            .find(|(_, authority)| authority.network_key == network_pubkey)
679            .map(|(index, _)| index)
680        else {
681            warn!(
682                "Network public key {:?} not found in committee, ignoring address update",
683                network_pubkey
684            );
685            return;
686        };
687
688        // Update the address in the network manager
689        self.network_manager.update_peer_address(peer, address);
690
691        // Re-subscribe to the peer to force reconnection with new address
692        if peer != self.context.own_index {
693            info!("Re-subscribing to peer {} after address update", peer);
694            match &self.subscriber {
695                SubscriberType::Validator(s) => s.subscribe(peer),
696                SubscriberType::Observer(s) => {
697                    // For observer, create a PeerId for the validator
698                    s.subscribe(PeerId::Validator(peer));
699                }
700            }
701        }
702    }
703}
704
705#[cfg(test)]
706mod tests {
707    #![allow(non_snake_case)]
708
709    use std::{
710        collections::{BTreeMap, BTreeSet},
711        sync::Arc,
712        time::Duration,
713    };
714
715    use consensus_config::{
716        AuthorityIndex, ObserverParameters, Parameters, PeerRecord, local_committee_and_keys,
717    };
718    use mysten_metrics::RegistryService;
719    use mysten_metrics::monitored_mpsc::UnboundedReceiver;
720    use prometheus::Registry;
721    use rand::{SeedableRng, rngs::StdRng};
722    use rstest::rstest;
723    use tempfile::TempDir;
724    use tokio::time::{sleep, timeout};
725    use typed_store::DBMetrics;
726
727    use super::*;
728    use crate::{
729        CommittedSubDag,
730        block::{BlockAPI as _, GENESIS_ROUND},
731        transaction::{NoopTransactionVerifier, Priority},
732    };
733
734    #[rstest]
735    #[tokio::test]
736    async fn test_authority_start_and_stop(
737        #[values(NetworkType::Tonic)] network_type: NetworkType,
738    ) {
739        let (committee, keypairs) = local_committee_and_keys(0, vec![1]);
740        let registry = Registry::new();
741
742        let temp_dir = TempDir::new().unwrap();
743        let parameters = Parameters {
744            db_path: temp_dir.keep(),
745            ..Default::default()
746        };
747        let txn_verifier = NoopTransactionVerifier {};
748
749        let own_index = committee.to_authority_index(0).unwrap();
750        let protocol_keypair = keypairs[own_index].1.clone();
751        let network_keypair = keypairs[own_index].0.clone();
752
753        let (commit_consumer, _) = CommitConsumerArgs::new(0, 0);
754
755        let authority = ConsensusAuthority::start(
756            network_type,
757            0,
758            committee,
759            parameters,
760            ConsensusProtocolConfig::for_testing(),
761            Some(protocol_keypair),
762            network_keypair,
763            Arc::new(Clock::default()),
764            Arc::new(txn_verifier),
765            None,
766            commit_consumer,
767            registry,
768            0,
769            None,
770        )
771        .await;
772
773        assert_eq!(authority.context().own_index, own_index);
774        assert_eq!(authority.context().committee.epoch(), 0);
775        assert_eq!(authority.context().committee.size(), 1);
776
777        authority.stop().await;
778    }
779
780    #[rstest]
781    #[tokio::test]
782    async fn test_observer_start_and_stop(#[values(NetworkType::Tonic)] network_type: NetworkType) {
783        let (committee, keypairs) = local_committee_and_keys(0, vec![1]);
784        let registry = Registry::new();
785
786        let temp_dir = TempDir::new().unwrap();
787        let parameters = Parameters {
788            db_path: temp_dir.keep(),
789            ..Default::default()
790        };
791        let txn_verifier = NoopTransactionVerifier {};
792
793        // Use any network keypair for the observer, it doesn't need to match a committee member
794        let network_keypair = keypairs[0].0.clone();
795
796        let (commit_consumer, _) = CommitConsumerArgs::new(0, 0);
797
798        let observer = ConsensusAuthority::start(
799            network_type,
800            0,
801            committee.clone(),
802            parameters,
803            ConsensusProtocolConfig::for_testing(),
804            None, // No protocol keypair for observer node
805            network_keypair,
806            Arc::new(Clock::default()),
807            Arc::new(txn_verifier),
808            None,
809            commit_consumer,
810            registry,
811            0,
812            None,
813        )
814        .await;
815
816        sleep(Duration::from_secs(2)).await;
817
818        // Observer nodes have own_index set to MAX as a special value
819        assert_eq!(observer.context().own_index, AuthorityIndex::MAX);
820        assert_eq!(observer.context().committee.epoch(), 0);
821        assert_eq!(observer.context().committee.size(), 1);
822        assert!(!observer.context().is_validator());
823
824        observer.stop().await;
825    }
826
827    // TODO: build AuthorityFixture.
828    // Spins up a committee of authorities and an observer node that connects to authority 0.
829    // Verifies that the network is progressing, advancing rounds and commits. It also verifies
830    // that the Observer node is receiving blocks from the network.
831    #[rstest]
832    #[tokio::test(flavor = "current_thread")]
833    async fn test_authority_committee(
834        #[values(NetworkType::Tonic)] network_type: NetworkType,
835        #[values(5, 10)] gc_depth: u32,
836    ) {
837        telemetry_subscribers::init_for_testing();
838        let db_registry = Registry::new();
839        DBMetrics::init(RegistryService::new(db_registry));
840
841        const NUM_OF_AUTHORITIES: usize = 4;
842        let (committee, keypairs) = local_committee_and_keys(0, [1; NUM_OF_AUTHORITIES].to_vec());
843        let mut protocol_config = ConsensusProtocolConfig::for_testing();
844        protocol_config.set_gc_depth_for_testing(gc_depth);
845
846        let temp_dirs = (0..NUM_OF_AUTHORITIES)
847            .map(|_| TempDir::new().unwrap())
848            .collect::<Vec<_>>();
849
850        let mut commit_receivers = Vec::with_capacity(committee.size());
851        let mut authorities = Vec::with_capacity(committee.size());
852        let mut boot_counters = [0; NUM_OF_AUTHORITIES];
853
854        // Use a unique port based on gc_depth to avoid conflicts between parallel tests
855        let observer_server_port = 8900 + gc_depth as u16;
856
857        // Create authorities with observer server enabled for authority 0
858        let mut authority_0_network_key = None;
859        for (index, authority_info) in committee.authorities() {
860            let (authority, commit_receiver) = if index.value() == 0 {
861                // Save authority 0's network key for Observer connection
862                authority_0_network_key = Some(authority_info.network_key.clone());
863                // Enable observer server for authority 0
864                make_authority_with_observer_server(
865                    index,
866                    &temp_dirs[index.value()],
867                    committee.clone(),
868                    keypairs.clone(),
869                    network_type,
870                    boot_counters[index],
871                    protocol_config.clone(),
872                    Some(observer_server_port),
873                )
874                .await
875            } else {
876                make_authority(
877                    index,
878                    &temp_dirs[index.value()],
879                    committee.clone(),
880                    keypairs.clone(),
881                    network_type,
882                    boot_counters[index],
883                    protocol_config.clone(),
884                )
885                .await
886            };
887            boot_counters[index] += 1;
888            commit_receivers.push(commit_receiver);
889            authorities.push(authority);
890        }
891
892        // Create an Observer node that connects to authority 0
893        let observer_temp_dir = TempDir::new().unwrap();
894        let mut rng = StdRng::from_seed([99; 32]);
895        let observer_network_keypair = consensus_config::NetworkKeyPair::generate(&mut rng);
896
897        let observer_parameters = Parameters {
898            db_path: observer_temp_dir.path().to_path_buf(),
899            observer: ObserverParameters {
900                // Configure Observer to connect to authority 0
901                peers: vec![PeerRecord {
902                    public_key: authority_0_network_key
903                        .clone()
904                        .expect("Authority 0 network key should be set"),
905                    address: format!("/ip4/127.0.0.1/udp/{}", observer_server_port)
906                        .parse()
907                        .unwrap(),
908                }],
909                ..Default::default()
910            },
911            ..Default::default()
912        };
913
914        let (observer_commit_consumer, observer_commit_receiver) = CommitConsumerArgs::new(0, 0);
915        let observer = ConsensusAuthority::start(
916            network_type,
917            0,
918            committee.clone(),
919            observer_parameters,
920            protocol_config.clone(),
921            None, // No protocol keypair for observer
922            observer_network_keypair,
923            Arc::new(Clock::default()),
924            Arc::new(NoopTransactionVerifier {}),
925            None,
926            observer_commit_consumer,
927            Registry::new(),
928            0,
929            None,
930        )
931        .await;
932        // The relevant endpoints are now implemented for the synchronizer and commit_syncer components, so the Observer node should be able to catch up and
933        // fetch blocks beyond the latest ones that are fetched from the stream.
934        commit_receivers.push(observer_commit_receiver);
935
936        // Give Observer more time to connect and sync
937        sleep(Duration::from_secs(5)).await;
938
939        const NUM_TRANSACTIONS: u8 = 15;
940        let mut submitted_transactions = BTreeSet::<Vec<u8>>::new();
941        for i in 0..NUM_TRANSACTIONS {
942            let txn = vec![i; 16];
943            submitted_transactions.insert(txn.clone());
944            authorities[i as usize % authorities.len()]
945                .transaction_client()
946                .submit(vec![txn], Priority::Normal)
947                .await
948                .unwrap();
949        }
950
951        for receiver in &mut commit_receivers {
952            let mut expected_transactions = submitted_transactions.clone();
953            loop {
954                let committed_subdag =
955                    tokio::time::timeout(Duration::from_secs(1), receiver.recv())
956                        .await
957                        .unwrap()
958                        .unwrap();
959                for b in committed_subdag.blocks {
960                    for txn in b.transactions().iter().map(|t| t.data().to_vec()) {
961                        assert!(
962                            expected_transactions.remove(&txn),
963                            "Transaction not submitted or already seen: {:?}",
964                            txn
965                        );
966                    }
967                }
968                if expected_transactions.is_empty() {
969                    break;
970                }
971            }
972        }
973
974        // Stop authority 1.
975        let index = committee.to_authority_index(1).unwrap();
976        authorities.remove(index.value()).stop().await;
977        sleep(Duration::from_secs(10)).await;
978
979        // Restart authority 1 and let it run.
980        let (authority, commit_receiver) = make_authority(
981            index,
982            &temp_dirs[index.value()],
983            committee.clone(),
984            keypairs.clone(),
985            network_type,
986            boot_counters[index],
987            protocol_config.clone(),
988        )
989        .await;
990        boot_counters[index] += 1;
991        commit_receivers[index] = commit_receiver;
992        authorities.insert(index.value(), authority);
993        sleep(Duration::from_secs(10)).await;
994
995        // Verify that the Observer node is running
996        // TODO: The actual block processing for observers is not fully implemented yet
997        // for now we just verify that blocks are received and the number of received blocks is not far from
998        // the number of blocks sent by authority 0.
999        let observer_context = observer.context();
1000        assert!(
1001            observer_context.is_observer(),
1002            "It should be an observer node"
1003        );
1004
1005        // Get the total verified_blocks from authority 0 (sum across all sending authorities)
1006        let authority_0 = &authorities[0];
1007        let authority_0_context = authority_0.context();
1008        let mut authority_0_total_verified_blocks = 0;
1009
1010        // Sum verified_blocks from all authorities as seen by authority 0
1011        for (_, authority_info) in committee.authorities() {
1012            if let Ok(metric) = authority_0_context
1013                .metrics
1014                .node_metrics
1015                .verified_blocks
1016                .get_metric_with_label_values(&[&authority_info.hostname])
1017            {
1018                authority_0_total_verified_blocks += metric.get();
1019                println!(
1020                    "authority_info.hostname: {}, metric: {:?}",
1021                    authority_info.hostname, authority_0_total_verified_blocks
1022                );
1023            }
1024        }
1025
1026        let mut authority_0_total_proposed_blocks = 0;
1027        for force in [true, false] {
1028            if let Ok(metric) = authority_0_context
1029                .metrics
1030                .node_metrics
1031                .proposed_blocks
1032                .get_metric_with_label_values(&[&force.to_string()])
1033            {
1034                authority_0_total_proposed_blocks += metric.get();
1035            }
1036        }
1037
1038        authority_0_total_verified_blocks += authority_0_total_proposed_blocks;
1039
1040        // Sum verified_blocks from all authorities as seen by the observer
1041        let mut observer_received_blocks = 0;
1042        for (_, authority_info) in committee.authorities() {
1043            if let Ok(metric) = observer_context
1044                .metrics
1045                .node_metrics
1046                .verified_blocks
1047                .get_metric_with_label_values(&[&authority_info.hostname])
1048            {
1049                observer_received_blocks += metric.get();
1050            }
1051        }
1052
1053        // Compare the values - they should be related but might not be exactly equal
1054        // due to timing and the observer connecting mid-stream
1055        assert!(
1056            observer_received_blocks > 0,
1057            "Observer should have received at least some blocks, got: {}",
1058            observer_received_blocks
1059        );
1060
1061        println!(
1062            "authority_0_total_verified_blocks: {}, observer_received_blocks: {}",
1063            authority_0_total_verified_blocks, observer_received_blocks
1064        );
1065
1066        const TOLERANCE: u64 = 20;
1067        assert!(
1068            authority_0_total_verified_blocks - observer_received_blocks <= TOLERANCE,
1069            "The number of blocks received by the observer ({}) should be close to the number of blocks verified by authority 0 ({})",
1070            observer_received_blocks,
1071            authority_0_total_verified_blocks,
1072        );
1073
1074        // Stop observer first
1075        observer.stop().await;
1076
1077        // Stop all authorities and exit.
1078        for authority in authorities {
1079            authority.stop().await;
1080        }
1081    }
1082
1083    #[rstest]
1084    #[tokio::test(flavor = "current_thread")]
1085    async fn test_small_committee(
1086        #[values(NetworkType::Tonic)] network_type: NetworkType,
1087        #[values(1, 2, 3)] num_authorities: usize,
1088    ) {
1089        telemetry_subscribers::init_for_testing();
1090        let db_registry = Registry::new();
1091        DBMetrics::init(RegistryService::new(db_registry));
1092
1093        let (committee, keypairs) = local_committee_and_keys(0, vec![1; num_authorities]);
1094        let protocol_config = ConsensusProtocolConfig::for_testing();
1095
1096        let temp_dirs = (0..num_authorities)
1097            .map(|_| TempDir::new().unwrap())
1098            .collect::<Vec<_>>();
1099
1100        let mut output_receivers = Vec::with_capacity(committee.size());
1101        let mut authorities: Vec<ConsensusAuthority> = Vec::with_capacity(committee.size());
1102        let mut boot_counters = vec![0; num_authorities];
1103
1104        for (index, _authority_info) in committee.authorities() {
1105            let (authority, commit_receiver) = make_authority(
1106                index,
1107                &temp_dirs[index.value()],
1108                committee.clone(),
1109                keypairs.clone(),
1110                network_type,
1111                boot_counters[index],
1112                protocol_config.clone(),
1113            )
1114            .await;
1115            boot_counters[index] += 1;
1116            output_receivers.push(commit_receiver);
1117            authorities.push(authority);
1118        }
1119
1120        const NUM_TRANSACTIONS: u8 = 15;
1121        let mut submitted_transactions = BTreeSet::<Vec<u8>>::new();
1122        for i in 0..NUM_TRANSACTIONS {
1123            let txn = vec![i; 16];
1124            submitted_transactions.insert(txn.clone());
1125            authorities[i as usize % authorities.len()]
1126                .transaction_client()
1127                .submit(vec![txn], Priority::Normal)
1128                .await
1129                .unwrap();
1130        }
1131
1132        for receiver in &mut output_receivers {
1133            let mut expected_transactions = submitted_transactions.clone();
1134            loop {
1135                let committed_subdag =
1136                    tokio::time::timeout(Duration::from_secs(1), receiver.recv())
1137                        .await
1138                        .unwrap()
1139                        .unwrap();
1140                for b in committed_subdag.blocks {
1141                    for txn in b.transactions().iter().map(|t| t.data().to_vec()) {
1142                        assert!(
1143                            expected_transactions.remove(&txn),
1144                            "Transaction not submitted or already seen: {:?}",
1145                            txn
1146                        );
1147                    }
1148                }
1149                if expected_transactions.is_empty() {
1150                    break;
1151                }
1152            }
1153        }
1154
1155        // Stop authority 0.
1156        let index = committee.to_authority_index(0).unwrap();
1157        authorities.remove(index.value()).stop().await;
1158        sleep(Duration::from_secs(10)).await;
1159
1160        // Restart authority 0 and let it run.
1161        let (authority, commit_receiver) = make_authority(
1162            index,
1163            &temp_dirs[index.value()],
1164            committee.clone(),
1165            keypairs.clone(),
1166            network_type,
1167            boot_counters[index],
1168            protocol_config.clone(),
1169        )
1170        .await;
1171        boot_counters[index] += 1;
1172        output_receivers[index] = commit_receiver;
1173        authorities.insert(index.value(), authority);
1174        sleep(Duration::from_secs(10)).await;
1175
1176        // Stop all authorities and exit.
1177        for authority in authorities {
1178            authority.stop().await;
1179        }
1180    }
1181
1182    #[rstest]
1183    #[tokio::test(flavor = "current_thread")]
1184    async fn test_amnesia_recovery_success(#[values(5, 10)] gc_depth: u32) {
1185        telemetry_subscribers::init_for_testing();
1186        let db_registry = Registry::new();
1187        DBMetrics::init(RegistryService::new(db_registry));
1188
1189        const NUM_OF_AUTHORITIES: usize = 4;
1190        let (committee, keypairs) = local_committee_and_keys(0, [1; NUM_OF_AUTHORITIES].to_vec());
1191        let mut commit_receivers = vec![];
1192        let mut authorities = BTreeMap::new();
1193        let mut temp_dirs = BTreeMap::new();
1194        let mut boot_counters = [0; NUM_OF_AUTHORITIES];
1195
1196        let mut protocol_config = ConsensusProtocolConfig::for_testing();
1197        protocol_config.set_gc_depth_for_testing(gc_depth);
1198
1199        for (index, _authority_info) in committee.authorities() {
1200            let dir = TempDir::new().unwrap();
1201            let (authority, commit_receiver) = make_authority(
1202                index,
1203                &dir,
1204                committee.clone(),
1205                keypairs.clone(),
1206                NetworkType::Tonic,
1207                boot_counters[index],
1208                protocol_config.clone(),
1209            )
1210            .await;
1211            boot_counters[index] += 1;
1212            commit_receivers.push(commit_receiver);
1213            authorities.insert(index, authority);
1214            temp_dirs.insert(index, dir);
1215        }
1216
1217        // Now we take the receiver of authority 1 and we wait until we see at least one block committed from this authority
1218        // We wait until we see at least one committed block authored from this authority. That way we'll be 100% sure that
1219        // at least one block has been proposed and successfully received by a quorum of nodes.
1220        let index_1 = committee.to_authority_index(1).unwrap();
1221        'outer: while let Some(result) =
1222            timeout(Duration::from_secs(10), commit_receivers[index_1].recv())
1223                .await
1224                .expect("Timed out while waiting for at least one committed block from authority 1")
1225        {
1226            for block in result.blocks {
1227                if block.round() > GENESIS_ROUND && block.author() == index_1 {
1228                    break 'outer;
1229                }
1230            }
1231        }
1232
1233        // Stop authority 1 & 2.
1234        // * Authority 1 will be used to wipe out their DB and practically "force" the amnesia recovery.
1235        // * Authority 2 is stopped in order to simulate less than f+1 availability which will
1236        // make authority 1 retry during amnesia recovery until it has finally managed to successfully get back f+1 responses.
1237        // once authority 2 is up and running again.
1238        authorities.remove(&index_1).unwrap().stop().await;
1239        let index_2 = committee.to_authority_index(2).unwrap();
1240        authorities.remove(&index_2).unwrap().stop().await;
1241        sleep(Duration::from_secs(5)).await;
1242
1243        // Authority 1: create a new directory to simulate amnesia. The node will start having participated previously
1244        // to consensus but now will attempt to synchronize the last own block and recover from there. It won't be able
1245        // to do that successfully as authority 2 is still down.
1246        let dir = TempDir::new().unwrap();
1247        // We do reset the boot counter for this one to simulate a "binary" restart
1248        boot_counters[index_1] = 0;
1249        let (authority, mut commit_receiver) = make_authority(
1250            index_1,
1251            &dir,
1252            committee.clone(),
1253            keypairs.clone(),
1254            NetworkType::Tonic,
1255            boot_counters[index_1],
1256            protocol_config.clone(),
1257        )
1258        .await;
1259        boot_counters[index_1] += 1;
1260        authorities.insert(index_1, authority);
1261        temp_dirs.insert(index_1, dir);
1262        sleep(Duration::from_secs(5)).await;
1263
1264        // Now spin up authority 2 using its earlier directly - so no amnesia recovery should be forced here.
1265        // Authority 1 should be able to recover from amnesia successfully.
1266        let (authority, _commit_receiver) = make_authority(
1267            index_2,
1268            &temp_dirs[&index_2],
1269            committee.clone(),
1270            keypairs,
1271            NetworkType::Tonic,
1272            boot_counters[index_2],
1273            protocol_config.clone(),
1274        )
1275        .await;
1276        boot_counters[index_2] += 1;
1277        authorities.insert(index_2, authority);
1278        sleep(Duration::from_secs(5)).await;
1279
1280        // We wait until we see at least one committed block authored from this authority
1281        'outer: while let Some(result) = commit_receiver.recv().await {
1282            for block in result.blocks {
1283                if block.round() > GENESIS_ROUND && block.author() == index_1 {
1284                    break 'outer;
1285                }
1286            }
1287        }
1288
1289        // Stop all authorities and exit.
1290        for (_, authority) in authorities {
1291            authority.stop().await;
1292        }
1293    }
1294
1295    // TODO: create a fixture
1296    async fn make_authority(
1297        index: AuthorityIndex,
1298        db_dir: &TempDir,
1299        committee: Committee,
1300        keypairs: Vec<(NetworkKeyPair, ProtocolKeyPair)>,
1301        network_type: NetworkType,
1302        boot_counter: u64,
1303        protocol_config: ConsensusProtocolConfig,
1304    ) -> (ConsensusAuthority, UnboundedReceiver<CommittedSubDag>) {
1305        make_authority_with_observer_server(
1306            index,
1307            db_dir,
1308            committee,
1309            keypairs,
1310            network_type,
1311            boot_counter,
1312            protocol_config,
1313            None, // No observer server port
1314        )
1315        .await
1316    }
1317
1318    async fn make_authority_with_observer_server(
1319        index: AuthorityIndex,
1320        db_dir: &TempDir,
1321        committee: Committee,
1322        keypairs: Vec<(NetworkKeyPair, ProtocolKeyPair)>,
1323        network_type: NetworkType,
1324        boot_counter: u64,
1325        protocol_config: ConsensusProtocolConfig,
1326        observer_server_port: Option<u16>,
1327    ) -> (ConsensusAuthority, UnboundedReceiver<CommittedSubDag>) {
1328        let registry = Registry::new();
1329
1330        // Cache less blocks to exercise commit sync.
1331        let mut parameters = Parameters {
1332            db_path: db_dir.path().to_path_buf(),
1333            dag_state_cached_rounds: 5,
1334            commit_sync_parallel_fetches: 2,
1335            commit_sync_batch_size: 3,
1336            sync_last_known_own_block_timeout: Duration::from_millis(2_000),
1337            ..Default::default()
1338        };
1339
1340        // Enable observer server if port is provided
1341        if let Some(port) = observer_server_port {
1342            parameters.observer.server_port = Some(port);
1343        }
1344
1345        let txn_verifier = NoopTransactionVerifier {};
1346
1347        let protocol_keypair = keypairs[index].1.clone();
1348        let network_keypair = keypairs[index].0.clone();
1349
1350        let (commit_consumer, commit_receiver) = CommitConsumerArgs::new(0, 0);
1351
1352        let authority = ConsensusAuthority::start(
1353            network_type,
1354            0,
1355            committee,
1356            parameters,
1357            protocol_config,
1358            Some(protocol_keypair),
1359            network_keypair,
1360            Arc::new(Clock::default()),
1361            Arc::new(txn_verifier),
1362            None,
1363            commit_consumer,
1364            registry,
1365            boot_counter,
1366            None,
1367        )
1368        .await;
1369
1370        (authority, commit_receiver)
1371    }
1372}