Skip to content

Commit

Permalink
[Consensus] add Committee type (MystenLabs#15455)
Browse files Browse the repository at this point in the history
## Description 

Add `Committee` config and crypto key types to the
`consensus-config` crate.

## Test Plan 

CI

---
If your changes are not user-facing and not a breaking change, you can
skip the following section. Otherwise, please indicate what changed, and
then add to the Release Notes section as highlighted during the release
process.

### Type of Change (Check all that apply)

- [ ] protocol change
- [ ] user-visible impact
- [ ] breaking change for a client SDKs
- [ ] breaking change for FNs (FN binary must upgrade)
- [ ] breaking change for validators or node operators (must upgrade
binaries)
- [ ] breaking change for on-chain data layout
- [ ] necessitate either a data wipe or data migration

### Release notes
  • Loading branch information
mwtian authored Dec 21, 2023
1 parent 035819c commit 80b1d97
Show file tree
Hide file tree
Showing 7 changed files with 307 additions and 2 deletions.
3 changes: 3 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions consensus/config/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ edition = "2021"
publish = false

[dependencies]
fastcrypto.workspace = true
multiaddr.workspace = true
rand.workspace = true
serde.workspace = true

workspace-hack.workspace = true
Expand Down
173 changes: 173 additions & 0 deletions consensus/config/src/committee.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use std::fmt::{Display, Formatter};

use multiaddr::Multiaddr;
use serde::{Deserialize, Serialize};

use crate::{NetworkPublicKey, ProtocolPublicKey};

/// Committee of the consensus protocol is updated each epoch.
pub type Epoch = u64;

/// Voting power of an authority, roughly proportional to the actual amount of Sui staked
/// by the authority.
/// Total stake / voting power of all authorities should sum to 10,000.
pub type Stake = u64;

/// Committee is the set of authorities that participate in the consensus protocol for this epoch.
/// Its configuration is stored and computed on chain.
#[derive(Serialize, Deserialize)]
pub struct Committee {
/// The epoch number of this committee
epoch: Epoch,
/// Total stake in the committee.
total_stake: Stake,
/// The quorum threshold (2f+1).
quorum_threshold: Stake,
/// The validity threshold (f+1).
validity_threshold: Stake,
/// Protocol and network info of each authority.
authorities: Vec<Authority>,
}

impl Committee {
pub fn new(epoch: Epoch, authorities: Vec<Authority>) -> Self {
assert!(!authorities.is_empty(), "Committee cannot be empty!");
assert!(
authorities.len() < u32::MAX as usize,
"Too many authorities ({})!",
authorities.len()
);
let total_stake = authorities.iter().map(|a| a.stake).sum();
assert_ne!(total_stake, 0, "Total stake cannot be zero!");
let quorum_threshold = 2 * total_stake / 3 + 1;
let validity_threshold = (total_stake + 2) / 3;
Self {
epoch,
total_stake,
quorum_threshold,
validity_threshold,
authorities,
}
}

/// Public accessors for Committee data.
pub fn epoch(&self) -> Epoch {
self.epoch
}

pub fn total_stake(&self) -> Stake {
self.total_stake
}

pub fn quorum_threshold(&self) -> Stake {
self.quorum_threshold
}

pub fn validity_threshold(&self) -> Stake {
self.validity_threshold
}

pub fn stake(&self, authority_index: AuthorityIndex) -> Stake {
self.authorities[authority_index.value()].stake
}

pub fn authority(&self, authority_index: AuthorityIndex) -> &Authority {
&self.authorities[authority_index.value()]
}

pub fn authorities(&self) -> impl Iterator<Item = (AuthorityIndex, &Authority)> {
self.authorities
.iter()
.enumerate()
.map(|(i, a)| (AuthorityIndex(i as u32), a))
}

pub fn size(&self) -> usize {
self.authorities.len()
}
}

/// Represents one authority in the committee.
///
/// NOTE: this is intentionally un-cloneable, to encourage only copying relevant fields.
/// AuthorityIndex should be used to reference an authority instead.
#[derive(Serialize, Deserialize)]
pub struct Authority {
/// Voting power of the authority in the committee.
pub stake: Stake,
/// Network address for communicating with the authority.
pub address: Multiaddr,
/// The validator's hostname, for metrics and logging.
pub hostname: String,
/// The authority's ed25519 publicKey for signing network messages and blocks.
pub network_key: NetworkPublicKey,
/// The authority's bls public key for random beacon.
pub protocol_key: ProtocolPublicKey,
}

/// Each authority is uniquely identified by its AuthorityIndex in the Committee.
/// AuthorityIndex is between 0 (inclusive) and the total number of authorities (exclusive).
///
/// NOTE: AuthorityIndex should not need to be created outside of this file or incremented.
#[derive(
Eq, PartialEq, Ord, PartialOrd, Clone, Copy, Debug, Default, Hash, Serialize, Deserialize,
)]
pub struct AuthorityIndex(u32);

impl AuthorityIndex {
pub fn value(&self) -> usize {
self.0 as usize
}
}

impl Display for AuthorityIndex {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.write_str(self.0.to_string().as_str())
}
}

#[cfg(test)]
mod tests {
use crate::{Authority, Committee, NetworkKeyPair, ProtocolKeyPair, Stake};
use fastcrypto::traits::KeyPair as _;
use multiaddr::Multiaddr;
use rand::{rngs::StdRng, SeedableRng};

#[test]
fn committee_basic() {
// GIVEN
let epoch = 100;

let mut authorities = vec![];
let mut rng = StdRng::from_seed([9; 32]);
let num_of_authorities = 9;
for i in 1..=num_of_authorities {
let network_keypair = NetworkKeyPair::generate(&mut rng);
let protocol_keypair = ProtocolKeyPair::generate(&mut rng);
authorities.push(Authority {
stake: i as Stake,
address: Multiaddr::empty(),
hostname: "test_host".to_string(),
network_key: network_keypair.public().clone(),
protocol_key: protocol_keypair.public().clone(),
});
}

let committee = Committee::new(epoch, authorities);

// THEN make sure the output Committee fields are populated correctly.
assert_eq!(committee.size(), num_of_authorities);
for (i, authority) in committee.authorities() {
assert_eq!((i.value() + 1) as Stake, authority.stake);
}

// AND ensure thresholds are calculated correctly.
assert_eq!(committee.total_stake(), 45);
assert_eq!(committee.quorum_threshold(), 31);
assert_eq!(committee.validity_threshold(), 15);
}
}
28 changes: 28 additions & 0 deletions consensus/config/src/crypto.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use fastcrypto::{bls12381, ed25519};

////////////////////////////////////////////////////////////////////////
// Type aliases selecting the cryptography algorithms for the code base.
////////////////////////////////////////////////////////////////////////
// Here we select the types that are used by default in the code base.
// The whole code base should only:
// - refer to those aliases and not use the individual scheme implementations
// - not use the schemes in a way that break genericity (e.g. using their Struct impl functions)
// - swap one of those aliases to point to another type if necessary
//
// Beware: if you change those aliases to point to another scheme implementation, you will have
// to change all four aliases to point to concrete types that work with each other. Failure to do
// so will result in a ton of compilation errors, and worse: it will not make sense!

/// Network key signs network messages and blocks.
pub type NetworkPublicKey = ed25519::Ed25519PublicKey;
pub type NetworkPrivateKey = ed25519::Ed25519PrivateKey;
pub type NetworkKeyPair = ed25519::Ed25519KeyPair;

/// Protocol key is used in random beacon.
pub type ProtocolPublicKey = bls12381::min_sig::BLS12381PublicKey;
pub type ProtocolPublicKeyBytes = bls12381::min_sig::BLS12381PublicKeyAsBytes;
pub type ProtocolPrivateKey = bls12381::min_sig::BLS12381PrivateKey;
pub type ProtocolKeyPair = bls12381::min_sig::BLS12381KeyPair;
8 changes: 6 additions & 2 deletions consensus/config/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

pub mod parameters;
mod committee;
mod crypto;
mod parameters;

pub use parameters::Parameters;
pub use committee::*;
pub use crypto::*;
pub use parameters::*;
34 changes: 34 additions & 0 deletions consensus/config/tests/committee_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright (c) Mysten Labs, Inc.
// SPDX-License-Identifier: Apache-2.0

use consensus_config::{Authority, Committee, NetworkKeyPair, ProtocolKeyPair, Stake};
use fastcrypto::traits::KeyPair as _;
use insta::assert_yaml_snapshot;
use multiaddr::Multiaddr;
use rand::{rngs::StdRng, SeedableRng as _};

// Committee is not sent over network or stored on disk itself, but some of its fields are.
// So this test can still be useful to detect accidental format changes.
#[test]
fn committee_snapshot_matches() {
let epoch = 100;

let mut authorities: Vec<_> = vec![];
let mut rng = StdRng::from_seed([9; 32]);
let num_of_authorities = 10;
for i in 1..=num_of_authorities {
let network_keypair = NetworkKeyPair::generate(&mut rng);
let protocol_keypair = ProtocolKeyPair::generate(&mut rng);
authorities.push(Authority {
stake: i as Stake,
address: Multiaddr::empty(),
hostname: "test_host".to_string(),
network_key: network_keypair.public().clone(),
protocol_key: protocol_keypair.public().clone(),
});
}

let committee = Committee::new(epoch, authorities);

assert_yaml_snapshot!("committee", committee)
}
60 changes: 60 additions & 0 deletions consensus/config/tests/snapshots/committee_test__committee.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
---
source: consensus/config/tests/committee_test.rs
expression: committee
---
epoch: 100
total_stake: 55
quorum_threshold: 37
validity_threshold: 19
authorities:
- stake: 1
address: ""
hostname: test_host
network_key: lscBzpFcgepvETNDWHp61B+teyauHrk6kb5XBszDufg=
protocol_key: tembhBjGw+XVGXywAYDja1R67d8pSKGhYypbgeRUJmTTdzfNnr/LHJ3U0bZY49ChGXw6mKwBVJV4KP3na8YFaef9wQuHKgwjvhJT3FnimCPyBwIz4cFasq1d3As4/yeP
- stake: 2
address: ""
hostname: test_host
network_key: VnXFtZxaTby9AquydHFr9dy8+GlVoPcsm8icljGTbWQ=
protocol_key: hLIJiig6cqajwuIlnxjL+bxhZxCgEWUGdN9VyJSqMAvuN6tDm7qTcBplvDICmgSlGJwHZ5IzyOH6N+Wj1Fa1odxsM86eLf9g+MjzT/h869sqV9EpyXtpYbufPYCW0fkT
- stake: 3
address: ""
hostname: test_host
network_key: +yTI/ZSBZa9CmHP/Qhjtf2bgTR0lC+9NUP8BgQq0OPw=
protocol_key: kci5lWPW4LlAr1jLSyEkGtYeSfstp3daJ0pChi5zo4KaDOOQd7IbnRPyP/zMqkfTE9MIyymjSkhnI72/Wogbqedrc5lhIp+C2wtbv79mdC8SrbHb2Y/LHvYD7w1mOJFM
- stake: 4
address: ""
hostname: test_host
network_key: TLb93AG1NlYtf9dVMnS6LJE5aRfqt2LsSBJeuoa3wHk=
protocol_key: onGDFBk46p2hpQFq3QJbyAVD0G05FOxRaJQH3LacRTRRfdXWLtewyUi1Z0MMX4H3CpoldjqoGeMbESvqgTZeKt6HE3d8pn6TApMzf632WI3DfUGUUMBM7ciCivNaKTBS
- stake: 5
address: ""
hostname: test_host
network_key: 7DYZ4De7EKRhhH0GBMTKlBqUeXnFMgkzLKjekMAuesw=
protocol_key: iM3E2wCApVtOKv1I/jSpNXO1kPRCnHo0c6H/qgyxvJngmgW1KYnWtycts8drNLIUGLtinT83V8D3Xnc3whUAqiKYrJ5vu2knQXqFBCmgrqo7N0xZ0T/uuKVWjoNqyHHl
- stake: 6
address: ""
hostname: test_host
network_key: B2I7VErvTa/POXtLemf+iDEt/966mNqq3F+DdHcvi8U=
protocol_key: pEiGWt/9mL2YXfHkYntXWy7OzGdS1psbJQ2D5Xp5FS7XZWYQzh7+zz/bkOfgWCRaFoV8bbsmb7eVwuMvOTnPvCSamtyUQ3pc+SKVTsXtRbwRNvWtXjTVHGQQSXE5R1kp
- stake: 7
address: ""
hostname: test_host
network_key: wCXBPdpdazlmPWntDXu+LQJiJZb72na9EY3njPLgBGY=
protocol_key: irfuZhbsh3mvm53TmiAPGWzDUEmr89RhBBtwNGwZOW36BOaSpxcgOZXfrOPTJDstDFU+o1XRq2T0t39P4tKagKjC50TFtMSVFU/4Qf6bHqEPzuZiOtCa3W3mKQUXz8wE
- stake: 8
address: ""
hostname: test_host
network_key: +0ebVcUY7X8QIafWGbUmzlhy/Vc2LtNRqqLzk4NgLoU=
protocol_key: jDvXpT0oTY7w9BWERszWiLBu9Aajj9WbbPkfmE9M+ijxGCooLK14P1lsqOZ67XMoAIfh8Z5t/hNanV5fH+3PtJT6o8APO5pCoTcLi5SoScRSQKMCevHqHAizI57wG4cl
- stake: 9
address: ""
hostname: test_host
network_key: YxoEz+3fDnLfrOkpqVkRxfj/HqlqsoQqt9bzSLjxJEc=
protocol_key: iIEIwu3rHEXU1fy+2VNUKjqV2tFLcu35a5Uo+qO99rQ63nywUvOsiDxrUvzOpJYoDZ/Xa4PnzgkhFTiGVi/Qt6ASIBhu/66jjxhDs6Pt7h1CtRFnD3zWRkgYinIzmxuQ
- stake: 10
address: ""
hostname: test_host
network_key: lT6jnl+ZiC2MhDfooUetAJFDd6cRQy1Lq9KKjKqOevo=
protocol_key: lxvqS7Rm/l8gmFj6Q3CJ3Qv994gzsM/hSt5YoBrE8v4UmfoyuvQ0JQ9gw2qNbMuiDwvkCAxotLiCa0yjKjZe5Kk8QS37dGZw5akNHQ2SHkc+CK7bccWVkdfRnF4iQal5

0 comments on commit 80b1d97

Please sign in to comment.