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

code: Initial implementation of the state machine, vote keeper and driver #1

Merged
merged 35 commits into from
Nov 8, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
14ec0f1
Add paper
romac Oct 19, 2023
9c0a24f
Add round state machine based on Agnes
romac Oct 19, 2023
90745f3
Move common code to common crate
romac Oct 19, 2023
6637021
Prefix crate names with `malachite-`
romac Oct 19, 2023
96e9e1f
Formatting
romac Oct 19, 2023
506ce1b
Fix clippy
romac Oct 19, 2023
558ab41
Formatting
romac Oct 19, 2023
98b1203
Formatting
romac Oct 19, 2023
dd0dbdc
Add vote counting facility (#3)
romac Oct 19, 2023
e4059de
Add a vote keeper (#4)
romac Oct 21, 2023
ccc12e6
Add a consensus executor (#5)
romac Oct 24, 2023
3fea113
Abstract over the various data types needed by the consensus engine (#8)
romac Oct 25, 2023
7365b13
Fix typo in Rust workflows
romac Oct 25, 2023
6ffff0c
Fix workflows
romac Oct 25, 2023
cb1e259
Refactor `VoteCount` (#15)
romac Oct 25, 2023
4968797
Merge branch 'main' into romac/rust-state-machine
romac Oct 25, 2023
5e5d175
Use different types for the executors inputs and outputs (#17)
romac Oct 27, 2023
39a2041
executor: Sign votes and verify votes signatures (#16)
romac Oct 27, 2023
69da637
Add adr template. (#31)
ancazamfir Oct 27, 2023
88c015a
executor: Cleanup input/output names and align `VoteKeeper` to it (#34)
romac Oct 30, 2023
98d2b63
common: Rename `Consensus` trait to `Context` (#35)
romac Oct 30, 2023
e46a95b
common: Move address from `SignedVote` to `Vote` (#36)
romac Oct 31, 2023
d39d681
Rename `TestConsensus` to `TestContext`
romac Nov 1, 2023
10677dc
Rename timeout-related variants for more clarity
romac Nov 1, 2023
5cdc87c
Improve `ValidatorSet::get_proposer`
romac Nov 3, 2023
828f7af
Avoid cloning the proposal
romac Nov 3, 2023
f53355f
code: Events and SM improvements (#19)
romac Nov 7, 2023
52d51a4
vote: Complete vote keeper (#39)
romac Nov 7, 2023
d79ecec
Add `Client` for getting a value and validate a proposal (#43)
romac Nov 7, 2023
4e8edda
Use workspace depdencies and common settings
romac Nov 7, 2023
d517da1
Remove `Round::is_valid()` because rounds are valid by construction
romac Nov 7, 2023
45d4575
Remove `Value::is_valid` as proposal validity is now handled outside …
romac Nov 7, 2023
672be3b
Rename `Executor` to `Driver` (#50)
romac Nov 7, 2023
7130b59
Add codepath and test for invalid proposal (#52)
romac Nov 7, 2023
62becad
Add expected round to test steps (#53)
romac Nov 8, 2023
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
9 changes: 5 additions & 4 deletions .github/workflows/coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: Coverage

on:
push:
branches: master
branches: main
paths:
- Code/**
pull_request:
Expand All @@ -12,6 +12,9 @@ on:
jobs:
coverage:
runs-on: ubuntu-latest
defaults:
run:
working-directory: Code
env:
CARGO_TERM_COLOR: always
steps:
Expand All @@ -26,11 +29,9 @@ jobs:
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@cargo-llvm-cov
- name: Generate code coverage
working-directory: Code
run: cargo llvm-cov nextest --all-features --workspace --lcov --output-path lcov.info
- name: Generate text report
working-directory: Code
run: cargo llvm-cov report --text
run: cargo llvm-cov report
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
Expand Down
14 changes: 6 additions & 8 deletions .github/workflows/rust.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,13 @@ name: Rust

on:
push:
branches: master
branches: main
paths:
- Code/**
pull_request:
paths:
- Code/**

# permissions:
# checks: write

env:
CARGO_INCREMENTAL: 0
CARGO_PROFILE_DEV_DEBUG: 1
Expand All @@ -24,6 +21,9 @@ jobs:
test:
name: Test
runs-on: ubuntu-latest
defaults:
run:
working-directory: Code
steps:
- name: Checkout
uses: actions/checkout@v4
Expand All @@ -32,10 +32,8 @@ jobs:
- name: Install cargo-nextest
uses: taiki-e/install-action@cargo-nextest
- name: Build code
working-directory: Code
run: cargo nextest run --workspace --all-features --no-run
- name: Run tests
working-directory: Code
run: cargo nextest run --workspace --all-features

clippy:
Expand All @@ -49,10 +47,10 @@ jobs:
with:
components: clippy
- name: Run clippy
uses: auguwu/clippy-action@1.3.0
uses: actions-rs/clippy@master
with:
token: ${{secrets.GITHUB_TOKEN}}
args: --manifest-path Code/Cargo.toml
args: --all-features --all-targets --manifest-path Code/Cargo.toml

fmt:
name: Formatting
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# macOS Finder and Windows Thumbs.db files
.DS_Store
Thumbs.db

# Generated by Cargo
# will have compiled files and executables
debug/
Expand Down
Empty file removed Code/.gitkeep
Empty file.
22 changes: 21 additions & 1 deletion Code/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,24 @@
[workspace]
resolver = "2"

members = []
members = [
"common",
"driver",
"round",
"vote",
"test",
]

[workspace.package]
version = "0.1.0"
edition = "2021"
repository = "https://github.com/informalsystems/malachite"
license = "Apache-2.0"
publish = false

[workspace.dependencies]
ed25519-consensus = "2.1.0"
rand = { version = "0.8.5", features = ["std_rng"] }
secrecy = "0.8.0"
sha2 = "0.10.8"
signature = "2.1.0"
1 change: 1 addition & 0 deletions Code/QUESTIONS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- How do we deal with errors?
11 changes: 11 additions & 0 deletions Code/TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
is proposal complete
if polka not nil, then need to see proof of lock (2f+1 votes)
then send proposal

count votes for cur, prev, 1/2 next round

if complete proposal from a past round => to current one
if we have some threshold (f+1) of votes for a future round => skip to that round

context (get proposer, get value)
signing context
13 changes: 13 additions & 0 deletions Code/common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[package]
name = "malachite-common"
description = "Common datatypes and interfaces for the Malachite consensus engine"

version.workspace = true
edition.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true

[dependencies]
secrecy.workspace = true
signature.workspace = true
55 changes: 55 additions & 0 deletions Code/common/src/context.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
use crate::{
Address, Height, PrivateKey, Proposal, PublicKey, Round, Signature, SignedVote, SigningScheme,
Validator, ValidatorSet, Value, ValueId, Vote,
};

/// This trait allows to abstract over the various datatypes
/// that are used in the consensus engine.
pub trait Context
where
Self: Sized,
{
type Address: Address;
type Height: Height;
type Proposal: Proposal<Self>;
type Validator: Validator<Self>;
type ValidatorSet: ValidatorSet<Self>;
type Value: Value;
type Vote: Vote<Self>;
type SigningScheme: SigningScheme; // TODO: Do we need to support multiple signing schemes?

// FIXME: Remove altogether
const DUMMY_VALUE: Self::Value;

/// Sign the given vote using the given private key.
/// TODO: Maybe move this as concrete methods in `SignedVote`?
fn sign_vote(vote: &Self::Vote, private_key: &PrivateKey<Self>) -> Signature<Self>;

/// Verify the given vote's signature using the given public key.
/// TODO: Maybe move this as concrete methods in `SignedVote`?
fn verify_signed_vote(signed_vote: &SignedVote<Self>, public_key: &PublicKey<Self>) -> bool;

/// Build a new proposal for the given value at the given height, round and POL round.
fn new_proposal(
height: Self::Height,
round: Round,
value: Self::Value,
pol_round: Round,
) -> Self::Proposal;

/// Build a new prevote vote by the validator with the given address,
/// for the value identified by the given value id, at the given round.
fn new_prevote(
round: Round,
value_id: Option<ValueId<Self>>,
address: Self::Address,
) -> Self::Vote;

/// Build a new precommit vote by the validator with the given address,
/// for the value identified by the given value id, at the given round.
fn new_precommit(
round: Round,
value_id: Option<ValueId<Self>>,
address: Self::Address,
) -> Self::Vote;
}
14 changes: 14 additions & 0 deletions Code/common/src/height.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use core::fmt::Debug;

// TODO: Keep the trait or just add the bounds to Consensus::Height?
/// Defines the requirements for a height type.
///
/// A height denotes the number of blocks (values) created since the chain began.
///
/// A height of 0 represents a chain which has not yet produced a block.
pub trait Height
where
// TODO: Require Copy as well?
Self: Clone + Debug + PartialEq + Eq + PartialOrd + Ord,
{
}
43 changes: 43 additions & 0 deletions Code/common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//! Common data types and abstractions for the consensus engine.

#![no_std]
#![forbid(unsafe_code)]
#![deny(unused_crate_dependencies, trivial_casts, trivial_numeric_casts)]
#![warn(
// missing_docs,
rustdoc::broken_intra_doc_links,
rustdoc::private_intra_doc_links,
variant_size_differences
)]
#![cfg_attr(not(test), deny(clippy::unwrap_used, clippy::panic))]

mod context;
mod height;
mod proposal;
mod round;
mod signed_vote;
mod signing;
mod timeout;
mod validator_set;
mod value;
mod vote;

// Re-export `signature` crate for convenience
pub use ::signature;

/// Type alias to make it easier to refer the `ValueId` type of a given `Consensus` engine.
pub type ValueId<Ctx> = <<Ctx as Context>::Value as Value>::Id;
pub type PublicKey<Ctx> = <<Ctx as Context>::SigningScheme as SigningScheme>::PublicKey;
pub type PrivateKey<Ctx> = <<Ctx as Context>::SigningScheme as SigningScheme>::PrivateKey;
pub type Signature<Ctx> = <<Ctx as Context>::SigningScheme as SigningScheme>::Signature;

pub use context::Context;
pub use height::Height;
pub use proposal::Proposal;
pub use round::Round;
pub use signed_vote::SignedVote;
pub use signing::SigningScheme;
pub use timeout::{Timeout, TimeoutStep};
pub use validator_set::{Address, Validator, ValidatorSet, VotingPower};
pub use value::Value;
pub use vote::{Vote, VoteType};
22 changes: 22 additions & 0 deletions Code/common/src/proposal.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
use core::fmt::Debug;

use crate::{Context, Round};

/// Defines the requirements for a proposal type.
pub trait Proposal<Ctx>
where
Self: Clone + Debug + PartialEq + Eq,
Ctx: Context,
{
/// The height for which the proposal is for.
fn height(&self) -> Ctx::Height;

/// The round for which the proposal is for.
fn round(&self) -> Round;

/// The value that is proposed.
fn value(&self) -> &Ctx::Value;

/// The POL round for which the proposal is for.
fn pol_round(&self) -> Round;
}
104 changes: 104 additions & 0 deletions Code/common/src/round.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use core::cmp;

/// A round number.
///
/// Can be either:
/// - `Round::Nil` (ie. `-1`)
/// - `Round::Some(r)` where `r >= 0`
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
pub enum Round {
/// No round, ie. `-1`
Nil,

/// Some round `r` where `r >= 0`
Some(i64),
}

impl Round {
/// The initial, zero round.
pub const INITIAL: Round = Round::new(0);
pub const NIL: Round = Round::new(-1);

/// Create a new round.
///
/// If `round < 0`, then `Round::Nil` is returned.
/// Otherwise, `Round::Some(round)` is returned.
pub const fn new(round: i64) -> Self {
if round < 0 {
Self::Nil
} else {
Self::Some(round)
}
}

/// Convert the round to an `i64`.
///
/// `Round::Nil` is converted to `-1`.
/// `Round::Some(r)` is converted to `r`.
pub fn as_i64(&self) -> i64 {
match self {
Round::Nil => -1,
Round::Some(r) => *r,
}
}

/// Wether the round is defined, ie. `Round::Some(r)` where `r >= 0`.
pub fn is_defined(&self) -> bool {
matches!(self, Round::Some(r) if *r >= 0)
}

/// Wether the round is `Round::Nil`.
pub fn is_nil(&self) -> bool {
matches!(self, Round::Nil)
}

/// Increment the round.
///
/// If the round is nil, then the initial zero round is returned.
/// Otherwise, the round is incremented by one.
pub fn increment(&self) -> Round {
match self {
Round::Nil => Round::new(0),
Round::Some(r) => Round::new(r + 1),
}
}
}

impl PartialOrd for Round {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}

impl Ord for Round {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.as_i64().cmp(&other.as_i64())
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_round() {
// Test Round::new()
assert_eq!(Round::new(-42), Round::Nil);
assert_eq!(Round::new(-1), Round::Nil);
assert_eq!(Round::new(0), Round::Some(0));
assert_eq!(Round::new(1), Round::Some(1));
assert_eq!(Round::new(2), Round::Some(2));

// Test Round::as_i64()
assert_eq!(Round::Nil.as_i64(), -1);
assert_eq!(Round::Some(0).as_i64(), 0);
assert_eq!(Round::Some(1).as_i64(), 1);
assert_eq!(Round::Some(2).as_i64(), 2);

// Test Round::is_defined()
assert!(!Round::Nil.is_defined());
assert!(Round::Some(0).is_defined());
assert!(Round::Some(1).is_defined());
assert!(Round::Some(2).is_defined());
}
}
Loading
Loading