iota_core/consensus_manager/
mod.rs1use std::{
6 path::PathBuf,
7 sync::Arc,
8 time::{Duration, Instant},
9};
10
11use arc_swap::ArcSwapOption;
12use async_trait::async_trait;
13use fastcrypto::traits::KeyPair as _;
14use iota_config::{ConsensusConfig, NodeConfig};
15use iota_metrics::RegistryService;
16use iota_protocol_config::ProtocolVersion;
17use iota_types::{committee::EpochId, error::IotaResult, messages_consensus::ConsensusTransaction};
18use prometheus_filtered::{IntGauge, Registry, register_int_gauge_with_registry};
19use starfish_core::{CommitConsumerMonitor, CommitIndex};
20use tokio::{
21 sync::{Mutex, MutexGuard, broadcast},
22 time::{sleep, timeout},
23};
24use tracing::info;
25
26use crate::{
27 authority::authority_per_epoch_store::AuthorityPerEpochStore,
28 consensus_adapter::{BlockStatusReceiver, ConsensusClient},
29 consensus_handler::ConsensusHandlerInitializer,
30 consensus_manager::starfish_manager::StarfishManager,
31 consensus_validator::IotaTxValidator,
32 starfish_adapter::LazyStarfishClient,
33};
34
35pub mod starfish_manager;
36
37#[derive(PartialEq)]
38pub(crate) enum Running {
39 True(EpochId, ProtocolVersion),
40 False,
41}
42
43#[async_trait]
44pub trait ConsensusManagerTrait {
45 async fn start(
46 &self,
47 node_config: &NodeConfig,
48 epoch_store: Arc<AuthorityPerEpochStore>,
49 consensus_handler_initializer: ConsensusHandlerInitializer,
50 tx_validator: IotaTxValidator,
51 );
52
53 async fn shutdown(&self);
54
55 async fn is_running(&self) -> bool;
56
57 fn replay_waiter(&self) -> ReplayWaiter;
58}
59
60pub struct ConsensusManager {
62 consensus_config: ConsensusConfig,
63 starfish_manager: StarfishManager,
64}
65
66impl ConsensusManager {
67 pub fn new(
68 node_config: &NodeConfig,
69 consensus_config: &ConsensusConfig,
70 registry_service: &RegistryService,
71 metrics_registry: &Registry,
72 consensus_client: Arc<UpdatableConsensusClient>,
73 ) -> Self {
74 let metrics = Arc::new(ConsensusManagerMetrics::new(metrics_registry));
75 let starfish_client = Arc::new(LazyStarfishClient::new());
76 consensus_client.set(starfish_client.clone());
77 let starfish_manager = StarfishManager::new(
78 node_config.protocol_key_pair().copy(),
79 node_config.network_key_pair().copy(),
80 consensus_config.db_path().to_path_buf(),
81 registry_service.clone(),
82 metrics,
83 starfish_client,
84 );
85 Self {
86 consensus_config: consensus_config.clone(),
87 starfish_manager,
88 }
89 }
90
91 pub fn get_storage_base_path(&self) -> PathBuf {
92 self.consensus_config.db_path().to_path_buf()
93 }
94}
95
96#[async_trait]
97impl ConsensusManagerTrait for ConsensusManager {
98 async fn start(
99 &self,
100 node_config: &NodeConfig,
101 epoch_store: Arc<AuthorityPerEpochStore>,
102 consensus_handler_initializer: ConsensusHandlerInitializer,
103 tx_validator: IotaTxValidator,
104 ) {
105 info!("Starting consensus protocol Starfish ...");
106 self.starfish_manager
107 .start(
108 node_config,
109 epoch_store,
110 consensus_handler_initializer,
111 tx_validator,
112 )
113 .await
114 }
115
116 async fn shutdown(&self) {
117 info!("Shutting down consensus ...");
118 self.starfish_manager.shutdown().await;
119 }
120
121 async fn is_running(&self) -> bool {
122 self.starfish_manager.is_running().await
123 }
124
125 fn replay_waiter(&self) -> ReplayWaiter {
126 self.starfish_manager.replay_waiter()
127 }
128}
129
130#[derive(Default)]
134pub struct UpdatableConsensusClient {
135 client: ArcSwapOption<Arc<dyn ConsensusClient>>,
137}
138
139impl UpdatableConsensusClient {
140 pub fn new() -> Self {
141 Self {
142 client: ArcSwapOption::empty(),
143 }
144 }
145
146 async fn get(&self) -> Arc<Arc<dyn ConsensusClient>> {
147 const START_TIMEOUT: Duration = Duration::from_secs(30);
148 const RETRY_INTERVAL: Duration = Duration::from_millis(100);
149 if let Ok(client) = timeout(START_TIMEOUT, async {
150 loop {
151 let Some(client) = self.client.load_full() else {
152 sleep(RETRY_INTERVAL).await;
153 continue;
154 };
155 return client;
156 }
157 })
158 .await
159 {
160 return client;
161 }
162
163 panic!("Timed out after {START_TIMEOUT:?} waiting for Consensus to start!",);
164 }
165
166 pub fn set(&self, client: Arc<dyn ConsensusClient>) {
167 self.client.store(Some(Arc::new(client)));
168 }
169
170 pub fn clear(&self) {
171 self.client.store(None);
172 }
173}
174
175#[async_trait]
176impl ConsensusClient for UpdatableConsensusClient {
177 async fn submit(
178 &self,
179 transactions: &[ConsensusTransaction],
180 epoch_store: &Arc<AuthorityPerEpochStore>,
181 ) -> IotaResult<BlockStatusReceiver> {
182 let client = self.get().await;
183 client.submit(transactions, epoch_store).await
184 }
185}
186
187pub(crate) type StartedConsumer = (Arc<CommitConsumerMonitor>, Option<CommitIndex>);
190
191pub(crate) fn replay_target(
196 last_validated_commit: Option<u64>,
197 resume_point: CommitIndex,
198 last_commit_before_synced_checkpoint_execution: Option<u64>,
199) -> CommitIndex {
200 let target = match last_validated_commit {
201 Some(index) => CommitIndex::try_from(index).unwrap_or(CommitIndex::MAX),
202 None => resume_point,
203 };
204 match last_commit_before_synced_checkpoint_execution {
205 Some(last) => target.min(CommitIndex::try_from(last).unwrap_or(CommitIndex::MAX)),
206 None => target,
207 }
208}
209
210pub type ConsensusReplayTargetReport = Arc<dyn Fn(CommitIndex, CommitIndex) + Send + Sync>;
214
215pub struct ReplayWaiter {
217 consumer_monitor_receiver: broadcast::Receiver<StartedConsumer>,
218}
219
220impl ReplayWaiter {
221 pub(crate) fn new(consumer_monitor_receiver: broadcast::Receiver<StartedConsumer>) -> Self {
222 Self {
223 consumer_monitor_receiver,
224 }
225 }
226
227 pub(crate) async fn wait_for_replay(mut self) {
228 loop {
229 info!("Waiting for consensus to start replaying ...");
230 let Ok((monitor, target)) = self.consumer_monitor_receiver.recv().await else {
231 continue;
232 };
233 let highest_observed = monitor.highest_observed_commit_at_startup();
234 let target = target.map_or(highest_observed, |target| target.min(highest_observed));
235 info!(
236 target,
237 highest_observed, "Waiting for consensus handler to finish replaying ..."
238 );
239 monitor.wait_until_handled(target).await;
240 break;
241 }
242 }
243}
244
245impl Clone for ReplayWaiter {
246 fn clone(&self) -> Self {
247 Self {
248 consumer_monitor_receiver: self.consumer_monitor_receiver.resubscribe(),
249 }
250 }
251}
252
253pub struct ConsensusManagerMetrics {
254 start_latency: IntGauge,
255 shutdown_latency: IntGauge,
256}
257
258impl ConsensusManagerMetrics {
259 pub fn new(registry: &Registry) -> Self {
260 Self {
261 start_latency: register_int_gauge_with_registry!(
262 "consensus_manager_start_latency",
263 "The latency of starting up consensus nodes",
264 registry,
265 )
266 .unwrap(),
267 shutdown_latency: register_int_gauge_with_registry!(
268 "consensus_manager_shutdown_latency",
269 "The latency of shutting down consensus nodes",
270 registry,
271 )
272 .unwrap(),
273 }
274 }
275}
276
277pub(crate) struct RunningLockGuard<'a> {
278 state_guard: MutexGuard<'a, Running>,
279 metrics: &'a ConsensusManagerMetrics,
280 epoch: Option<EpochId>,
281 protocol_version: Option<ProtocolVersion>,
282 start: Instant,
283}
284
285impl<'a> RunningLockGuard<'a> {
286 pub(crate) async fn acquire_start(
287 metrics: &'a ConsensusManagerMetrics,
288 running_mutex: &'a Mutex<Running>,
289 epoch: EpochId,
290 version: ProtocolVersion,
291 ) -> Option<RunningLockGuard<'a>> {
292 let running = running_mutex.lock().await;
293 if let Running::True(epoch, version) = *running {
294 tracing::warn!(
295 "Consensus is already Running for epoch {epoch:?} & protocol version {version:?} - shutdown first before starting",
296 );
297 return None;
298 }
299
300 tracing::info!("Starting up consensus for epoch {epoch:?} & protocol version {version:?}");
301
302 Some(RunningLockGuard {
303 state_guard: running,
304 metrics,
305 start: Instant::now(),
306 epoch: Some(epoch),
307 protocol_version: Some(version),
308 })
309 }
310
311 pub(crate) async fn acquire_shutdown(
312 metrics: &'a ConsensusManagerMetrics,
313 running_mutex: &'a Mutex<Running>,
314 ) -> Option<RunningLockGuard<'a>> {
315 let running = running_mutex.lock().await;
316 if let Running::True(epoch, version) = *running {
317 tracing::info!(
318 "Shutting down consensus for epoch {epoch:?} & protocol version {version:?}"
319 );
320 } else {
321 tracing::warn!("Consensus shutdown was called but consensus is not running");
322 return None;
323 }
324
325 Some(RunningLockGuard {
326 state_guard: running,
327 metrics,
328 start: Instant::now(),
329 epoch: None,
330 protocol_version: None,
331 })
332 }
333}
334
335impl Drop for RunningLockGuard<'_> {
336 fn drop(&mut self) {
337 match *self.state_guard {
338 Running::True(epoch, version) => {
340 tracing::info!(
341 "Consensus shutdown for epoch {epoch:?} & protocol version {version:?} is complete - took {} seconds",
342 self.start.elapsed().as_secs_f64()
343 );
344
345 self.metrics
346 .shutdown_latency
347 .set(self.start.elapsed().as_secs_f64() as i64);
348
349 *self.state_guard = Running::False;
350 }
351 Running::False => {
353 tracing::info!(
354 "Starting up consensus for epoch {} & protocol version {:?} is complete - took {} seconds",
355 self.epoch.unwrap(),
356 self.protocol_version.unwrap(),
357 self.start.elapsed().as_secs_f64()
358 );
359
360 self.metrics
361 .start_latency
362 .set(self.start.elapsed().as_secs_f64() as i64);
363
364 *self.state_guard =
365 Running::True(self.epoch.unwrap(), self.protocol_version.unwrap());
366 }
367 }
368 }
369}