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 multi_leader_rounds: u32,
88 pub open_multi_leader_rounds: bool,
92 pub timeout_config: TimeoutConfig,
94}
95
96impl ChainOwnership {
97 pub fn single_super(owner: AccountOwner) -> Self {
99 ChainOwnership {
100 super_owners: iter::once(owner).collect(),
101 owners: BTreeMap::new(),
102 multi_leader_rounds: 2,
103 open_multi_leader_rounds: false,
104 timeout_config: TimeoutConfig::default(),
105 }
106 }
107
108 pub fn single(owner: AccountOwner) -> Self {
110 ChainOwnership {
111 super_owners: BTreeSet::new(),
112 owners: iter::once((owner, 100)).collect(),
113 multi_leader_rounds: 2,
114 open_multi_leader_rounds: false,
115 timeout_config: TimeoutConfig::default(),
116 }
117 }
118
119 pub fn multiple(
121 owners_and_weights: impl IntoIterator<Item = (AccountOwner, u64)>,
122 multi_leader_rounds: u32,
123 timeout_config: TimeoutConfig,
124 ) -> Self {
125 ChainOwnership {
126 super_owners: BTreeSet::new(),
127 owners: owners_and_weights.into_iter().collect(),
128 multi_leader_rounds,
129 open_multi_leader_rounds: false,
130 timeout_config,
131 }
132 }
133
134 pub fn with_regular_owner(mut self, owner: AccountOwner, weight: u64) -> Self {
136 self.owners.insert(owner, weight);
137 self
138 }
139
140 pub fn is_active(&self) -> bool {
142 !self.super_owners.is_empty()
143 || !self.owners.is_empty()
144 || self.timeout_config.fallback_duration == TimeDelta::ZERO
145 }
146
147 pub fn verify_owner(&self, owner: &AccountOwner) -> bool {
149 self.super_owners.contains(owner) || self.owners.contains_key(owner)
150 }
151
152 pub fn round_timeout(&self, round: Round) -> Option<TimeDelta> {
154 let tc = &self.timeout_config;
155 if round.is_fast() && self.owners.is_empty() {
156 return None; }
158 match round {
159 Round::Fast => tc.fast_round_duration,
160 Round::MultiLeader(r) if r.saturating_add(1) == self.multi_leader_rounds => {
161 Some(tc.base_timeout)
162 }
163 Round::MultiLeader(_) => None,
164 Round::SingleLeader(r) | Round::Validator(r) => {
165 let increment = tc.timeout_increment.saturating_mul(u64::from(r));
166 Some(tc.base_timeout.saturating_add(increment))
167 }
168 }
169 }
170
171 pub fn first_round(&self) -> Round {
173 if !self.super_owners.is_empty() {
174 Round::Fast
175 } else if self.owners.is_empty() {
176 Round::Validator(0)
177 } else if self.multi_leader_rounds > 0 {
178 Round::MultiLeader(0)
179 } else {
180 Round::SingleLeader(0)
181 }
182 }
183
184 pub fn all_owners(&self) -> impl Iterator<Item = &AccountOwner> {
186 self.super_owners.iter().chain(self.owners.keys())
187 }
188
189 pub fn next_round(&self, round: Round) -> Option<Round> {
191 let next_round = match round {
192 Round::Fast if self.multi_leader_rounds == 0 => Round::SingleLeader(0),
193 Round::Fast => Round::MultiLeader(0),
194 Round::MultiLeader(r) => r
195 .checked_add(1)
196 .filter(|r| *r < self.multi_leader_rounds)
197 .map_or(Round::SingleLeader(0), Round::MultiLeader),
198 Round::SingleLeader(r) => r
199 .checked_add(1)
200 .map_or(Round::Validator(0), Round::SingleLeader),
201 Round::Validator(r) => Round::Validator(r.checked_add(1)?),
202 };
203 Some(next_round)
204 }
205
206 pub fn is_super_owner_no_regular_owners(&self, owner: &AccountOwner) -> bool {
208 self.owners.is_empty() && self.super_owners.contains(owner)
209 }
210}
211
212#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
214pub enum CloseChainError {
215 #[error("Unauthorized attempt to close the chain")]
217 NotPermitted,
218}
219
220#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
222pub enum ChangeApplicationPermissionsError {
223 #[error("Unauthorized attempt to change the application permissions")]
225 NotPermitted,
226}
227
228#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
231pub enum AccountPermissionError {
232 #[error("Unauthorized attempt to access account owned by {0}")]
234 NotPermitted(AccountOwner),
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240 use crate::crypto::{Ed25519SecretKey, Secp256k1SecretKey};
241
242 #[test]
243 fn test_ownership_round_timeouts() {
244 let super_pub_key = Ed25519SecretKey::generate().public();
245 let super_owner = AccountOwner::from(super_pub_key);
246 let pub_key = Secp256k1SecretKey::generate().public();
247 let owner = AccountOwner::from(pub_key);
248
249 let ownership = ChainOwnership {
250 super_owners: BTreeSet::from_iter([super_owner]),
251 owners: BTreeMap::from_iter([(owner, 100)]),
252 multi_leader_rounds: 10,
253 open_multi_leader_rounds: false,
254 timeout_config: TimeoutConfig {
255 fast_round_duration: Some(TimeDelta::from_secs(5)),
256 base_timeout: TimeDelta::from_secs(10),
257 timeout_increment: TimeDelta::from_secs(1),
258 fallback_duration: TimeDelta::from_secs(60 * 60),
259 },
260 };
261
262 assert_eq!(
263 ownership.round_timeout(Round::Fast),
264 Some(TimeDelta::from_secs(5))
265 );
266 assert_eq!(ownership.round_timeout(Round::MultiLeader(8)), None);
267 assert_eq!(
268 ownership.round_timeout(Round::MultiLeader(9)),
269 Some(TimeDelta::from_secs(10))
270 );
271 assert_eq!(
272 ownership.round_timeout(Round::SingleLeader(0)),
273 Some(TimeDelta::from_secs(10))
274 );
275 assert_eq!(
276 ownership.round_timeout(Round::SingleLeader(1)),
277 Some(TimeDelta::from_secs(11))
278 );
279 assert_eq!(
280 ownership.round_timeout(Round::SingleLeader(8)),
281 Some(TimeDelta::from_secs(18))
282 );
283 }
284}
285
286doc_scalar!(ChainOwnership, "Represents the owner(s) of a chain");