Skip to main content

linera_execution/
committee.rs

1// Copyright (c) Facebook, Inc. and its affiliates.
2// Copyright (c) Zefchain Labs, Inc.
3// SPDX-License-Identifier: Apache-2.0
4
5use 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/// Public state of a validator.
17#[derive(Eq, PartialEq, Hash, Clone, Debug, Serialize, Deserialize, Allocative)]
18pub struct ValidatorState {
19    /// The network address (in a string format understood by the networking layer).
20    pub network_address: String,
21    /// The voting power.
22    pub votes: u64,
23    /// The public key of the account associated with the validator.
24    pub account_public_key: AccountPublicKey,
25}
26
27/// A set of validators (identified by their public keys) and their voting rights.
28#[derive(Eq, PartialEq, Hash, Clone, Debug, Default, Allocative)]
29#[cfg_attr(with_graphql, derive(async_graphql::InputObject))]
30pub struct Committee {
31    /// The validators in the committee.
32    pub validators: BTreeMap<ValidatorPublicKey, ValidatorState>,
33    /// The sum of all voting rights.
34    total_votes: u64,
35    /// The threshold to form a quorum.
36    quorum_threshold: u64,
37    /// The threshold to prove the validity of a statement. I.e. the assumption is that strictly
38    /// less than `validity_threshold` are faulty.
39    validity_threshold: u64,
40    /// The policy agreed on for this epoch.
41    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 N = 3f + 1 + k such that 0 <= k <= 2. (Notably ⌊k / 3⌋ = 0 and ⌊(2 - k) / 3⌋ = 0.)
171        // The following thresholds verify:
172        // * ⌊2 N / 3⌋ + 1 = ⌊(6f + 2 + 2k) / 3⌋ + 1 = 2f + 1 + k + ⌊(2 - k) / 3⌋ = N - f
173        // * ⌊(N + 2) / 3⌋= ⌊(3f + 3 + k) / 3⌋ = f + 1 + ⌊k / 3⌋ = f + 1
174        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    /// Returns a mutable reference to this committee's [`ResourceControlPolicy`].
244    pub fn policy_mut(&mut self) -> &mut ResourceControlPolicy {
245        &mut self.policy
246    }
247}
248
249/// Process-global, append-only cache of committees by epoch.
250///
251/// Committees are network-global state (created by the admin chain, agreed on
252/// by every validator), so caching them once per process avoids holding a
253/// separate copy in every chain's execution state. The map is populated
254/// lazily by `get_or_load`-style lookups in [`crate::ExecutionRuntimeContext`]
255/// and in the storage layer.
256#[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    /// Returns the cached committee for `epoch`, if any.
267    pub fn get(&self, epoch: Epoch) -> Option<Arc<Committee>> {
268        self.map.pin().get(&epoch).cloned()
269    }
270
271    /// Inserts `committee` for `epoch`. If an entry was already present, the
272    /// existing value wins and is returned (avoiding spurious clones when two
273    /// callers race to populate the same epoch).
274    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}