1use std::{
8 collections::{BTreeMap, BTreeSet},
9 iter,
10};
11
12use allocative::Allocative;
13use custom_debug_derive::Debug;
14use linera_witty::{WitLoad, WitStore, WitType};
15use serde::{Deserialize, Serialize};
16use thiserror::Error;
17
18use crate::{
19 data_types::{Round, TimeDelta},
20 doc_scalar,
21 identifiers::AccountOwner,
22};
23
24#[derive(
26 PartialEq,
27 Eq,
28 Clone,
29 Hash,
30 Debug,
31 Serialize,
32 Deserialize,
33 WitLoad,
34 WitStore,
35 WitType,
36 Allocative,
37)]
38pub struct TimeoutConfig {
39 #[debug(skip_if = Option::is_none)]
41 pub fast_round_duration: Option<TimeDelta>,
42 pub base_timeout: TimeDelta,
44 pub timeout_increment: TimeDelta,
46 pub fallback_duration: TimeDelta,
49}
50
51impl Default for TimeoutConfig {
52 fn default() -> Self {
53 Self {
54 fast_round_duration: None,
55 base_timeout: TimeDelta::from_secs(10),
56 timeout_increment: TimeDelta::from_secs(1),
57 fallback_duration: TimeDelta::MAX,
60 }
61 }
62}
63
64#[derive(
66 PartialEq,
67 Eq,
68 Clone,
69 Hash,
70 Debug,
71 Default,
72 Serialize,
73 Deserialize,
74 WitLoad,
75 WitStore,
76 WitType,
77 Allocative,
78)]
79pub struct ChainOwnership {
80 #[debug(skip_if = BTreeSet::is_empty)]
82 pub super_owners: BTreeSet<AccountOwner>,
83 #[debug(skip_if = BTreeMap::is_empty)]
85 pub owners: BTreeMap<AccountOwner, u64>,
86 pub first_leader: Option<AccountOwner>,
88 pub multi_leader_rounds: u32,
90 pub open_multi_leader_rounds: bool,
94 pub timeout_config: TimeoutConfig,
96}
97
98impl ChainOwnership {
99 pub fn single_super(owner: AccountOwner) -> Self {
101 ChainOwnership {
102 super_owners: iter::once(owner).collect(),
103 owners: BTreeMap::new(),
104 first_leader: None,
105 multi_leader_rounds: 2,
106 open_multi_leader_rounds: false,
107 timeout_config: TimeoutConfig::default(),
108 }
109 }
110
111 pub fn single(owner: AccountOwner) -> Self {
113 ChainOwnership {
114 super_owners: BTreeSet::new(),
115 owners: iter::once((owner, 100)).collect(),
116 first_leader: None,
117 multi_leader_rounds: 2,
118 open_multi_leader_rounds: false,
119 timeout_config: TimeoutConfig::default(),
120 }
121 }
122
123 pub fn multiple(
125 owners_and_weights: impl IntoIterator<Item = (AccountOwner, u64)>,
126 multi_leader_rounds: u32,
127 timeout_config: TimeoutConfig,
128 ) -> Self {
129 ChainOwnership {
130 super_owners: BTreeSet::new(),
131 owners: owners_and_weights.into_iter().collect(),
132 first_leader: None,
133 multi_leader_rounds,
134 open_multi_leader_rounds: false,
135 timeout_config,
136 }
137 }
138
139 pub fn with_regular_owner(mut self, owner: AccountOwner, weight: u64) -> Self {
141 self.owners.insert(owner, weight);
142 self
143 }
144
145 pub fn with_first_leader(mut self, owner: AccountOwner) -> Self {
147 self.first_leader = Some(owner);
148 self
149 }
150
151 pub fn is_active(&self) -> bool {
153 !self.super_owners.is_empty()
154 || !self.owners.is_empty()
155 || self.timeout_config.fallback_duration == TimeDelta::ZERO
156 }
157
158 pub fn is_owner(&self, owner: &AccountOwner) -> bool {
160 self.super_owners.contains(owner)
161 || self.owners.contains_key(owner)
162 || self.first_leader.as_ref().is_some_and(|fl| fl == owner)
163 }
164
165 pub fn is_multi_leader_owner(&self, owner: &AccountOwner) -> bool {
167 self.open_multi_leader_rounds
168 || self.owners.contains_key(owner)
169 || self.super_owners.contains(owner)
170 }
171
172 pub fn round_timeout(&self, round: Round) -> Option<TimeDelta> {
174 let tc = &self.timeout_config;
175 if round.is_fast() && self.owners.is_empty() {
176 return None; }
178 match round {
179 Round::Fast => tc.fast_round_duration,
180 Round::MultiLeader(r) if r.saturating_add(1) == self.multi_leader_rounds => {
181 Some(tc.base_timeout)
182 }
183 Round::MultiLeader(_) => None,
184 Round::SingleLeader(r) | Round::Validator(r) => {
185 let increment = tc.timeout_increment.saturating_mul(u64::from(r));
186 Some(tc.base_timeout.saturating_add(increment))
187 }
188 }
189 }
190
191 pub fn first_round(&self) -> Round {
193 if !self.super_owners.is_empty() {
194 Round::Fast
195 } else if self.owners.is_empty() {
196 Round::Validator(0)
197 } else if self.multi_leader_rounds > 0 {
198 Round::MultiLeader(0)
199 } else {
200 Round::SingleLeader(0)
201 }
202 }
203
204 pub fn all_owners(&self) -> impl Iterator<Item = &AccountOwner> {
206 self.super_owners.iter().chain(self.owners.keys())
207 }
208
209 pub fn has_fallback(&self) -> bool {
212 self.timeout_config.fallback_duration < TimeDelta::MAX
213 }
214
215 pub fn next_round(&self, round: Round) -> Option<Round> {
217 let next_round = match round {
218 Round::Fast if self.multi_leader_rounds == 0 => Round::SingleLeader(0),
219 Round::Fast => Round::MultiLeader(0),
220 Round::MultiLeader(r) => r
221 .checked_add(1)
222 .filter(|r| *r < self.multi_leader_rounds)
223 .map_or(Round::SingleLeader(0), Round::MultiLeader),
224 Round::SingleLeader(r) => r
225 .checked_add(1)
226 .map_or(Round::Validator(0), Round::SingleLeader),
227 Round::Validator(r) => Round::Validator(r.checked_add(1)?),
228 };
229 Some(next_round)
230 }
231
232 pub fn is_super_owner_no_regular_owners(&self, owner: &AccountOwner) -> bool {
234 self.owners.is_empty() && self.super_owners.contains(owner)
235 }
236}
237
238#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
240pub enum CloseChainError {
241 #[error("Unauthorized attempt to close the chain")]
243 NotPermitted,
244}
245
246#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
248pub enum ChangeApplicationPermissionsError {
249 #[error("Unauthorized attempt to change the application permissions")]
251 NotPermitted,
252}
253
254#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
257pub enum AccountPermissionError {
258 #[error("Unauthorized attempt to access account owned by {0}")]
260 NotPermitted(AccountOwner),
261}
262
263#[cfg(test)]
264mod tests {
265 use super::*;
266 use crate::crypto::{Ed25519SecretKey, Secp256k1SecretKey};
267
268 #[test]
269 fn test_ownership_round_timeouts() {
270 let super_pub_key = Ed25519SecretKey::generate().public();
271 let super_owner = AccountOwner::from(super_pub_key);
272 let pub_key = Secp256k1SecretKey::generate().public();
273 let owner = AccountOwner::from(pub_key);
274
275 let ownership = ChainOwnership {
276 super_owners: BTreeSet::from_iter([super_owner]),
277 owners: BTreeMap::from_iter([(owner, 100)]),
278 first_leader: Some(owner),
279 multi_leader_rounds: 10,
280 open_multi_leader_rounds: false,
281 timeout_config: TimeoutConfig {
282 fast_round_duration: Some(TimeDelta::from_secs(5)),
283 base_timeout: TimeDelta::from_secs(10),
284 timeout_increment: TimeDelta::from_secs(1),
285 fallback_duration: TimeDelta::from_secs(60 * 60),
286 },
287 };
288
289 assert_eq!(
290 ownership.round_timeout(Round::Fast),
291 Some(TimeDelta::from_secs(5))
292 );
293 assert_eq!(ownership.round_timeout(Round::MultiLeader(8)), None);
294 assert_eq!(
295 ownership.round_timeout(Round::MultiLeader(9)),
296 Some(TimeDelta::from_secs(10))
297 );
298 assert_eq!(
299 ownership.round_timeout(Round::SingleLeader(0)),
300 Some(TimeDelta::from_secs(10))
301 );
302 assert_eq!(
303 ownership.round_timeout(Round::SingleLeader(1)),
304 Some(TimeDelta::from_secs(11))
305 );
306 assert_eq!(
307 ownership.round_timeout(Round::SingleLeader(8)),
308 Some(TimeDelta::from_secs(18))
309 );
310 }
311}
312
313doc_scalar!(ChainOwnership, "Represents the owner(s) of a chain");