1use crate::authority::authority_per_epoch_store::AuthorityPerEpochStore;
4use crate::consensus_adapter::{BlockStatusReceiver, ConsensusClient};
5use crate::consensus_handler::{ConsensusHandlerInitializer, MysticetiConsensusHandler};
6use crate::consensus_transaction_pool::{
7 ConsensusTransactionPool, TransactionPoolClient, TransactionPoolContext,
8};
9use crate::consensus_validator::SuiTxValidator;
10use crate::mysticeti_adapter::LazyMysticetiClient;
11use arc_swap::ArcSwapOption;
12use async_trait::async_trait;
13use consensus_config::{
14 ChainType, Committee, ConsensusProtocolConfig, NetworkKeyPair,
15 NetworkPublicKey as ConsensusNetworkPublicKey, Parameters, ProtocolKeyPair, Stake,
16};
17use consensus_core::{
18 Clock, CommitConsumerArgs, CommitConsumerMonitor, CommitIndex, ConsensusAuthority, NetworkType,
19 RandomnessSignatureHandler, TransactionPool, storage::rocksdb_store::RocksDBStore,
20};
21use core::panic;
22use fastcrypto::encoding::{Encoding, Hex};
23use fastcrypto::traits::KeyPair as _;
24use mysten_common::debug_fatal;
25use mysten_metrics::{RegistryID, RegistryService};
26use mysten_network::Multiaddr;
27use prometheus::{
28 IntGauge, IntGaugeVec, Registry, register_int_gauge_vec_with_registry,
29 register_int_gauge_with_registry,
30};
31use std::collections::BTreeMap;
32use std::path::PathBuf;
33use std::sync::Arc;
34use std::time::{Duration, Instant};
35use sui_config::{ConsensusConfig, NodeConfig};
36use sui_network::endpoint_manager::{AddressSource, ConsensusAddressUpdater};
37use sui_protocol_config::{Chain, ProtocolConfig, ProtocolVersion};
38use sui_types::crypto::NetworkPublicKey;
39use sui_types::error::{SuiErrorKind, SuiResult};
40use sui_types::messages_consensus::{ConsensusPosition, ConsensusTransaction};
41use sui_types::node_role::NodeRole;
42use sui_types::{
43 committee::EpochId, sui_system_state::epoch_start_sui_system_state::EpochStartSystemStateTrait,
44};
45use tokio::sync::{Mutex, broadcast};
46use tokio::time::{sleep, timeout};
47use tracing::{error, info};
48
49#[cfg(test)]
50#[path = "../unit_tests/consensus_manager_tests.rs"]
51pub mod consensus_manager_tests;
52
53#[derive(PartialEq)]
54enum Running {
55 True(EpochId, ProtocolVersion),
56 False,
57}
58
59struct AddressOverridesMap {
62 map: BTreeMap<
64 ConsensusNetworkPublicKey,
65 BTreeMap<sui_network::endpoint_manager::AddressSource, Vec<Multiaddr>>,
66 >,
67}
68
69impl AddressOverridesMap {
70 pub fn new() -> Self {
71 Self {
72 map: BTreeMap::new(),
73 }
74 }
75
76 pub fn insert(
77 &mut self,
78 network_pubkey: ConsensusNetworkPublicKey,
79 source: sui_network::endpoint_manager::AddressSource,
80 addresses: Vec<Multiaddr>,
81 ) {
82 self.map
83 .entry(network_pubkey)
84 .or_default()
85 .insert(source, addresses);
86 }
87
88 pub fn remove(
89 &mut self,
90 network_pubkey: ConsensusNetworkPublicKey,
91 source: sui_network::endpoint_manager::AddressSource,
92 ) {
93 self.map
94 .entry(network_pubkey.clone())
95 .or_default()
96 .remove(&source);
97
98 if self.map.get(&network_pubkey.clone()).unwrap().is_empty() {
100 self.map.remove(&network_pubkey);
101 }
102 }
103
104 pub fn get_highest_priority_source_and_address(
108 &self,
109 network_pubkey: ConsensusNetworkPublicKey,
110 ) -> Option<(sui_network::endpoint_manager::AddressSource, Multiaddr)> {
111 self.map
112 .get(&network_pubkey)
113 .and_then(|sources| sources.first_key_value())
114 .and_then(|(source, addresses)| {
115 addresses.first().cloned().map(|address| (*source, address))
116 })
117 }
118
119 pub fn get_all_highest_priority_addresses(
120 &self,
121 ) -> Vec<(ConsensusNetworkPublicKey, Multiaddr)> {
122 let mut result = Vec::new();
123
124 for (network_pubkey, sources) in self.map.iter() {
125 if let Some((_source, addresses)) = sources.first_key_value()
126 && let Some(address) = addresses.first()
127 {
128 result.push((network_pubkey.clone(), address.clone()));
129 }
130 }
131 result
132 }
133}
134
135fn apply_v3_threshold_overrides(committee: Committee) -> Committee {
141 let malicious_stake: Stake = std::env::var("SUI_CONSENSUS_V3_MALICIOUS_STAKE")
142 .ok()
143 .and_then(|s| s.parse().ok())
144 .unwrap_or(1_250);
145 let crash_stake: Stake = std::env::var("SUI_CONSENSUS_V3_CRASH_STAKE")
146 .ok()
147 .and_then(|s| s.parse().ok())
148 .unwrap_or(1_250);
149 info!(
150 "consensus_manager: applying v3 committee thresholds \
151 (malicious_stake={malicious_stake}, crash_stake={crash_stake})"
152 );
153 Committee::new_v3(
154 committee.epoch(),
155 committee.authorities_slice().to_vec(),
156 malicious_stake,
157 crash_stake,
158 )
159}
160
161fn to_consensus_protocol_config(config: &ProtocolConfig) -> ConsensusProtocolConfig {
162 let chain_type = match config.chain() {
163 Chain::Mainnet => ChainType::Mainnet,
164 Chain::Testnet => ChainType::Testnet,
165 Chain::Unknown => ChainType::Unknown,
166 };
167 ConsensusProtocolConfig::new(
168 config.version.as_u64(),
169 chain_type,
170 config.max_transaction_size_bytes(),
171 config.max_transactions_in_block_bytes(),
172 config.max_num_transactions_in_block(),
173 config.gc_depth(),
174 config.consensus_slim_block_propagation(),
175 true,
176 config.mysticeti_num_leaders_per_round(),
177 config.consensus_bad_nodes_stake_threshold(),
178 false,
179 300,
180 12,
181 )
182}
183
184pub struct ConsensusManager {
187 consensus_config: ConsensusConfig,
188 protocol_keypair: Option<ProtocolKeyPair>,
189 network_keypair: NetworkKeyPair,
190 storage_base_path: PathBuf,
191 metrics: Arc<ConsensusManagerMetrics>,
192 registry_service: RegistryService,
193 authority: ArcSwapOption<(ConsensusAuthority, RegistryID)>,
194
195 client: Arc<LazyMysticetiClient>,
198 consensus_client: Arc<UpdatableConsensusClient>,
199 transaction_pool_context: Option<Arc<TransactionPoolContext>>,
200 transaction_pool: ArcSwapOption<ConsensusTransactionPool>,
201
202 consensus_handler: Mutex<Option<MysticetiConsensusHandler>>,
203
204 #[cfg(test)]
205 pub(crate) consumer_monitor: ArcSwapOption<CommitConsumerMonitor>,
206 #[cfg(not(test))]
207 consumer_monitor: ArcSwapOption<CommitConsumerMonitor>,
208 consumer_monitor_sender: broadcast::Sender<Arc<CommitConsumerMonitor>>,
209
210 running: Mutex<Running>,
211
212 #[cfg(test)]
213 pub(crate) boot_counter: Mutex<u64>,
214 #[cfg(not(test))]
215 boot_counter: Mutex<u64>,
216
217 address_overrides: parking_lot::Mutex<AddressOverridesMap>,
220}
221
222impl ConsensusManager {
223 pub fn new(
224 node_config: &NodeConfig,
225 consensus_config: &ConsensusConfig,
226 registry_service: &RegistryService,
227 consensus_client: Arc<UpdatableConsensusClient>,
228 transaction_pool_context: Option<Arc<TransactionPoolContext>>,
229 node_role: NodeRole,
230 ) -> Self {
231 let metrics = Arc::new(ConsensusManagerMetrics::new(
232 ®istry_service.default_registry(),
233 ));
234 let client = Arc::new(LazyMysticetiClient::new());
235 let (consumer_monitor_sender, _) = broadcast::channel(1);
236 let protocol_keypair = if node_role.is_validator() {
237 Some(ProtocolKeyPair::new(node_config.worker_key_pair().copy()))
238 } else {
239 None
240 };
241 Self {
242 consensus_config: consensus_config.clone(),
243 protocol_keypair,
244 network_keypair: NetworkKeyPair::new(node_config.network_key_pair().copy()),
245 storage_base_path: consensus_config.db_path().to_path_buf(),
246 metrics,
247 registry_service: registry_service.clone(),
248 authority: ArcSwapOption::empty(),
249 client,
250 consensus_client,
251 transaction_pool_context,
252 transaction_pool: ArcSwapOption::empty(),
253 consensus_handler: Mutex::new(None),
254 consumer_monitor: ArcSwapOption::empty(),
255 consumer_monitor_sender,
256 running: Mutex::new(Running::False),
257 boot_counter: Mutex::new(0),
258 address_overrides: parking_lot::Mutex::new(AddressOverridesMap::new()),
259 }
260 }
261
262 pub async fn start(
263 &self,
264 node_config: &NodeConfig,
265 epoch_store: Arc<AuthorityPerEpochStore>,
266 consensus_handler_initializer: ConsensusHandlerInitializer,
267 tx_validator: SuiTxValidator,
268 randomness_signature_handler: Option<Arc<dyn RandomnessSignatureHandler>>,
269 ) {
270 let epoch = epoch_store.epoch();
271 let protocol_config = epoch_store.protocol_config();
272 let consensus_protocol_config = to_consensus_protocol_config(protocol_config);
273 let system_state = epoch_store.epoch_start_state();
274 let committee = if consensus_protocol_config.enable_v3() {
275 apply_v3_threshold_overrides(system_state.get_consensus_committee())
276 } else {
277 system_state.get_consensus_committee()
278 };
279
280 let start_time = Instant::now();
282 let mut running = self.running.lock().await;
283 if let Running::True(running_epoch, running_version) = *running {
284 error!(
285 "Consensus is already Running for epoch {running_epoch:?} & protocol version {running_version:?} - shutdown first before starting",
286 );
287 return;
288 }
289 *running = Running::True(epoch, protocol_config.version);
290
291 info!(
292 "Starting up consensus for epoch {epoch:?} & protocol version {:?}",
293 protocol_config.version
294 );
295
296 let is_validator = epoch_store.is_validator();
297 if is_validator && self.protocol_keypair.is_none() {
298 debug_fatal!("validator epoch {epoch} started without a protocol keypair");
301 }
302 let pool_context = self
303 .transaction_pool_context
304 .as_ref()
305 .filter(|_| is_validator);
306 let transaction_pool: Option<Arc<dyn TransactionPool>> = if let Some(context) = pool_context
307 {
308 let config = node_config
309 .consensus_transaction_pool
310 .as_ref()
311 .expect("transaction pool context requires pool config");
312 let pool = Arc::new(ConsensusTransactionPool::new(
313 epoch_store.clone(),
314 config.max_pending_transactions(&self.consensus_config),
315 context.metrics().clone(),
316 context.adapter_metrics().clone(),
317 ));
318 context.set_active(epoch, pool.clone());
319 self.transaction_pool.store(Some(pool.clone()));
320 self.consensus_client
321 .set(Arc::new(TransactionPoolClient::new(context.clone())));
322 Some(pool)
323 } else {
324 if let Some(context) = &self.transaction_pool_context {
325 context.set_unavailable(epoch);
326 }
327 self.consensus_client.set(self.client.clone());
328 None
329 };
330
331 let consensus_config = node_config
332 .consensus_config()
333 .expect("consensus_config should exist");
334
335 let parameters = Parameters {
336 db_path: self.get_store_path(epoch),
337 listen_address_override: consensus_config.listen_address.clone(),
338 ..consensus_config.parameters.clone().unwrap_or_default()
339 };
340
341 let registry = Registry::new_custom(Some("consensus".to_string()), None).unwrap();
342
343 let consensus_handler = consensus_handler_initializer.new_consensus_handler();
344
345 let num_prior_commits = protocol_config.consensus_num_requested_prior_commits_at_startup();
346 let last_processed_commit_index =
347 consensus_handler.last_processed_subdag_index() as CommitIndex;
348 let replay_after_commit_index =
349 last_processed_commit_index.saturating_sub(num_prior_commits);
350
351 let (commit_consumer, commit_receiver) =
352 CommitConsumerArgs::new(replay_after_commit_index, last_processed_commit_index);
353 let monitor = commit_consumer.monitor();
354
355 let handler = MysticetiConsensusHandler::new(
357 last_processed_commit_index,
358 consensus_handler,
359 commit_receiver,
360 monitor.clone(),
361 );
362 let mut consensus_handler = self.consensus_handler.lock().await;
363 *consensus_handler = Some(handler);
364
365 let participated_on_previous_run =
369 if let Some(previous_monitor) = self.consumer_monitor.swap(Some(monitor.clone())) {
370 previous_monitor.highest_handled_commit() > 0
371 } else {
372 false
373 };
374
375 let mut boot_counter = self.boot_counter.lock().await;
380 if participated_on_previous_run {
381 *boot_counter += 1;
382 } else {
383 info!(
384 "Node has not participated in previous epoch consensus. Boot counter ({}) will not increment.",
385 *boot_counter
386 );
387 }
388
389 let authority = ConsensusAuthority::start(
390 NetworkType::Tonic,
391 epoch_store.epoch_start_config().epoch_start_timestamp_ms(),
392 committee.clone(),
393 parameters.clone(),
394 consensus_protocol_config,
395 self.protocol_keypair.clone(),
396 self.network_keypair.clone(),
397 Arc::new(Clock::default()),
398 Arc::new(tx_validator.clone()),
399 transaction_pool,
400 commit_consumer,
401 registry.clone(),
402 *boot_counter,
403 randomness_signature_handler,
404 )
405 .await;
406 let client = pool_context
407 .is_none()
408 .then(|| authority.transaction_client());
409
410 let registry_id = self.registry_service.add(registry.clone());
411
412 let registered_authority = Arc::new((authority, registry_id));
413 self.authority.swap(Some(registered_authority.clone()));
414
415 let highest_priority_addresses = self
417 .address_overrides
418 .lock()
419 .get_all_highest_priority_addresses();
420 for (network_pubkey, address) in highest_priority_addresses {
421 registered_authority
422 .0
423 .update_peer_address(network_pubkey, Some(address.clone()));
424 }
425
426 if let Some(client) = client {
428 self.client.set(client);
429 }
430
431 let _ = self.consumer_monitor_sender.send(monitor);
433
434 let elapsed = start_time.elapsed().as_secs_f64();
435 self.metrics.start_latency.set(elapsed as i64);
436
437 tracing::info!(
438 "Started consensus for epoch {} & protocol version {:?} completed - took {} seconds",
439 epoch,
440 protocol_config.version,
441 elapsed
442 );
443 }
444
445 pub async fn shutdown(&self) {
446 info!("Shutting down consensus ...");
447
448 let start_time = Instant::now();
450 let mut running = self.running.lock().await;
451 let (shutdown_epoch, shutdown_version) = match *running {
452 Running::True(epoch, version) => {
453 tracing::info!(
454 "Shutting down consensus for epoch {epoch:?} & protocol version {version:?}"
455 );
456 *running = Running::False;
457 (epoch, version)
458 }
459 Running::False => {
460 error!("Consensus shutdown was called but consensus is not running");
461 return;
462 }
463 };
464
465 let pool = self.transaction_pool.swap(None);
467 if let Some(pool) = &pool {
468 pool.close();
469 }
470 self.client.clear();
471
472 let r = self.authority.swap(None).unwrap();
474 let Ok((authority, registry_id)) = Arc::try_unwrap(r) else {
475 panic!("Failed to retrieve the Mysticeti authority");
476 };
477
478 authority.stop().await;
480
481 let mut consensus_handler = self.consensus_handler.lock().await;
483 if let Some(mut handler) = consensus_handler.take() {
484 handler.abort().await;
485 }
486
487 self.registry_service.remove(registry_id);
489
490 if pool.is_none() {
491 self.consensus_client.clear();
492 }
493
494 let elapsed = start_time.elapsed().as_secs_f64();
495 self.metrics.shutdown_latency.set(elapsed as i64);
496
497 tracing::info!(
498 "Consensus stopped for epoch {shutdown_epoch:?} & protocol version {shutdown_version:?} is complete - took {} seconds",
499 elapsed
500 );
501 }
502
503 pub async fn is_running(&self) -> bool {
504 let running = self.running.lock().await;
505 matches!(*running, Running::True(_, _))
506 }
507
508 pub fn replay_waiter(&self) -> ReplayWaiter {
509 let consumer_monitor_receiver = self.consumer_monitor_sender.subscribe();
510 ReplayWaiter::new(consumer_monitor_receiver)
511 }
512
513 pub fn get_storage_base_path(&self) -> PathBuf {
514 self.consensus_config.db_path().to_path_buf()
515 }
516
517 pub fn consensus_store(&self) -> Option<Arc<RocksDBStore>> {
518 self.authority.load().as_ref().map(|a| a.0.store())
519 }
520
521 pub fn address_overrides_snapshot(
522 &self,
523 ) -> BTreeMap<
524 ConsensusNetworkPublicKey,
525 BTreeMap<sui_network::endpoint_manager::AddressSource, Vec<Multiaddr>>,
526 > {
527 self.address_overrides.lock().map.clone()
528 }
529
530 fn get_store_path(&self, epoch: EpochId) -> PathBuf {
531 let mut store_path = self.storage_base_path.clone();
532 store_path.push(format!("{}", epoch));
533 store_path
534 }
535}
536
537impl Drop for ConsensusManager {
538 fn drop(&mut self) {
539 if let Some(pool) = self.transaction_pool.swap(None) {
542 pool.close();
543 }
544 }
545}
546
547impl ConsensusAddressUpdater for ConsensusManager {
549 fn update_address(
550 &self,
551 network_pubkey: NetworkPublicKey,
552 source: sui_network::endpoint_manager::AddressSource,
553 addresses: Vec<Multiaddr>,
554 ) -> SuiResult<()> {
555 let network_pubkey = ConsensusNetworkPublicKey::new(network_pubkey.clone());
557
558 let highest_priority = {
560 let mut address_overrides = self.address_overrides.lock();
561
562 if addresses.is_empty() {
563 address_overrides.remove(network_pubkey.clone(), source);
564 } else {
565 address_overrides.insert(network_pubkey.clone(), source, addresses.clone());
566 }
567
568 address_overrides.get_highest_priority_source_and_address(network_pubkey.clone())
569 };
570 self.metrics.set_active_address_source(
571 &Hex::encode(network_pubkey.to_bytes()),
572 highest_priority.as_ref().map(|(source, _)| *source),
573 );
574
575 let address_to_apply = highest_priority.map(|(_, address)| address);
577 if let Some(authority) = self.authority.load_full() {
578 authority
579 .0
580 .update_peer_address(network_pubkey, address_to_apply);
581 Ok(())
582 } else {
583 info!(
584 "Consensus authority node is not running, address update persisted for peer {:?} from source {:?} and will be applied on next start",
585 network_pubkey, source
586 );
587 Err(SuiErrorKind::GenericAuthorityError {
588 error: "Consensus authority node is not running. Can not apply address update"
589 .to_string(),
590 }
591 .into())
592 }
593 }
594}
595
596#[derive(Default)]
599pub struct UpdatableConsensusClient {
600 client: ArcSwapOption<Arc<dyn ConsensusClient>>,
602}
603
604impl UpdatableConsensusClient {
605 pub fn new() -> Self {
606 Self {
607 client: ArcSwapOption::empty(),
608 }
609 }
610
611 async fn get(&self) -> Arc<Arc<dyn ConsensusClient>> {
612 const START_TIMEOUT: Duration = Duration::from_secs(300);
613 const RETRY_INTERVAL: Duration = Duration::from_millis(100);
614 if let Ok(client) = timeout(START_TIMEOUT, async {
615 loop {
616 let Some(client) = self.client.load_full() else {
617 sleep(RETRY_INTERVAL).await;
618 continue;
619 };
620 return client;
621 }
622 })
623 .await
624 {
625 return client;
626 }
627
628 panic!(
629 "Timed out after {:?} waiting for Consensus to start!",
630 START_TIMEOUT,
631 );
632 }
633
634 pub fn set(&self, client: Arc<dyn ConsensusClient>) {
635 self.client.store(Some(Arc::new(client)));
636 }
637
638 pub fn clear(&self) {
639 self.client.store(None);
640 }
641}
642
643#[async_trait]
644impl ConsensusClient for UpdatableConsensusClient {
645 async fn submit(
646 &self,
647 transactions: &[ConsensusTransaction],
648 epoch_store: &Arc<AuthorityPerEpochStore>,
649 ) -> SuiResult<(Vec<ConsensusPosition>, BlockStatusReceiver)> {
650 let client = self.get().await;
651 client.submit(transactions, epoch_store).await
652 }
653}
654
655pub struct ReplayWaiter {
657 consumer_monitor_receiver: broadcast::Receiver<Arc<CommitConsumerMonitor>>,
658}
659
660impl ReplayWaiter {
661 pub(crate) fn new(
662 consumer_monitor_receiver: broadcast::Receiver<Arc<CommitConsumerMonitor>>,
663 ) -> Self {
664 Self {
665 consumer_monitor_receiver,
666 }
667 }
668
669 pub(crate) async fn wait_for_replay(mut self) {
670 loop {
671 info!("Waiting for consensus to start replaying ...");
672 let Ok(monitor) = self.consumer_monitor_receiver.recv().await else {
673 continue;
674 };
675 info!("Waiting for consensus handler to finish replaying ...");
676 monitor
677 .replay_to_consumer_last_processed_commit_complete()
678 .await;
679 break;
680 }
681 }
682}
683
684impl Clone for ReplayWaiter {
685 fn clone(&self) -> Self {
686 Self {
687 consumer_monitor_receiver: self.consumer_monitor_receiver.resubscribe(),
688 }
689 }
690}
691
692pub struct ConsensusManagerMetrics {
693 start_latency: IntGauge,
694 shutdown_latency: IntGauge,
695 active_address_source: IntGaugeVec,
696}
697
698impl ConsensusManagerMetrics {
699 pub fn new(registry: &Registry) -> Self {
700 Self {
701 start_latency: register_int_gauge_with_registry!(
702 "consensus_manager_start_latency",
703 "The latency of starting up consensus nodes",
704 registry,
705 )
706 .unwrap(),
707 shutdown_latency: register_int_gauge_with_registry!(
708 "consensus_manager_shutdown_latency",
709 "The latency of shutting down consensus nodes",
710 registry,
711 )
712 .unwrap(),
713 active_address_source: register_int_gauge_vec_with_registry!(
714 "consensus_active_address_source",
715 "Active consensus address source per committee peer, encoded as the gauge \
716 value: 0=committee (no override active; the on-chain committee address is in \
717 use), 1=admin, 2=config, 3=discovery, 4=seed, 5=chain (override priority \
718 highest to lowest). One series per peer; `peer_id` is the full hex consensus \
719 network public key.",
720 &["peer_id"],
721 registry,
722 )
723 .unwrap(),
724 }
725 }
726
727 fn set_active_address_source(
731 &self,
732 peer_id: &str,
733 active: Option<sui_network::endpoint_manager::AddressSource>,
734 ) {
735 let code = active.map_or(
736 AddressSource::DEFAULT_ADDRESS_SOURCE_CODE,
737 sui_network::endpoint_manager::AddressSource::metric_code,
738 );
739 self.active_address_source
740 .with_label_values(&[peer_id])
741 .set(code);
742 }
743}