1use std::{borrow::Cow, collections::BTreeMap, sync::Arc};
6
7use allocative::Allocative;
8use linera_base::{
9 crypto::{AccountPublicKey, ValidatorPublicKey},
10 data_types::Epoch,
11};
12use serde::{Deserialize, Serialize};
13
14use crate::policy::ResourceControlPolicy;
15
16#[derive(Eq, PartialEq, Hash, Clone, Debug, Serialize, Deserialize, Allocative)]
18pub struct ValidatorState {
19 pub network_address: String,
21 pub votes: u64,
23 pub account_public_key: AccountPublicKey,
25}
26
27#[derive(Eq, PartialEq, Hash, Clone, Debug, Default, Allocative)]
29#[cfg_attr(with_graphql, derive(async_graphql::InputObject))]
30pub struct Committee {
31 pub validators: BTreeMap<ValidatorPublicKey, ValidatorState>,
33 total_votes: u64,
35 quorum_threshold: u64,
37 validity_threshold: u64,
40 policy: ResourceControlPolicy,
42}
43
44impl Serialize for Committee {
45 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
46 where
47 S: serde::ser::Serializer,
48 {
49 if serializer.is_human_readable() {
50 CommitteeFull::from(self).serialize(serializer)
51 } else {
52 CommitteeMinimal::from(self).serialize(serializer)
53 }
54 }
55}
56
57impl<'de> Deserialize<'de> for Committee {
58 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
59 where
60 D: serde::de::Deserializer<'de>,
61 {
62 if deserializer.is_human_readable() {
63 let committee_full = CommitteeFull::deserialize(deserializer)?;
64 Committee::try_from(committee_full).map_err(serde::de::Error::custom)
65 } else {
66 let committee_minimal = CommitteeMinimal::deserialize(deserializer)?;
67 Ok(Committee::from(committee_minimal))
68 }
69 }
70}
71
72#[derive(Serialize, Deserialize)]
73#[serde(rename = "Committee")]
74struct CommitteeFull<'a> {
75 validators: Cow<'a, BTreeMap<ValidatorPublicKey, ValidatorState>>,
76 total_votes: u64,
77 quorum_threshold: u64,
78 validity_threshold: u64,
79 policy: Cow<'a, ResourceControlPolicy>,
80}
81
82#[derive(Serialize, Deserialize)]
83#[serde(rename = "Committee")]
84struct CommitteeMinimal<'a> {
85 validators: Cow<'a, BTreeMap<ValidatorPublicKey, ValidatorState>>,
86 policy: Cow<'a, ResourceControlPolicy>,
87}
88
89impl TryFrom<CommitteeFull<'static>> for Committee {
90 type Error = String;
91
92 fn try_from(committee_full: CommitteeFull) -> Result<Committee, Self::Error> {
93 let CommitteeFull {
94 validators,
95 total_votes,
96 quorum_threshold,
97 validity_threshold,
98 policy,
99 } = committee_full;
100 let committee = Committee::new(validators.into_owned(), policy.into_owned());
101 if total_votes != committee.total_votes {
102 Err(format!(
103 "invalid committee: total_votes is {}; should be {}",
104 total_votes, committee.total_votes,
105 ))
106 } else if quorum_threshold != committee.quorum_threshold {
107 Err(format!(
108 "invalid committee: quorum_threshold is {}; should be {}",
109 quorum_threshold, committee.quorum_threshold,
110 ))
111 } else if validity_threshold != committee.validity_threshold {
112 Err(format!(
113 "invalid committee: validity_threshold is {}; should be {}",
114 validity_threshold, committee.validity_threshold,
115 ))
116 } else {
117 Ok(committee)
118 }
119 }
120}
121
122impl<'a> From<&'a Committee> for CommitteeFull<'a> {
123 fn from(committee: &'a Committee) -> CommitteeFull<'a> {
124 let Committee {
125 validators,
126 total_votes,
127 quorum_threshold,
128 validity_threshold,
129 policy,
130 } = committee;
131 CommitteeFull {
132 validators: Cow::Borrowed(validators),
133 total_votes: *total_votes,
134 quorum_threshold: *quorum_threshold,
135 validity_threshold: *validity_threshold,
136 policy: Cow::Borrowed(policy),
137 }
138 }
139}
140
141impl From<CommitteeMinimal<'static>> for Committee {
142 fn from(committee_min: CommitteeMinimal) -> Committee {
143 let CommitteeMinimal { validators, policy } = committee_min;
144 Committee::new(validators.into_owned(), policy.into_owned())
145 }
146}
147
148impl<'a> From<&'a Committee> for CommitteeMinimal<'a> {
149 fn from(committee: &'a Committee) -> CommitteeMinimal<'a> {
150 let Committee {
151 validators,
152 total_votes: _,
153 quorum_threshold: _,
154 validity_threshold: _,
155 policy,
156 } = committee;
157 CommitteeMinimal {
158 validators: Cow::Borrowed(validators),
159 policy: Cow::Borrowed(policy),
160 }
161 }
162}
163
164impl Committee {
165 pub fn new(
166 validators: BTreeMap<ValidatorPublicKey, ValidatorState>,
167 policy: ResourceControlPolicy,
168 ) -> Self {
169 let total_votes = validators.values().fold(0, |sum, state| sum + state.votes);
170 let quorum_threshold = 2 * total_votes / 3 + 1;
175 let validity_threshold = total_votes.div_ceil(3);
176
177 Committee {
178 validators,
179 total_votes,
180 quorum_threshold,
181 validity_threshold,
182 policy,
183 }
184 }
185
186 #[cfg(with_testing)]
187 pub fn make_simple(keys: Vec<(ValidatorPublicKey, AccountPublicKey)>) -> Self {
188 let map = keys
189 .into_iter()
190 .map(|(validator_key, account_key)| {
191 (
192 validator_key,
193 ValidatorState {
194 network_address: "Tcp:localhost:8080".to_string(),
195 votes: 100,
196 account_public_key: account_key,
197 },
198 )
199 })
200 .collect();
201 Committee::new(map, ResourceControlPolicy::default())
202 }
203
204 pub fn weight(&self, author: &ValidatorPublicKey) -> u64 {
205 match self.validators.get(author) {
206 Some(state) => state.votes,
207 None => 0,
208 }
209 }
210
211 pub fn account_keys_and_weights(&self) -> impl Iterator<Item = (AccountPublicKey, u64)> + '_ {
212 self.validators
213 .values()
214 .map(|validator| (validator.account_public_key, validator.votes))
215 }
216
217 pub fn quorum_threshold(&self) -> u64 {
218 self.quorum_threshold
219 }
220
221 pub fn validity_threshold(&self) -> u64 {
222 self.validity_threshold
223 }
224
225 pub fn validators(&self) -> &BTreeMap<ValidatorPublicKey, ValidatorState> {
226 &self.validators
227 }
228
229 pub fn validator_addresses(&self) -> impl Iterator<Item = (ValidatorPublicKey, &str)> {
230 self.validators
231 .iter()
232 .map(|(name, validator)| (*name, &*validator.network_address))
233 }
234
235 pub fn total_votes(&self) -> u64 {
236 self.total_votes
237 }
238
239 pub fn policy(&self) -> &ResourceControlPolicy {
240 &self.policy
241 }
242
243 pub fn policy_mut(&mut self) -> &mut ResourceControlPolicy {
245 &mut self.policy
246 }
247}
248
249#[derive(Clone, Debug, Default)]
257pub struct SharedCommittees {
258 map: Arc<papaya::HashMap<Epoch, Arc<Committee>>>,
259}
260
261impl SharedCommittees {
262 pub fn new() -> Self {
263 Self::default()
264 }
265
266 pub fn get(&self, epoch: Epoch) -> Option<Arc<Committee>> {
268 self.map.pin().get(&epoch).cloned()
269 }
270
271 pub fn insert(&self, epoch: Epoch, committee: Arc<Committee>) -> Arc<Committee> {
275 let pinned = self.map.pin();
276 match pinned.try_insert(epoch, committee) {
277 Ok(inserted) => inserted.clone(),
278 Err(e) => e.current.clone(),
279 }
280 }
281}
282
283#[cfg(test)]
284mod tests {
285 use super::*;
286
287 #[test]
288 fn shared_committees_insert_and_get() {
289 let shared = SharedCommittees::new();
290 assert!(shared.get(Epoch(0)).is_none());
291 let committee = Arc::new(Committee::default());
292 let inserted = shared.insert(Epoch(0), committee.clone());
293 assert!(Arc::ptr_eq(&inserted, &committee));
294 let fetched = shared.get(Epoch(0)).unwrap();
295 assert!(Arc::ptr_eq(&fetched, &committee));
296 }
297
298 #[test]
299 fn shared_committees_insert_is_first_writer_wins() {
300 let shared = SharedCommittees::new();
301 let first = Arc::new(Committee::default());
302 let second = Arc::new(Committee::default());
303 let winner = shared.insert(Epoch(5), first.clone());
304 assert!(Arc::ptr_eq(&winner, &first));
305 let loser = shared.insert(Epoch(5), second.clone());
306 assert!(Arc::ptr_eq(&loser, &first));
307 assert!(!Arc::ptr_eq(&loser, &second));
308 }
309
310 #[test]
311 fn shared_committees_clones_share_storage() {
312 let a = SharedCommittees::new();
313 let b = a.clone();
314 let committee = Arc::new(Committee::default());
315 a.insert(Epoch(1), committee.clone());
316 let fetched = b.get(Epoch(1)).unwrap();
317 assert!(Arc::ptr_eq(&fetched, &committee));
318 }
319}