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    /// Creates a new committee from the given validators and resource control policy.
166    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 N = 3f + 1 + k such that 0 <= k <= 2. (Notably ⌊k / 3⌋ = 0 and ⌊(2 - k) / 3⌋ = 0.)
172        // The following thresholds verify:
173        // * ⌊2 N / 3⌋ + 1 = ⌊(6f + 2 + 2k) / 3⌋ + 1 = 2f + 1 + k + ⌊(2 - k) / 3⌋ = N - f
174        // * ⌊(N + 2) / 3⌋= ⌊(3f + 3 + k) / 3⌋ = f + 1 + ⌊k / 3⌋ = f + 1
175        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    /// Creates a simple committee for testing, giving each validator equal voting weight.
188    #[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    /// Returns the number of votes held by the given validator, or zero if it is not a member.
207    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    /// Returns an iterator over each validator's account public key and its number of votes.
215    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    /// Returns the number of votes required to reach a quorum.
222    pub fn quorum_threshold(&self) -> u64 {
223        self.quorum_threshold
224    }
225
226    /// Returns the number of votes required to reach the validity threshold.
227    pub fn validity_threshold(&self) -> u64 {
228        self.validity_threshold
229    }
230
231    /// Returns the validators in this committee, keyed by their public key.
232    pub fn validators(&self) -> &BTreeMap<ValidatorPublicKey, ValidatorState> {
233        &self.validators
234    }
235
236    /// Returns an iterator over each validator's public key and network address.
237    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    /// Returns the total number of votes across all validators.
244    pub fn total_votes(&self) -> u64 {
245        self.total_votes
246    }
247
248    /// Returns the resource control policy of this committee.
249    pub fn policy(&self) -> &ResourceControlPolicy {
250        &self.policy
251    }
252
253    /// Returns a mutable reference to this committee's [`ResourceControlPolicy`].
254    pub fn policy_mut(&mut self) -> &mut ResourceControlPolicy {
255        &mut self.policy
256    }
257}
258
259/// Process-global, append-only cache of committees by epoch.
260///
261/// Committees are network-global state (created by the admin chain, agreed on
262/// by every validator), so caching them once per process avoids holding a
263/// separate copy in every chain's execution state. The map is populated
264/// lazily by `get_or_load`-style lookups in [`crate::ExecutionRuntimeContext`]
265/// and in the storage layer.
266#[derive(Clone, Debug, Default)]
267pub struct SharedCommittees {
268    map: Arc<papaya::HashMap<Epoch, Arc<Committee>>>,
269}
270
271impl SharedCommittees {
272    /// Creates a new, empty committee cache.
273    pub fn new() -> Self {
274        Self::default()
275    }
276
277    /// Returns the cached committee for `epoch`, if any.
278    pub fn get(&self, epoch: Epoch) -> Option<Arc<Committee>> {
279        self.map.pin().get(&epoch).cloned()
280    }
281
282    /// Inserts `committee` for `epoch`. If an entry was already present, the
283    /// existing value wins and is returned (avoiding spurious clones when two
284    /// callers race to populate the same epoch).
285    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}