Skip to content

Commit

Permalink
Add QBFT manager (#77)
Browse files Browse the repository at this point in the history
Co-authored-by: diegomrsantos <[email protected]>
Co-authored-by: ThreeHrSleep <[email protected]>
  • Loading branch information
3 people authored Jan 14, 2025
1 parent 089f79a commit 9389ccd
Show file tree
Hide file tree
Showing 17 changed files with 1,213 additions and 677 deletions.
150 changes: 86 additions & 64 deletions Cargo.lock

Large diffs are not rendered by default.

5 changes: 5 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ members = [
"anchor/http_metrics",
"anchor/network",
"anchor/processor",
"anchor/qbft_manager",
"anchor/signature_collector",
]
resolver = "2"
Expand Down Expand Up @@ -53,9 +54,11 @@ either = "1.13.0"
futures = "0.3.30"
health_metrics = { git = "https://github.com/sigp/lighthouse", branch = "anchor" }
hyper = "1.4"
indexmap = "2.7.0"
num_cpus = "1"
openssl = "0.10.68"
parking_lot = "0.12"
qbft_manager = { path = "anchor/common/qbft" }
rusqlite = "0.28.0"
serde = { version = "1.0.208", features = ["derive"] }
strum = { version = "0.24", features = ["derive"] }
Expand All @@ -69,6 +72,8 @@ tokio = { version = "1.39.2", features = [
tower-http = { version = "0.6", features = ["cors"] }
tracing = "0.1.40"
tracing-subscriber = { version = "0.3.18", features = ["fmt", "env-filter"] }
tree_hash = "0.8"
tree_hash_derive = "0.8"
hex = "0.4.3"
alloy = { version = "0.6.4", features = [
"sol-types",
Expand Down
5 changes: 3 additions & 2 deletions anchor/common/qbft/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ edition = { workspace = true }

[dependencies]
derive_more = { workspace = true }
futures = { workspace = true }
tokio = { workspace = true, features = ["sync"] }
indexmap = { workspace = true }
tracing = { workspace = true }

[dev-dependencies]
tracing-subscriber = { workspace = true }
253 changes: 184 additions & 69 deletions anchor/common/qbft/src/config.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use super::error::ConfigBuilderError;
use crate::types::{DefaultLeaderFunction, InstanceHeight, LeaderFunction, OperatorId, Round};
use std::collections::HashSet;
use indexmap::IndexSet;
use std::fmt::Debug;
use std::time::Duration;

Expand All @@ -9,16 +9,14 @@ pub struct Config<F>
where
F: LeaderFunction + Clone,
{
pub operator_id: OperatorId,
pub instance_height: InstanceHeight,
pub round: Round,
pub pr: usize,
pub committee_size: usize,
pub committee_members: HashSet<OperatorId>,
pub quorum_size: usize,
pub round_time: Duration,
pub max_rounds: usize,
pub leader_fn: F,
operator_id: OperatorId,
instance_height: InstanceHeight,
round: Round,
committee_members: IndexSet<OperatorId>,
quorum_size: usize,
round_time: Duration,
max_rounds: usize,
leader_fn: F,
}

impl<F: Clone + LeaderFunction> Config<F> {
Expand All @@ -27,26 +25,25 @@ impl<F: Clone + LeaderFunction> Config<F> {
pub fn operator_id(&self) -> OperatorId {
self.operator_id
}
/// The committee size
pub fn committee_size(&self) -> usize {
self.committee_size
}

pub fn commmittee_members(&self) -> HashSet<OperatorId> {
self.committee_members.clone()
}

/// The quorum size required for the committee to reach consensus
pub fn quorum_size(&self) -> usize {
self.quorum_size
pub fn instance_height(&self) -> &InstanceHeight {
&self.instance_height
}

/// The round number -- likely always 0 at initialisation unless we want to implement re-joining an existing
/// The round number -- likely always 1 at initialisation unless we want to implement re-joining an existing
/// instance that has been dropped locally
pub fn round(&self) -> Round {
self.round
}

pub fn committee_members(&self) -> &IndexSet<OperatorId> {
&self.committee_members
}

pub fn quorum_size(&self) -> usize {
self.quorum_size
}

/// How long the round will last
pub fn round_time(&self) -> Duration {
self.round_time
Expand All @@ -61,83 +58,201 @@ impl<F: Clone + LeaderFunction> Config<F> {
pub fn leader_fn(&self) -> &F {
&self.leader_fn
}
}
impl Default for Config<DefaultLeaderFunction> {
fn default() -> Self {
//use the builder to also validate defaults
ConfigBuilder::<DefaultLeaderFunction>::default()
.build()
.expect("Default parameters should be valid")

/// Obtains the maximum number of faulty nodes that this consensus can tolerate
pub fn get_f(&self) -> usize {
get_f(self.committee_members.len())
}

/// Private constructor so it can only be built by our `ConfigBuilder`.
fn from_builder(builder: &ConfigBuilder<F>) -> Self {
Self {
operator_id: builder.operator_id,
instance_height: builder.instance_height,
committee_members: builder.committee_members.clone(),
round: builder.round,
round_time: builder.round_time,
max_rounds: builder.max_rounds,
quorum_size: builder.quorum_size,
leader_fn: builder.leader_fn.clone(),
}
}
}

fn get_f(members: usize) -> usize {
(members - 1) / 3
}

/// Builder struct for constructing the QBFT instance configuration
pub struct ConfigBuilder<F: LeaderFunction + Clone> {
config: Config<F>,
#[derive(Clone, Debug)]
pub struct ConfigBuilder<F = DefaultLeaderFunction>
where
F: LeaderFunction + Clone,
{
// Mandatory fields
operator_id: OperatorId,
instance_height: InstanceHeight,
committee_members: IndexSet<OperatorId>,
leader_fn: F,

// Optional fields with defaults set in the constructor
round: Round,
round_time: Duration,
max_rounds: usize,
quorum_size: usize,
}

impl Default for ConfigBuilder<DefaultLeaderFunction> {
fn default() -> Self {
impl<F> ConfigBuilder<F>
where
F: LeaderFunction + Clone + Default,
{
pub fn new(
operator_id: OperatorId,
instance_height: InstanceHeight,
committee_members: IndexSet<OperatorId>,
) -> Self {
let committee_size = committee_members.len();
let f = get_f(committee_size);
let default_quorum = committee_size.saturating_sub(f);

ConfigBuilder {
config: Config {
operator_id: OperatorId::default(),
instance_height: InstanceHeight::default(),
committee_size: 0,
committee_members: HashSet::new(),
quorum_size: 4,
round: Round::default(),
pr: 0,
round_time: Duration::new(2, 0),
max_rounds: 4,
leader_fn: DefaultLeaderFunction {},
},
operator_id,
instance_height,
committee_members,
round: Round::default(),
round_time: Duration::new(2, 0),
max_rounds: 4,
quorum_size: default_quorum,
leader_fn: F::default(),
}
}
}
impl<F: LeaderFunction + Clone> From<Config<F>> for ConfigBuilder<F> {
fn from(config: Config<F>) -> Self {
ConfigBuilder { config }

impl<F> ConfigBuilder<F>
where
F: LeaderFunction + Clone,
{
pub fn new_with_leader_fn(
operator_id: OperatorId,
instance_height: InstanceHeight,
committee_members: IndexSet<OperatorId>,
leader_fn: F,
) -> Self {
let committee_size = committee_members.len();
let f = get_f(committee_size);
let default_quorum = committee_size.saturating_sub(f);

ConfigBuilder {
operator_id,
instance_height,
committee_members,
round: Round::default(),
round_time: Duration::new(2, 0),
max_rounds: 4,
quorum_size: default_quorum,
leader_fn,
}
}
pub fn operator_id(&self) -> OperatorId {
self.operator_id
}

pub fn committee_members(&self) -> &IndexSet<OperatorId> {
&self.committee_members
}

pub fn instance_height(&self) -> &InstanceHeight {
&self.instance_height
}
}

impl<F: LeaderFunction + Clone> ConfigBuilder<F> {
pub fn operator_id(&mut self, operator_id: OperatorId) -> &mut Self {
self.config.operator_id = operator_id;
pub fn round(&self) -> Round {
self.round
}

pub fn round_time(&self) -> Duration {
self.round_time
}

pub fn quorum_size(&self) -> usize {
self.quorum_size
}

pub fn max_rounds(&self) -> usize {
self.max_rounds
}

pub fn leader_fn(&self) -> &F {
&self.leader_fn
}

// Chained setter methods for optional fields to override defaults
pub fn with_operator_id(mut self, operator_id: OperatorId) -> Self {
self.operator_id = operator_id;
self
}

pub fn with_instance_height(mut self, instance_height: InstanceHeight) -> Self {
self.instance_height = instance_height;
self
}

pub fn instance_height(&mut self, instance_height: InstanceHeight) -> &mut Self {
self.config.instance_height = instance_height;
pub fn with_committee_members(mut self, committee_members: IndexSet<OperatorId>) -> Self {
self.committee_members = committee_members;
self
}

pub fn committee_size(&mut self, committee_size: usize) -> &mut Self {
self.config.committee_size = committee_size;
pub fn with_round(mut self, round: Round) -> Self {
self.round = round;
self
}

pub fn quorum_size(&mut self, quorum_size: usize) -> &mut Self {
self.config.quorum_size = quorum_size;
pub fn with_round_time(mut self, round_time: Duration) -> Self {
self.round_time = round_time;
self
}

pub fn round(&mut self, round: Round) -> &mut Self {
self.config.round = round;
pub fn with_max_rounds(mut self, max_rounds: usize) -> Self {
self.max_rounds = max_rounds;
self
}
pub fn round_time(&mut self, round_time: Duration) -> &mut Self {
self.config.round_time = round_time;

pub fn with_quorum_size(mut self, quorum_size: usize) -> Self {
self.quorum_size = quorum_size;
self
}
pub fn leader_fn(&mut self, leader_fn: F) -> &mut Self {
self.config.leader_fn = leader_fn;

pub fn with_leader_fn(mut self, leader_fn: F) -> Self {
self.leader_fn = leader_fn;
self
}

pub fn build(&self) -> Result<Config<F>, ConfigBuilderError> {
if self.config.quorum_size < 1 {
return Err(ConfigBuilderError::QuorumSizeTooSmall);
pub fn build(self) -> Result<Config<F>, ConfigBuilderError> {
// Validate mandatory fields
let committee_size = self.committee_members.len();
if committee_size == 0 {
return Err(ConfigBuilderError::NoParticipants);
}
if !self.committee_members.contains(&self.operator_id) {
return Err(ConfigBuilderError::OperatorNotParticipant);
}

// Validate `quorum_size`
let f = get_f(committee_size);
if self.quorum_size < f * 2 + 1 || self.quorum_size > committee_size - f {
return Err(ConfigBuilderError::InvalidQuorumSize);
}

// Validate `max_rounds`
if self.max_rounds == 0 {
return Err(ConfigBuilderError::ZeroMaxRounds);
}

// Validate `round`
if self.round.get() > self.max_rounds {
return Err(ConfigBuilderError::ExceedingStartingRound);
}

Ok(self.config.clone())
// If everything is okay, build and return a `Config`.
Ok(Config::from_builder(&self))
}
}
Loading

0 comments on commit 9389ccd

Please sign in to comment.