1use std::{borrow::Cow, collections::BTreeMap, sync::Arc};
6
7use allocative::Allocative;
8use linera_base::{
9 crypto::{AccountPublicKey, CryptoHash, ValidatorPublicKey},
10 data_types::ArithmeticError,
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 Committee::try_from(committee_minimal).map_err(serde::de::Error::custom)
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 .map_err(|e| e.to_string())?;
102 if total_votes != committee.total_votes {
103 Err(format!(
104 "invalid committee: total_votes is {}; should be {}",
105 total_votes, committee.total_votes,
106 ))
107 } else if quorum_threshold != committee.quorum_threshold {
108 Err(format!(
109 "invalid committee: quorum_threshold is {}; should be {}",
110 quorum_threshold, committee.quorum_threshold,
111 ))
112 } else if validity_threshold != committee.validity_threshold {
113 Err(format!(
114 "invalid committee: validity_threshold is {}; should be {}",
115 validity_threshold, committee.validity_threshold,
116 ))
117 } else {
118 Ok(committee)
119 }
120 }
121}
122
123impl<'a> From<&'a Committee> for CommitteeFull<'a> {
124 fn from(committee: &'a Committee) -> CommitteeFull<'a> {
125 let Committee {
126 validators,
127 total_votes,
128 quorum_threshold,
129 validity_threshold,
130 policy,
131 } = committee;
132 CommitteeFull {
133 validators: Cow::Borrowed(validators),
134 total_votes: *total_votes,
135 quorum_threshold: *quorum_threshold,
136 validity_threshold: *validity_threshold,
137 policy: Cow::Borrowed(policy),
138 }
139 }
140}
141
142impl TryFrom<CommitteeMinimal<'static>> for Committee {
143 type Error = ArithmeticError;
144
145 fn try_from(committee_min: CommitteeMinimal) -> Result<Committee, ArithmeticError> {
146 let CommitteeMinimal { validators, policy } = committee_min;
147 Committee::new(validators.into_owned(), policy.into_owned())
148 }
149}
150
151impl<'a> From<&'a Committee> for CommitteeMinimal<'a> {
152 fn from(committee: &'a Committee) -> CommitteeMinimal<'a> {
153 let Committee {
154 validators,
155 total_votes: _,
156 quorum_threshold: _,
157 validity_threshold: _,
158 policy,
159 } = committee;
160 CommitteeMinimal {
161 validators: Cow::Borrowed(validators),
162 policy: Cow::Borrowed(policy),
163 }
164 }
165}
166
167impl Committee {
168 pub fn new(
170 validators: BTreeMap<ValidatorPublicKey, ValidatorState>,
171 policy: ResourceControlPolicy,
172 ) -> Result<Self, ArithmeticError> {
173 let mut total_votes: u64 = 0;
174 for state in validators.values() {
175 total_votes = total_votes
176 .checked_add(state.votes)
177 .ok_or(ArithmeticError::Overflow)?;
178 }
179 let validity_threshold = total_votes.div_ceil(3);
182 let quorum_threshold = total_votes
185 .checked_add(validity_threshold)
186 .ok_or(ArithmeticError::Overflow)?
187 .div_ceil(2);
188
189 Ok(Committee {
190 validators,
191 total_votes,
192 quorum_threshold,
193 validity_threshold,
194 policy,
195 })
196 }
197
198 #[cfg(with_testing)]
200 pub fn make_simple(keys: Vec<(ValidatorPublicKey, AccountPublicKey)>) -> Self {
201 let map = keys
202 .into_iter()
203 .map(|(validator_key, account_key)| {
204 (
205 validator_key,
206 ValidatorState {
207 network_address: "Tcp:localhost:8080".to_string(),
208 votes: 100,
209 account_public_key: account_key,
210 },
211 )
212 })
213 .collect();
214 Committee::new(map, ResourceControlPolicy::default())
215 .expect("test committee votes should not overflow")
216 }
217
218 pub fn weight(&self, author: &ValidatorPublicKey) -> u64 {
220 match self.validators.get(author) {
221 Some(state) => state.votes,
222 None => 0,
223 }
224 }
225
226 pub fn account_keys_and_weights(&self) -> impl Iterator<Item = (AccountPublicKey, u64)> + '_ {
228 self.validators
229 .values()
230 .map(|validator| (validator.account_public_key, validator.votes))
231 }
232
233 pub fn quorum_threshold(&self) -> u64 {
235 self.quorum_threshold
236 }
237
238 pub fn validity_threshold(&self) -> u64 {
240 self.validity_threshold
241 }
242
243 pub fn validators(&self) -> &BTreeMap<ValidatorPublicKey, ValidatorState> {
245 &self.validators
246 }
247
248 pub fn validator_addresses(&self) -> impl Iterator<Item = (ValidatorPublicKey, &str)> {
250 self.validators
251 .iter()
252 .map(|(name, validator)| (*name, &*validator.network_address))
253 }
254
255 pub fn total_votes(&self) -> u64 {
257 self.total_votes
258 }
259
260 pub fn policy(&self) -> &ResourceControlPolicy {
262 &self.policy
263 }
264
265 pub fn policy_mut(&mut self) -> &mut ResourceControlPolicy {
267 &mut self.policy
268 }
269}
270
271#[derive(Clone, Debug, Default)]
278pub struct SharedCommittees {
279 map: Arc<papaya::HashMap<CryptoHash, Arc<Committee>>>,
280}
281
282impl SharedCommittees {
283 pub fn new() -> Self {
285 Self::default()
286 }
287
288 pub fn get(&self, hash: CryptoHash) -> Option<Arc<Committee>> {
290 self.map.pin().get(&hash).cloned()
291 }
292
293 pub fn insert(&self, hash: CryptoHash, committee: Arc<Committee>) -> Arc<Committee> {
297 let pinned = self.map.pin();
298 match pinned.try_insert(hash, committee) {
299 Ok(inserted) => inserted.clone(),
300 Err(e) => e.current.clone(),
301 }
302 }
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308
309 #[test]
310 fn shared_committees_insert_and_get() {
311 let shared = SharedCommittees::new();
312 let hash = CryptoHash::test_hash("c0");
313 assert!(shared.get(hash).is_none());
314 let committee = Arc::new(Committee::default());
315 let inserted = shared.insert(hash, committee.clone());
316 assert!(Arc::ptr_eq(&inserted, &committee));
317 let fetched = shared.get(hash).unwrap();
318 assert!(Arc::ptr_eq(&fetched, &committee));
319 }
320
321 #[test]
322 fn shared_committees_insert_is_first_writer_wins() {
323 let shared = SharedCommittees::new();
324 let hash = CryptoHash::test_hash("c1");
325 let first = Arc::new(Committee::default());
326 let second = Arc::new(Committee::default());
327 let winner = shared.insert(hash, first.clone());
328 assert!(Arc::ptr_eq(&winner, &first));
329 let loser = shared.insert(hash, second.clone());
330 assert!(Arc::ptr_eq(&loser, &first));
331 assert!(!Arc::ptr_eq(&loser, &second));
332 }
333
334 #[test]
335 fn shared_committees_clones_share_storage() {
336 let a = SharedCommittees::new();
337 let b = a.clone();
338 let hash = CryptoHash::test_hash("c2");
339 let committee = Arc::new(Committee::default());
340 a.insert(hash, committee.clone());
341 let fetched = b.get(hash).unwrap();
342 assert!(Arc::ptr_eq(&fetched, &committee));
343 }
344}