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::from_secs(60 * 60 * 24),
45 }
46 }
47}
48
49#[derive(
51 PartialEq, Eq, Clone, Hash, Debug, Default, Serialize, Deserialize, WitLoad, WitStore, WitType,
52)]
53pub struct ChainOwnership {
54 #[debug(skip_if = BTreeSet::is_empty)]
56 pub super_owners: BTreeSet<AccountOwner>,
57 #[debug(skip_if = BTreeMap::is_empty)]
59 pub owners: BTreeMap<AccountOwner, u64>,
60 pub multi_leader_rounds: u32,
62 pub open_multi_leader_rounds: bool,
66 pub timeout_config: TimeoutConfig,
68}
69
70impl ChainOwnership {
71 pub fn single_super(owner: AccountOwner) -> Self {
73 ChainOwnership {
74 super_owners: iter::once(owner).collect(),
75 owners: BTreeMap::new(),
76 multi_leader_rounds: 2,
77 open_multi_leader_rounds: false,
78 timeout_config: TimeoutConfig::default(),
79 }
80 }
81
82 pub fn single(owner: AccountOwner) -> Self {
84 ChainOwnership {
85 super_owners: BTreeSet::new(),
86 owners: iter::once((owner, 100)).collect(),
87 multi_leader_rounds: 2,
88 open_multi_leader_rounds: false,
89 timeout_config: TimeoutConfig::default(),
90 }
91 }
92
93 pub fn multiple(
95 owners_and_weights: impl IntoIterator<Item = (AccountOwner, u64)>,
96 multi_leader_rounds: u32,
97 timeout_config: TimeoutConfig,
98 ) -> Self {
99 ChainOwnership {
100 super_owners: BTreeSet::new(),
101 owners: owners_and_weights.into_iter().collect(),
102 multi_leader_rounds,
103 open_multi_leader_rounds: false,
104 timeout_config,
105 }
106 }
107
108 pub fn with_regular_owner(mut self, owner: AccountOwner, weight: u64) -> Self {
110 self.owners.insert(owner, weight);
111 self
112 }
113
114 pub fn is_active(&self) -> bool {
116 !self.super_owners.is_empty()
117 || !self.owners.is_empty()
118 || self.timeout_config.fallback_duration == TimeDelta::ZERO
119 }
120
121 pub fn verify_owner(&self, owner: &AccountOwner) -> bool {
123 self.super_owners.contains(owner) || self.owners.contains_key(owner)
124 }
125
126 pub fn round_timeout(&self, round: Round) -> Option<TimeDelta> {
128 let tc = &self.timeout_config;
129 match round {
130 Round::Fast => tc.fast_round_duration,
131 Round::MultiLeader(r) if r.saturating_add(1) == self.multi_leader_rounds => {
132 Some(tc.base_timeout)
133 }
134 Round::MultiLeader(_) => None,
135 Round::SingleLeader(r) => {
136 let increment = tc.timeout_increment.saturating_mul(u64::from(r));
137 Some(tc.base_timeout.saturating_add(increment))
138 }
139 Round::Validator(r) => {
140 let increment = tc.timeout_increment.saturating_mul(u64::from(r));
141 Some(tc.base_timeout.saturating_add(increment))
142 }
143 }
144 }
145
146 pub fn first_round(&self) -> Round {
148 if !self.super_owners.is_empty() {
149 Round::Fast
150 } else if self.owners.is_empty() {
151 Round::Validator(0)
152 } else if self.multi_leader_rounds > 0 {
153 Round::MultiLeader(0)
154 } else {
155 Round::SingleLeader(0)
156 }
157 }
158
159 pub fn all_owners(&self) -> impl Iterator<Item = &AccountOwner> {
161 self.super_owners.iter().chain(self.owners.keys())
162 }
163
164 pub fn next_round(&self, round: Round) -> Option<Round> {
166 let next_round = match round {
167 Round::Fast if self.multi_leader_rounds == 0 => Round::SingleLeader(0),
168 Round::Fast => Round::MultiLeader(0),
169 Round::MultiLeader(r) => r
170 .checked_add(1)
171 .filter(|r| *r < self.multi_leader_rounds)
172 .map_or(Round::SingleLeader(0), Round::MultiLeader),
173 Round::SingleLeader(r) => r
174 .checked_add(1)
175 .map_or(Round::Validator(0), Round::SingleLeader),
176 Round::Validator(r) => Round::Validator(r.checked_add(1)?),
177 };
178 Some(next_round)
179 }
180}
181
182#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
184pub enum CloseChainError {
185 #[error("Unauthorized attempt to close the chain")]
187 NotPermitted,
188}
189
190#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
192pub enum ChangeApplicationPermissionsError {
193 #[error("Unauthorized attempt to change the application permissions")]
195 NotPermitted,
196}
197
198#[derive(Clone, Copy, Debug, Error, WitStore, WitType)]
201pub enum AccountPermissionError {
202 #[error("Unauthorized attempt to access account owned by {0}")]
204 NotPermitted(AccountOwner),
205}
206
207#[cfg(test)]
208mod tests {
209 use super::*;
210 use crate::crypto::{Ed25519SecretKey, Secp256k1SecretKey};
211
212 #[test]
213 fn test_ownership_round_timeouts() {
214 let super_pub_key = Ed25519SecretKey::generate().public();
215 let super_owner = AccountOwner::from(super_pub_key);
216 let pub_key = Secp256k1SecretKey::generate().public();
217 let owner = AccountOwner::from(pub_key);
218
219 let ownership = ChainOwnership {
220 super_owners: BTreeSet::from_iter([super_owner]),
221 owners: BTreeMap::from_iter([(owner, 100)]),
222 multi_leader_rounds: 10,
223 open_multi_leader_rounds: false,
224 timeout_config: TimeoutConfig {
225 fast_round_duration: Some(TimeDelta::from_secs(5)),
226 base_timeout: TimeDelta::from_secs(10),
227 timeout_increment: TimeDelta::from_secs(1),
228 fallback_duration: TimeDelta::from_secs(60 * 60),
229 },
230 };
231
232 assert_eq!(
233 ownership.round_timeout(Round::Fast),
234 Some(TimeDelta::from_secs(5))
235 );
236 assert_eq!(ownership.round_timeout(Round::MultiLeader(8)), None);
237 assert_eq!(
238 ownership.round_timeout(Round::MultiLeader(9)),
239 Some(TimeDelta::from_secs(10))
240 );
241 assert_eq!(
242 ownership.round_timeout(Round::SingleLeader(0)),
243 Some(TimeDelta::from_secs(10))
244 );
245 assert_eq!(
246 ownership.round_timeout(Round::SingleLeader(1)),
247 Some(TimeDelta::from_secs(11))
248 );
249 assert_eq!(
250 ownership.round_timeout(Round::SingleLeader(8)),
251 Some(TimeDelta::from_secs(18))
252 );
253 }
254}
255
256doc_scalar!(ChainOwnership, "Represents the owner(s) of a chain");