1use 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#[derive(PartialEq, Eq, Clone, Hash, Debug, Serialize, Deserialize, WitLoad, WitStore, WitType)]
22pub struct TimeoutConfig {
23 pub fast_round_duration: Option<TimeDelta>,
25 pub base_timeout: TimeDelta,
27 pub timeout_increment: TimeDelta,
29 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#[derive(
47 PartialEq, Eq, Clone, Hash, Debug, Default, Serialize, Deserialize, WitLoad, WitStore, WitType,
48)]
49pub struct ChainOwnership {
50 pub super_owners: BTreeMap<Owner, PublicKey>,
52 pub owners: BTreeMap<Owner, (PublicKey, u64)>,
54 pub multi_leader_rounds: u32,
56 pub timeout_config: TimeoutConfig,
58}
59
60impl ChainOwnership {
61 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 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 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 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 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 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 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 pub fn all_owners(&self) -> impl Iterator<Item = &Owner> {
146 self.super_owners.keys().chain(self.owners.keys())
147 }
148
149 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 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#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
176pub enum CloseChainError {
177 #[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");