Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: tests and docs #9

Merged
merged 9 commits into from
Sep 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading