consensus_core/
context.rs

1// Copyright (c) Mysten Labs, Inc.
2// Modifications Copyright (c) 2024 IOTA Stiftung
3// SPDX-License-Identifier: Apache-2.0
4
5use std::{sync::Arc, time::SystemTime};
6
7use consensus_config::{AuthorityIndex, Committee, Parameters};
8#[cfg(test)]
9use consensus_config::{NetworkKeyPair, ProtocolKeyPair};
10use iota_protocol_config::ProtocolConfig;
11#[cfg(test)]
12use tempfile::TempDir;
13use tokio::time::Instant;
14
15#[cfg(test)]
16use crate::metrics::test_metrics;
17use crate::{
18    block::BlockTimestampMs, metrics::Metrics, scoring_metrics_store::MysticetiScoringMetricsStore,
19};
20/// Context contains per-epoch configuration and metrics shared by all
21/// components of this authority.
22#[derive(Clone)]
23pub(crate) struct Context {
24    /// Timestamp of the start of the current epoch.
25    pub epoch_start_timestamp_ms: u64,
26    /// Index of this authority in the committee.
27    pub own_index: AuthorityIndex,
28    /// Committee of the current epoch.
29    pub committee: Committee,
30    /// Parameters of this authority.
31    pub parameters: Parameters,
32    /// Protocol configuration of current epoch.
33    pub protocol_config: ProtocolConfig,
34    /// Metrics of this authority.
35    pub metrics: Arc<Metrics>,
36    /// Store for scoring metrics collected by this authority.
37    pub(crate) scoring_metrics_store: Arc<MysticetiScoringMetricsStore>,
38    /// Access to local clock
39    pub clock: Arc<Clock>,
40}
41
42impl Context {
43    pub(crate) fn new(
44        epoch_start_timestamp_ms: u64,
45        own_index: AuthorityIndex,
46        committee: Committee,
47        parameters: Parameters,
48        protocol_config: ProtocolConfig,
49        metrics: Arc<Metrics>,
50        scoring_metrics_store: Arc<MysticetiScoringMetricsStore>,
51
52        clock: Arc<Clock>,
53    ) -> Self {
54        Self {
55            epoch_start_timestamp_ms,
56            own_index,
57            committee,
58            parameters,
59            protocol_config,
60            metrics,
61            scoring_metrics_store,
62            clock,
63        }
64    }
65
66    /// Create a test context with a committee of given size and even stake
67    #[cfg(test)]
68    pub(crate) fn new_for_test(
69        committee_size: usize,
70    ) -> (Self, Vec<(NetworkKeyPair, ProtocolKeyPair)>) {
71        use iota_common::scoring_metrics::{ScoringMetricsV1, VersionedScoringMetrics};
72
73        let (committee, keypairs) =
74            consensus_config::local_committee_and_keys(0, vec![1; committee_size]);
75        let metrics = test_metrics();
76        let temp_dir = TempDir::new().unwrap();
77        let clock = Arc::new(Clock::default());
78        let current_local_metrics_count = Arc::new(VersionedScoringMetrics::V1(
79            ScoringMetricsV1::new(committee_size),
80        ));
81        let scoring_metrics_store = Arc::new(MysticetiScoringMetricsStore::new(
82            committee_size,
83            current_local_metrics_count,
84            &ProtocolConfig::get_for_max_version_UNSAFE(),
85        ));
86        let context = Context::new(
87            0,
88            AuthorityIndex::new_for_test(0),
89            committee,
90            Parameters {
91                db_path: temp_dir.keep(),
92                ..Default::default()
93            },
94            ProtocolConfig::get_for_max_version_UNSAFE(),
95            metrics,
96            scoring_metrics_store,
97            clock,
98        );
99        (context, keypairs)
100    }
101
102    #[cfg(test)]
103    pub(crate) fn with_epoch_start_timestamp_ms(mut self, epoch_start_timestamp_ms: u64) -> Self {
104        self.epoch_start_timestamp_ms = epoch_start_timestamp_ms;
105        self
106    }
107
108    #[cfg(test)]
109    pub(crate) fn with_authority_index(mut self, authority: AuthorityIndex) -> Self {
110        self.own_index = authority;
111        self
112    }
113
114    #[cfg(test)]
115    pub(crate) fn with_committee(mut self, committee: Committee) -> Self {
116        self.committee = committee;
117        self
118    }
119
120    #[cfg(test)]
121    pub(crate) fn with_parameters(mut self, parameters: Parameters) -> Self {
122        self.parameters = parameters;
123        self
124    }
125}
126
127/// A clock that allows to derive the current UNIX system timestamp while
128/// guaranteeing that timestamp will be monotonically incremented, tolerating
129/// ntp and system clock changes and corrections. Explicitly avoid to make
130/// `[Clock]` cloneable to ensure that a single instance is shared behind an
131/// `[Arc]` wherever is needed in order to make sure that consecutive calls to
132/// receive the system timestamp will remain monotonically increasing.
133pub struct Clock {
134    initial_instant: Instant,
135    initial_system_time: SystemTime,
136    // `clock_drift` should be used only for testing
137    #[cfg(any(test, msim))]
138    clock_drift: BlockTimestampMs,
139}
140
141impl Default for Clock {
142    fn default() -> Self {
143        Self {
144            initial_instant: Instant::now(),
145            initial_system_time: SystemTime::now(),
146            #[cfg(any(test, msim))]
147            clock_drift: 0,
148        }
149    }
150}
151
152impl Clock {
153    #[cfg(any(test, msim))]
154    pub fn new_for_test(clock_drift: BlockTimestampMs) -> Self {
155        Self {
156            initial_instant: Instant::now(),
157            initial_system_time: SystemTime::now(),
158            clock_drift,
159        }
160    }
161
162    // Returns the current time expressed as UNIX timestamp in milliseconds.
163    // Calculated with Tokio Instant to ensure monotonicity,
164    // and to allow testing with tokio clock.
165    pub(crate) fn timestamp_utc_ms(&self) -> BlockTimestampMs {
166        let now: Instant = Instant::now();
167        let monotonic_system_time = self
168            .initial_system_time
169            .checked_add(
170                now.checked_duration_since(self.initial_instant)
171                    .unwrap_or_else(|| {
172                        panic!(
173                            "current instant ({:?}) < initial instant ({:?})",
174                            now, self.initial_instant
175                        )
176                    }),
177            )
178            .expect("Computing system time should not overflow");
179        let timestamp = monotonic_system_time
180            .duration_since(SystemTime::UNIX_EPOCH)
181            .unwrap_or_else(|_| {
182                panic!(
183                    "system time ({:?}) < UNIX_EPOCH ({:?})",
184                    monotonic_system_time,
185                    SystemTime::UNIX_EPOCH,
186                )
187            })
188            .as_millis() as BlockTimestampMs;
189
190        // Apply clock drift only in test/msim environments to simulate clock skew
191        // between nodes. In production builds, clock_drift field doesn't exist
192        // and this returns timestamp directly.
193        #[cfg(any(test, msim))]
194        {
195            timestamp + self.clock_drift
196        }
197        #[cfg(not(any(test, msim)))]
198        {
199            timestamp
200        }
201    }
202}