Skip to content

Commit

Permalink
feat: tests and docs (#9)
Browse files Browse the repository at this point in the history
* feat: refactored

* feat: refactored errors

* feat: improved errors, fixed tests

* fix: resolved end to end ballot -> working

* feat: moved value definition into separate file

* chore: enhanced code-centric documentation

* chore: extended Cargo.toml

* chore: updated README

* chore: added links to docs in README and fixed incorrect message type in docs for ValueSelector
  • Loading branch information
NikitaMasych authored Sep 3, 2024
1 parent 4322a91 commit 754e246
Show file tree
Hide file tree
Showing 9 changed files with 1,634 additions and 967 deletions.
33 changes: 24 additions & 9 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,29 @@
name = "bpcon"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
description = "BPCon: A Byzantine Fault-Tolerant Consensus Protocol Implementation in Rust."
license = "MIT"
repository = "https://github.com/distributed-lab/bpcon"
homepage = "https://github.com/distributed-lab/bpcon"
documentation = "https://distributed-lab.github.io/bpcon/"
keywords = ["consensus", "byzantine", "protocol", "distributed-systems", "blockchain"]
categories = ["algorithms"]
authors = ["Distributed Lab"]
readme = "README.md"

[dependencies]
log = "0.4.22"
rkyv = { version = "0.7.44", features = ["validation"]}
serde = { version = "1.0.207", features = ["derive"] }
bincode = "1.3.3"
tokio = { version = "1.39.2", features = ["full", "test-util"] }
rand = "0.9.0-alpha.2"
seeded-random = "0.6.0"
log = "^0.4.22"
serde = { version = "^1.0.207", features = ["derive"] }
bincode = "^1.3.3"
rkyv = { version = "^0.7.44", features = ["validation"] }
tokio = { version = "^1.39.2", features = ["full"] }
rand = "^0.9.0-alpha.2"
seeded-random = "^0.6.0"
thiserror = "^1.0.63"

[features]
default = ["full"]
full = ["tokio/full", "rkyv/validation"]

[dev-dependencies]
tokio = { version = "^1.39.2", features = ["test-util"] }
136 changes: 122 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,135 @@
# BPCon Rust Library
# Weighted BPCon Rust Library

[![License: GPL v3](https://img.shields.io/badge/License-GPLv3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
[![Rust CI🌌](https://github.com/distributed-lab/bpcon/actions/workflows/rust.yml/badge.svg)](https://github.com/distributed-lab/bpcon/actions/workflows/rust.yml)
[![Docs 🌌](https://github.com/distributed-lab/bpcon/actions/workflows/docs.yml/badge.svg)](https://github.com/distributed-lab/bpcon/actions/workflows/docs.yml)

This is a generic rust implementation of the `BPCon` consensus mechanism.
> This is a rust library implementing weighted BPCon consensus.
## Library Structure
## Documentation

### src/party.rs
Library is documented with `rustdoc`.
Compiled documentation for `main` branch is available at [GitBook](https://distributed-lab.github.io/bpcon).

Main entity in this implementation is `Party` - it represents member of the consensus.
## Usage

External system shall create desired amount of parties.
### Add dependency in your `Cargo.toml`

We have 2 communication channels - one for sending `MessageWire` - encoded in bytes message and routing information,
and the other for pitching consensus events - this allows for external system to impose custom limitations and rules
regarding runway.
```toml
[dependencies]
bpcon = {version = "0.1.0", git = "https://github.com/distributed-lab/bpcon"}
```

### src/message.rs
### Implement [Value](https://distributed-lab.github.io/bpcon/bpcon/value/trait.Value.html) trait

Definitions of the general message struct, routing information and type-specific contents.
This is a core trait, which defines what type are you selecting in your consensus.
It may be the next block in blockchain, or leader for some operation, or anything else you need.

### src/lib.rs
Below is a simple example, where we will operate on selection for `u64` type.
Using it you may interpret `ID` for leader of distributed operation, for instance.

Here we present a trait for the value on which consensus is being conducted. Additionally, there is a trait for
defining custom value selection rules, called `ValueSelector`.
```rust
...
use bpcon::value::Value;

#[derive(Clone, PartialEq, Eq, serde::Serialize, serde::Deserialize, Debug, Hash)]
pub(crate) struct MyValue(u64);

impl Value for MyValue {}
```

### Implement [ValueSelector](https://distributed-lab.github.io/bpcon/bpcon/value/trait.ValueSelector.html) trait

`BPCon` allows you to define specific conditions how proposer (leader) will select value
and how other members will verify its selection.

Here is a simple example:

```rust
...
use bpcon::value::ValueSelector;

#[derive(Clone)]
pub struct MyValueSelector;

impl ValueSelector<MyValue> for MyValueSelector {
/// Verifies if the given value `v` has been correctly selected according to the protocol rules.
///
/// In this basic implementation, we'll consider a value as "correctly selected" if it matches
/// the majority of the values in the provided `HashMap`.
fn verify(&self, v: &MyValue, m: &HashMap<u64, Option<MyValue>>) -> bool {
// Count how many times the value `v` appears in the `HashMap`
let count = m.values().filter(|&val| val.as_ref() == Some(v)).count();

// For simplicity, consider the value verified if it appears in more than half of the entries
count > m.len() / 2
}

/// Selects a value based on the provided `HashMap` of party votes.
///
/// This implementation selects the value that appears most frequently in the `HashMap`.
/// If there is a tie, it selects the smallest value (as per the natural ordering of `u64`).
fn select(&self, m: &HashMap<u64, Option<MyValue>>) -> MyValue {
let mut frequency_map = HashMap::new();

// Count the frequency of each value in the `HashMap`
for value in m.values().flatten() {
*frequency_map.entry(value).or_insert(0) += 1;
}

// Find the value with the highest frequency. In case of a tie, select the smallest value.
frequency_map
.into_iter()
.max_by_key(|(value, count)| (*count, value.0))
.map(|(value, _)| value.clone())
.expect("No valid value found")
}
}
```

### Decide on a [LeaderElector](https://distributed-lab.github.io/bpcon/bpcon/leader/trait.LeaderElector.html)

`LeaderElector` trait allows you to define specific conditions, how to select leader for consensus.

__NOTE: it is important to provide deterministic mechanism,
because each participant will compute leader for itself
and in case it is not deterministic, state divergence occurs.__

We also provide ready-to-use
[DefaultLeaderElector](https://distributed-lab.github.io/bpcon/bpcon/leader/struct.DefaultLeaderElector.html)
which is using weighted randomization.

### Configure your ballot

As a next step, you need to decide on parameters for your ballot:

1. Amount of parties and their weight.
2. Threshold weight.
3. Time bounds.

Example:

```rust
use bpcon::config::BPConConfig;

let cfg = BPConConfig::with_default_timeouts(vec![1, 1, 1, 1, 1, 1], 4);
```

Feel free to explore [config.rs](https://distributed-lab.github.io/bpcon/bpcon/config/struct.BPConConfig.html)
for more information.

### Create parties

Having `BPConConfig`, `ValueSelector` and `LeaderElector` defined, instantiate your parties.
Check out [new](https://distributed-lab.github.io/bpcon/bpcon/party/struct.Party.html#method.new)
method on a `Party` struct.

### Launch ballot on parties and handle messages

Each party interfaces communication with external system via channels.
In a way, you shall propagate outgoing messages to other parties like:

1. Listen for outgoing message using `msg_out_receiver`.
2. Forward it to other parties using `msg_in_sender`.

We welcome you to check `test_end_to_end_ballot` in `party.rs` for example.
109 changes: 109 additions & 0 deletions src/config.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
//! Definitions central to BPCon configuration.
use std::time::Duration;

/// Configuration structure for BPCon.
///
/// `BPConConfig` holds the various timeouts and weight configurations necessary
/// for the BPCon consensus protocol. This configuration controls the timing
/// of different stages in the protocol and the distribution of party weights.
#[derive(PartialEq, Eq, Debug, Clone)]
pub struct BPConConfig {
/// Parties weights: `party_weights[i]` corresponds to the i-th party's weight.
///
/// These weights determine the influence each party has in the consensus process.
/// The sum of these weights is used to calculate the `threshold` for reaching a
/// Byzantine Fault Tolerant (BFT) quorum.
pub party_weights: Vec<u64>,

/// Threshold weight to define BFT quorum.
///
/// This value must be greater than 2/3 of the total weight of all parties combined.
/// The quorum is the minimum weight required to make decisions in the BPCon protocol.
pub threshold: u128,

/// Timeout before the ballot is launched.
///
/// This timeout differs from `launch1a_timeout` as it applies to a distinct status
/// and does not involve listening to external events and messages.
pub launch_timeout: Duration,

/// Timeout before the 1a stage is launched.
///
/// The 1a stage is the first phase of the BPCon consensus process, where leader
/// is informing other participants about the start of the ballot.
/// This timeout controls the delay before starting this stage.
pub launch1a_timeout: Duration,

/// Timeout before the 1b stage is launched.
///
/// In the 1b stage, participants exchange their last voted ballot number and elected value.
/// This timeout controls the delay before starting this stage.
pub launch1b_timeout: Duration,

/// Timeout before the 2a stage is launched.
///
/// The 2a stage involves leader proposing a selected value and
/// other participants verifying it.
/// This timeout controls the delay before starting this stage.
pub launch2a_timeout: Duration,

/// Timeout before the 2av stage is launched.
///
/// The 2av stage is where 2a obtained value shall gather needed weight to pass.
/// This timeout controls the delay before starting this stage.
pub launch2av_timeout: Duration,

/// Timeout before the 2b stage is launched.
///
/// The 2b stage is where the final value is chosen and broadcasted.
/// This timeout controls the delay before starting this stage.
pub launch2b_timeout: Duration,

/// Timeout before the finalization stage is launched.
///
/// The finalization stage is not a part of protocol, but rather internal-centric mechanics
/// to conclude ballot.
/// This timeout controls the delay before starting this stage.
pub finalize_timeout: Duration,

/// Timeout for a graceful period to accommodate parties with latency.
///
/// This grace period allows parties with slower communication or processing times
/// to catch up, helping to ensure that all parties can participate fully in the
/// consensus process.
pub grace_period: Duration,
}

impl BPConConfig {
/// Create a new `BPConConfig` with default timeouts.
///
/// This method initializes a `BPConConfig` instance using default timeout values for each
/// stage of the BPCon consensus protocol. These defaults are placeholders and should be
/// tuned according to the specific needs and characteristics of the network.
///
/// # Parameters
///
/// - `party_weights`: A vector of weights corresponding to each party involved in the consensus.
/// - `threshold`: The weight threshold required to achieve a BFT quorum.
///
/// # Returns
///
/// A new `BPConConfig` instance with the provided `party_weights` and `threshold`,
/// and default timeouts for all stages.
pub fn with_default_timeouts(party_weights: Vec<u64>, threshold: u128) -> Self {
Self {
party_weights,
threshold,
// TODO: deduce actually good defaults.
launch_timeout: Duration::from_secs(0),
launch1a_timeout: Duration::from_secs(5),
launch1b_timeout: Duration::from_secs(10),
launch2a_timeout: Duration::from_secs(15),
launch2av_timeout: Duration::from_secs(20),
launch2b_timeout: Duration::from_secs(25),
finalize_timeout: Duration::from_secs(30),
grace_period: Duration::from_secs(1),
}
}
}
Loading

0 comments on commit 754e246

Please sign in to comment.