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