1use std::{
8 collections::{BTreeMap, BTreeSet},
9 iter,
10};
11
12use custom_debug_derive::Debug;
13use linera_witty::{WitLoad, WitStore, WitType};
14use serde::{Deserialize, Serialize};
15use thiserror::Error;
16
17use crate::{
18 data_types::{Round, TimeDelta},
19 doc_scalar,
20 identifiers::AccountOwner,
21};
22
23#[derive(PartialEq, Eq, Clone, Hash, Debug, Serialize, Deserialize, WitLoad, WitStore, WitType)]
25pub struct TimeoutConfig {
26 #[debug(skip_if = Option::is_none)]
28 pub fast_round_duration: Option<TimeDelta>,
29 pub base_timeout: TimeDelta,
31 pub timeout_increment: TimeDelta,
33 pub fallback_duration: TimeDelta,
36}
37
38impl Default for TimeoutConfig {
39 fn default() -> Self {
40 Self {
41 fast_round_duration: None,
42 base_timeout: TimeDelta::from_secs(10),
43 timeout_increment: TimeDelta::from_secs(1),
44 fallback_duration: TimeDelta::MAX,
47 }
48 }
49}
50
51#[derive(
53 PartialEq, Eq, Clone, Hash, Debug, Default, Serialize, Deserialize, WitLoad, WitStore, WitType,
54)]
55pub struct ChainOwnership {
56 #[debug(skip_if = BTreeSet::is_empty)]
58 pub super_owners: BTreeSet<AccountOwner>,
59 #[debug(skip_if = BTreeMap::is_empty)]
61 pub owners: BTreeMap<AccountOwner, u64>,
62 pub multi_leader_rounds: u32,
64 pub open_multi_leader_rounds: bool,
68 pub timeout_config: TimeoutConfig,
70}
71
72impl ChainOwnership {
73 pub fn single_super(owner: AccountOwner) -> Self {
75 ChainOwnership {
76 super_owners: iter::once(owner).collect(),
77 owners: BTreeMap::new(),
78 multi_leader_rounds: 2,
79 open_multi_leader_rounds: false,
80 timeout_config: TimeoutConfig::default(),
81 }
82 }
83
84 pub fn single(owner: AccountOwner) -> Self {
86 ChainOwnership {
87 super_owners: BTreeSet::new(),
88 owners: iter::once((owner, 100)).collect(),
89 multi_leader_rounds: 2,
90 open_multi_leader_rounds: false,
91 timeout_config: TimeoutConfig::default(),
92 }
93 }
94
95 pub fn multiple(
97 owners_and_weights: impl IntoIterator<Item = (AccountOwner, u64)>,
98 multi_leader_rounds: u32,
99 timeout_config: TimeoutConfig,
100 ) -> Self {
101 ChainOwnership {
102 super_owners: BTreeSet::new(),
103 owners: owners_and_weights.into_iter().collect(),
104 multi_leader_rounds,
105 open_multi_leader_rounds: false,
106 timeout_config,
107 }
108 }
109
110 pub fn with_regular_owner(mut self, owner: AccountOwner, weight: u64) -> Self {
112 self.owners.insert(owner, weight);
113 self
114 }
115
116 pub fn is_active(&self) -> bool {
118 !self.super_owners.is_empty()
119 || !self.owners.is_empty()
120 || self.timeout_config.fallback_duration == TimeDelta::ZERO
121 }
122
123 pub fn verify_owner(&self, owner: &AccountOwner) -> bool {
125 self.super_owners.contains(owner) || self.owners.contains_key(owner)
126 }
127
128 pub fn round_timeout(&self, round: Round) -> Option<TimeDelta> {
130 let tc = &self.timeout_config;
131 if round.is_fast() && self.owners.is_empty() {
132 return None; }
134 match round {
135 Round::Fast => tc.fast_round_duration,
136 Round::MultiLeader(r) if r.saturating_add(1) == self.multi_leader_rounds => {
137 Some(tc.base_timeout)
138 }
139 Round::MultiLeader(_) => None,
140 Round::SingleLeader(r) | Round::Validator(r) => {
141 let increment = tc.timeout_increment.saturating_mul(u64::from(r));
142 Some(tc.base_timeout.saturating_add(increment))
143 }
144 }
145 }
146
147 pub fn first_round(&self) -> Round {
149 if !self.super_owners.is_empty() {
150 Round::Fast
151 } else if self.owners.is_empty() {
152 Round::Validator(0)
153 } else if self.multi_leader_rounds > 0 {
154 Round::MultiLeader(0)
155 } else {
156 Round::SingleLeader(0)
157 }
158 }
159
160 pub fn all_owners(&self) -> impl Iterator<Item = &AccountOwner> {
162 self.super_owners.iter().chain(self.owners.keys())
163 }
164
165 pub fn next_round(&self, round: Round) -> Option<Round> {
167 let next_round = match round {
168 Round::Fast if self.multi_leader_rounds == 0 => Round::SingleLeader(0),
169 Round::Fast => Round::MultiLeader(0),
170 Round::MultiLeader(r) => r
171 .checked_add(1)
172 .filter(|r| *r < self.multi_leader_rounds)
173 .map_or(Round::SingleLeader(0), Round::MultiLeader),
174 Round::SingleLeader(r) => r
175 .checked_add(1)
176 .map_or(Round::Validator(0), Round::SingleLeader),
177 Round::Validator(r) => Round::Validator(r.checked_add(1)?),
178 };
179 Some(next_round)
180 }
181
182 pub fn is_super_owner_no_regular_owners(&self, owner: &AccountOwner) -> bool {
184 self.owners.is_empty() && self.super_owners.contains(owner)
185 }
186}
187
188#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
190pub enum CloseChainError {
191 #[error("Unauthorized attempt to close the chain")]
193 NotPermitted,
194}
195
196#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
198pub enum ChangeApplicationPermissionsError {
199 #[error("Unauthorized attempt to change the application permissions")]
201 NotPermitted,
202}
203
204#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
207pub enum AccountPermissionError {
208 #[error("Unauthorized attempt to access account owned by {0}")]
210 NotPermitted(AccountOwner),
211}
212
213#[cfg(test)]
214mod tests {
215 use super::*;
216 use crate::crypto::{Ed25519SecretKey, Secp256k1SecretKey};
217
218 #[test]
219 fn test_ownership_round_timeouts() {
220 let super_pub_key = Ed25519SecretKey::generate().public();
221 let super_owner = AccountOwner::from(super_pub_key);
222 let pub_key = Secp256k1SecretKey::generate().public();
223 let owner = AccountOwner::from(pub_key);
224
225 let ownership = ChainOwnership {
226 super_owners: BTreeSet::from_iter([super_owner]),
227 owners: BTreeMap::from_iter([(owner, 100)]),
228 multi_leader_rounds: 10,
229 open_multi_leader_rounds: false,
230 timeout_config: TimeoutConfig {
231 fast_round_duration: Some(TimeDelta::from_secs(5)),
232 base_timeout: TimeDelta::from_secs(10),
233 timeout_increment: TimeDelta::from_secs(1),
234 fallback_duration: TimeDelta::from_secs(60 * 60),
235 },
236 };
237
238 assert_eq!(
239 ownership.round_timeout(Round::Fast),
240 Some(TimeDelta::from_secs(5))
241 );
242 assert_eq!(ownership.round_timeout(Round::MultiLeader(8)), None);
243 assert_eq!(
244 ownership.round_timeout(Round::MultiLeader(9)),
245 Some(TimeDelta::from_secs(10))
246 );
247 assert_eq!(
248 ownership.round_timeout(Round::SingleLeader(0)),
249 Some(TimeDelta::from_secs(10))
250 );
251 assert_eq!(
252 ownership.round_timeout(Round::SingleLeader(1)),
253 Some(TimeDelta::from_secs(11))
254 );
255 assert_eq!(
256 ownership.round_timeout(Round::SingleLeader(8)),
257 Some(TimeDelta::from_secs(18))
258 );
259 }
260}
261
262doc_scalar!(ChainOwnership, "Represents the owner(s) of a chain");