-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* 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
1 parent
4322a91
commit 754e246
Showing
9 changed files
with
1,634 additions
and
967 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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), | ||
} | ||
} | ||
} |
Oops, something went wrong.