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(
167 validators: BTreeMap<ValidatorPublicKey, ValidatorState>,
168 policy: ResourceControlPolicy,
169 ) -> Self {
170 let total_votes = validators.values().fold(0, |sum, state| sum + state.votes);
171 let quorum_threshold = 2 * total_votes / 3 + 1;
176 let validity_threshold = total_votes.div_ceil(3);
177
178 Committee {
179 validators,
180 total_votes,
181 quorum_threshold,
182 validity_threshold,
183 policy,
184 }
185 }
186
187 #[cfg(with_testing)]
189 pub fn make_simple(keys: Vec<(ValidatorPublicKey, AccountPublicKey)>) -> Self {
190 let map = keys
191 .into_iter()
192 .map(|(validator_key, account_key)| {
193 (
194 validator_key,
195 ValidatorState {
196 network_address: "Tcp:localhost:8080".to_string(),
197 votes: 100,
198 account_public_key: account_key,
199 },
200 )
201 })
202 .collect();
203 Committee::new(map, ResourceControlPolicy::default())
204 }
205
206 pub fn weight(&self, author: &ValidatorPublicKey) -> u64 {
208 match self.validators.get(author) {
209 Some(state) => state.votes,
210 None => 0,
211 }
212 }
213
214 pub fn account_keys_and_weights(&self) -> impl Iterator<Item = (AccountPublicKey, u64)> + '_ {
216 self.validators
217 .values()
218 .map(|validator| (validator.account_public_key, validator.votes))
219 }
220
221 pub fn quorum_threshold(&self) -> u64 {
223 self.quorum_threshold
224 }
225
226 pub fn validity_threshold(&self) -> u64 {
228 self.validity_threshold
229 }
230
231 pub fn validators(&self) -> &BTreeMap<ValidatorPublicKey, ValidatorState> {
233 &self.validators
234 }
235
236 pub fn validator_addresses(&self) -> impl Iterator<Item = (ValidatorPublicKey, &str)> {
238 self.validators
239 .iter()
240 .map(|(name, validator)| (*name, &*validator.network_address))
241 }
242
243 pub fn total_votes(&self) -> u64 {
245 self.total_votes
246 }
247
248 pub fn policy(&self) -> &ResourceControlPolicy {
250 &self.policy
251 }
252
253 pub fn policy_mut(&mut self) -> &mut ResourceControlPolicy {
255 &mut self.policy
256 }
257}
258
259#[derive(Clone, Debug, Default)]
267pub struct SharedCommittees {
268 map: Arc<papaya::HashMap<Epoch, Arc<Committee>>>,
269}
270
271impl SharedCommittees {
272 pub fn new() -> Self {
274 Self::default()
275 }
276
277 pub fn get(&self, epoch: Epoch) -> Option<Arc<Committee>> {
279 self.map.pin().get(&epoch).cloned()
280 }
281
282 pub fn insert(&self, epoch: Epoch, committee: Arc<Committee>) -> Arc<Committee> {
286 let pinned = self.map.pin();
287 match pinned.try_insert(epoch, committee) {
288 Ok(inserted) => inserted.clone(),
289 Err(e) => e.current.clone(),
290 }
291 }
292}
293
294#[cfg(test)]
295mod tests {
296 use super::*;
297
298 #[test]
299 fn shared_committees_insert_and_get() {
300 let shared = SharedCommittees::new();
301 assert!(shared.get(Epoch(0)).is_none());
302 let committee = Arc::new(Committee::default());
303 let inserted = shared.insert(Epoch(0), committee.clone());
304 assert!(Arc::ptr_eq(&inserted, &committee));
305 let fetched = shared.get(Epoch(0)).unwrap();
306 assert!(Arc::ptr_eq(&fetched, &committee));
307 }
308
309 #[test]
310 fn shared_committees_insert_is_first_writer_wins() {
311 let shared = SharedCommittees::new();
312 let first = Arc::new(Committee::default());
313 let second = Arc::new(Committee::default());
314 let winner = shared.insert(Epoch(5), first.clone());
315 assert!(Arc::ptr_eq(&winner, &first));
316 let loser = shared.insert(Epoch(5), second.clone());
317 assert!(Arc::ptr_eq(&loser, &first));
318 assert!(!Arc::ptr_eq(&loser, &second));
319 }
320
321 #[test]
322 fn shared_committees_clones_share_storage() {
323 let a = SharedCommittees::new();
324 let b = a.clone();
325 let committee = Arc::new(Committee::default());
326 a.insert(Epoch(1), committee.clone());
327 let fetched = b.get(Epoch(1)).unwrap();
328 assert!(Arc::ptr_eq(&fetched, &committee));
329 }
330}