linera_base/
ownership.rs

1// Copyright (c) Zefchain Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Structures defining the set of owners and super owners, as well as the consensus
5//! round types and timeouts for chains.
6
7use std::{collections::BTreeMap, iter};
8
9use linera_witty::{WitLoad, WitStore, WitType};
10use serde::{Deserialize, Serialize};
11use thiserror::Error;
12
13use crate::{
14    crypto::PublicKey,
15    data_types::{Round, TimeDelta},
16    doc_scalar,
17    identifiers::Owner,
18};
19
20/// The timeout configuration: how long fast, multi-leader and single-leader rounds last.
21#[derive(PartialEq, Eq, Clone, Hash, Debug, Serialize, Deserialize, WitLoad, WitStore, WitType)]
22pub struct TimeoutConfig {
23    /// The duration of the fast round.
24    pub fast_round_duration: Option<TimeDelta>,
25    /// The duration of the first single-leader and all multi-leader rounds.
26    pub base_timeout: TimeDelta,
27    /// The duration by which the timeout increases after each single-leader round.
28    pub timeout_increment: TimeDelta,
29    /// The age of an incoming tracked or protected message after which the validators start
30    /// transitioning the chain to fallback mode.
31    pub fallback_duration: TimeDelta,
32}
33
34impl Default for TimeoutConfig {
35    fn default() -> Self {
36        Self {
37            fast_round_duration: None,
38            base_timeout: TimeDelta::from_secs(10),
39            timeout_increment: TimeDelta::from_secs(1),
40            fallback_duration: TimeDelta::from_secs(60 * 60 * 24),
41        }
42    }
43}
44
45/// Represents the owner(s) of a chain.
46#[derive(
47    PartialEq, Eq, Clone, Hash, Debug, Default, Serialize, Deserialize, WitLoad, WitStore, WitType,
48)]
49pub struct ChainOwnership {
50    /// Super owners can propose fast blocks in the first round, and regular blocks in any round.
51    pub super_owners: BTreeMap<Owner, PublicKey>,
52    /// The regular owners, with their weights that determine how often they are round leader.
53    pub owners: BTreeMap<Owner, (PublicKey, u64)>,
54    /// The number of initial rounds after 0 in which all owners are allowed to propose blocks.
55    pub multi_leader_rounds: u32,
56    /// The timeout configuration: how long fast, multi-leader and single-leader rounds last.
57    pub timeout_config: TimeoutConfig,
58}
59
60impl ChainOwnership {
61    /// Creates a `ChainOwnership` with a single super owner.
62    pub fn single(public_key: PublicKey) -> Self {
63        ChainOwnership {
64            super_owners: iter::once((Owner::from(public_key), public_key)).collect(),
65            owners: BTreeMap::new(),
66            multi_leader_rounds: 2,
67            timeout_config: TimeoutConfig::default(),
68        }
69    }
70
71    /// Creates a `ChainOwnership` with the specified regular owners.
72    pub fn multiple(
73        keys_and_weights: impl IntoIterator<Item = (PublicKey, u64)>,
74        multi_leader_rounds: u32,
75        timeout_config: TimeoutConfig,
76    ) -> Self {
77        ChainOwnership {
78            super_owners: BTreeMap::new(),
79            owners: keys_and_weights
80                .into_iter()
81                .map(|(public_key, weight)| (Owner::from(public_key), (public_key, weight)))
82                .collect(),
83            multi_leader_rounds,
84            timeout_config,
85        }
86    }
87
88    /// Adds a regular owner.
89    pub fn with_regular_owner(mut self, public_key: PublicKey, weight: u64) -> Self {
90        self.owners
91            .insert(Owner::from(public_key), (public_key, weight));
92        self
93    }
94
95    /// Returns whether there are any owners or super owners or it is a public chain.
96    pub fn is_active(&self) -> bool {
97        !self.super_owners.is_empty()
98            || !self.owners.is_empty()
99            || self.timeout_config.fallback_duration == TimeDelta::ZERO
100    }
101
102    /// Returns the given owner's public key, if they are an owner or super owner.
103    pub fn verify_owner(&self, owner: &Owner) -> Option<PublicKey> {
104        if let Some(public_key) = self.super_owners.get(owner) {
105            Some(*public_key)
106        } else {
107            self.owners.get(owner).map(|(public_key, _)| *public_key)
108        }
109    }
110
111    /// Returns the duration of the given round.
112    pub fn round_timeout(&self, round: Round) -> Option<TimeDelta> {
113        let tc = &self.timeout_config;
114        match round {
115            Round::Fast => tc.fast_round_duration,
116            Round::MultiLeader(r) if r.saturating_add(1) == self.multi_leader_rounds => {
117                Some(tc.base_timeout)
118            }
119            Round::MultiLeader(_) => None,
120            Round::SingleLeader(r) => {
121                let increment = tc.timeout_increment.saturating_mul(u64::from(r));
122                Some(tc.base_timeout.saturating_add(increment))
123            }
124            Round::Validator(r) => {
125                let increment = tc.timeout_increment.saturating_mul(u64::from(r));
126                Some(tc.base_timeout.saturating_add(increment))
127            }
128        }
129    }
130
131    /// Returns the first consensus round for this configuration.
132    pub fn first_round(&self) -> Round {
133        if !self.super_owners.is_empty() {
134            Round::Fast
135        } else if self.owners.is_empty() {
136            Round::Validator(0)
137        } else if self.multi_leader_rounds > 0 {
138            Round::MultiLeader(0)
139        } else {
140            Round::SingleLeader(0)
141        }
142    }
143
144    /// Returns an iterator over all super owners, followed by all owners.
145    pub fn all_owners(&self) -> impl Iterator<Item = &Owner> {
146        self.super_owners.keys().chain(self.owners.keys())
147    }
148
149    /// Returns an iterator over all super owners' keys, followed by all owners'.
150    pub fn all_public_keys(&self) -> impl Iterator<Item = &PublicKey> {
151        self.super_owners
152            .values()
153            .chain(self.owners.values().map(|(public_key, _)| public_key))
154    }
155
156    /// Returns the round following the specified one, if any.
157    pub fn next_round(&self, round: Round) -> Option<Round> {
158        let next_round = match round {
159            Round::Fast if self.multi_leader_rounds == 0 => Round::SingleLeader(0),
160            Round::Fast => Round::MultiLeader(0),
161            Round::MultiLeader(r) => r
162                .checked_add(1)
163                .filter(|r| *r < self.multi_leader_rounds)
164                .map_or(Round::SingleLeader(0), Round::MultiLeader),
165            Round::SingleLeader(r) => r
166                .checked_add(1)
167                .map_or(Round::Validator(0), Round::SingleLeader),
168            Round::Validator(r) => Round::Validator(r.checked_add(1)?),
169        };
170        Some(next_round)
171    }
172}
173
174/// Errors that can happen when attempting to close a chain.
175#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
176pub enum CloseChainError {
177    /// Authenticated signer wasn't allowed to close the chain.
178    #[error("Unauthorized attempt to close the chain")]
179    NotPermitted,
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_ownership_round_timeouts() {
188        use crate::crypto::KeyPair;
189
190        let super_pub_key = KeyPair::generate().public();
191        let super_owner = Owner::from(super_pub_key);
192        let pub_key = KeyPair::generate().public();
193        let owner = Owner::from(pub_key);
194
195        let ownership = ChainOwnership {
196            super_owners: BTreeMap::from_iter([(super_owner, super_pub_key)]),
197            owners: BTreeMap::from_iter([(owner, (pub_key, 100))]),
198            multi_leader_rounds: 10,
199            timeout_config: TimeoutConfig {
200                fast_round_duration: Some(TimeDelta::from_secs(5)),
201                base_timeout: TimeDelta::from_secs(10),
202                timeout_increment: TimeDelta::from_secs(1),
203                fallback_duration: TimeDelta::from_secs(60 * 60),
204            },
205        };
206
207        assert_eq!(
208            ownership.round_timeout(Round::Fast),
209            Some(TimeDelta::from_secs(5))
210        );
211        assert_eq!(ownership.round_timeout(Round::MultiLeader(8)), None);
212        assert_eq!(
213            ownership.round_timeout(Round::MultiLeader(9)),
214            Some(TimeDelta::from_secs(10))
215        );
216        assert_eq!(
217            ownership.round_timeout(Round::SingleLeader(0)),
218            Some(TimeDelta::from_secs(10))
219        );
220        assert_eq!(
221            ownership.round_timeout(Round::SingleLeader(1)),
222            Some(TimeDelta::from_secs(11))
223        );
224        assert_eq!(
225            ownership.round_timeout(Round::SingleLeader(8)),
226            Some(TimeDelta::from_secs(18))
227        );
228    }
229}
230
231doc_scalar!(ChainOwnership, "Represents the owner(s) of a chain");