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