Skip to content

Commit

Permalink
Add skeleton of consensus executor
Browse files Browse the repository at this point in the history
  • Loading branch information
romac committed Oct 20, 2023
1 parent e945368 commit 8ef7115
Show file tree
Hide file tree
Showing 12 changed files with 251 additions and 21 deletions.
1 change: 1 addition & 0 deletions Code/common/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@
name = "malachite-common"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
7 changes: 7 additions & 0 deletions Code/common/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
mod validator_set;

pub use validator_set::{Address, Validator, ValidatorSet, VotingPower};

/// A blockchain height
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord)]
pub struct Height(u64);
Expand Down Expand Up @@ -98,6 +102,7 @@ pub struct Vote {
pub typ: VoteType,
pub round: Round,
pub value: Option<Value>,
// pub address: Address,
}

impl Vote {
Expand All @@ -106,6 +111,7 @@ impl Vote {
typ: VoteType::Prevote,
round,
value,
// address,
}
}

Expand All @@ -114,6 +120,7 @@ impl Vote {
typ: VoteType::Precommit,
round,
value,
// address,
}
}
}
Expand Down
94 changes: 94 additions & 0 deletions Code/common/src/validator_set.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
// TODO: Abstract over all of this

#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] // TODO: Remove PartialOrd, Ord
pub struct PublicKey(Vec<u8>);

pub type VotingPower = u64;

// TODO: Use an actual address
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct Address(PublicKey);

/// A validator is a public key and voting power
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Validator {
pub public_key: PublicKey,
pub voting_power: VotingPower,
}

impl Validator {
pub fn new(public_key: PublicKey, voting_power: VotingPower) -> Self {
Self {
public_key,
voting_power,
}
}

pub fn hash(&self) -> Vec<u8> {
self.public_key.0.clone() // TODO
}

pub fn address(&self) -> Address {
Address(self.public_key.clone()) // TODO
}
}

/// A validator set contains a list of validators sorted by address.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ValidatorSet {
validators: Vec<Validator>,
total_voting_power: VotingPower,
}

impl ValidatorSet {
pub fn new(validators: impl IntoIterator<Item = Validator>) -> Self {
let mut validators: Vec<_> = validators.into_iter().collect();
ValidatorSet::sort_validators(&mut validators);

let total_voting_power = validators.iter().map(|v| v.voting_power).sum();

Self {
validators,
total_voting_power,
}
}

pub fn total_voting_power(&self) -> VotingPower {
self.total_voting_power
}

pub fn add(&mut self, validator: Validator) {
self.validators.push(validator);
ValidatorSet::sort_validators(&mut self.validators);
}

/// Update the voting power of the given validator
pub fn update(&mut self, val: Validator) {
if let Some(v) = self
.validators
.iter_mut()
.find(|v| v.address() == val.address())
{
v.voting_power = val.voting_power;
}

Self::sort_validators(&mut self.validators);
}

pub fn remove(&mut self, val: Validator) {
self.validators.retain(|v| v.address() != val.address());

Self::sort_validators(&mut self.validators); // TODO: Not needed
}

/// In place sort and deduplication of a list of validators
fn sort_validators(vals: &mut Vec<Validator>) {
use core::cmp::Reverse;

// Sort the validators according to the current Tendermint requirements
// (v. 0.34 -> first by validator power, descending, then by address, ascending)
vals.sort_unstable_by_key(|v| (Reverse(v.voting_power), v.address()));

vals.dedup();
}
}
4 changes: 4 additions & 0 deletions Code/consensus/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,9 @@
name = "malachite-consensus"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
malachite-common = { version = "0.1.0", path = "../common" }
malachite-round = { version = "0.1.0", path = "../round" }
malachite-vote = { version = "0.1.0", path = "../vote" }
119 changes: 119 additions & 0 deletions Code/consensus/src/executor.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
use std::collections::BTreeMap;

use malachite_common::{Height, Proposal, Round, Timeout, TimeoutStep, ValidatorSet, Vote};
use malachite_round::events::Event as RoundEvent;
use malachite_round::message::Message as RoundMessage;
use malachite_round::state::State as RoundState;
use malachite_vote::keeper::VoteKeeper;

#[derive(Clone, Debug)]
pub struct Executor {
height: Height,
validator_set: ValidatorSet,

vote_keeper: VoteKeeper,
round_states: BTreeMap<Round, RoundState>,
}

#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Message {
Proposal(Proposal),
Vote(Vote),
Timeout(Timeout),
}

impl Executor {
pub fn new(height: Height, validator_set: ValidatorSet) -> Self {
let vote_keeper = VoteKeeper::new(height, validator_set.total_voting_power());

Self {
height,
validator_set,
vote_keeper,
round_states: BTreeMap::new(),
}
}

pub fn execute(&mut self, msg: Message) {
let msg = match self.apply(msg) {
Some(msg) => msg,
None => return,
};

match msg {
RoundMessage::NewRound(_) => {
// check if we are the proposer
}
RoundMessage::Proposal(_) => {
// sign the proposal
// call execute
}
RoundMessage::Vote(_) => {
// sign the vote
// call execute
}
RoundMessage::Timeout(_) => {
// schedule the timeout
}
RoundMessage::Decision(_) => {
// update the state
}
}
}

fn apply(&mut self, msg: Message) -> Option<RoundMessage> {
match msg {
Message::Proposal(proposal) => self.apply_proposal(proposal),
Message::Vote(vote) => self.apply_vote(vote),
Message::Timeout(timeout) => self.apply_timeout(timeout),
}
}

fn apply_proposal(&mut self, proposal: Proposal) -> Option<RoundMessage> {
// TODO: Check for invalid proposal
let event = RoundEvent::Proposal(proposal.value, Round::new(0));
self.apply_event(proposal.round, event)
}

fn apply_vote(&mut self, vote: Vote) -> Option<RoundMessage> {
// TODO: Get weight
let weight = 1;

let round = vote.round;

let event = match self.vote_keeper.apply_vote(vote, weight) {
Some(event) => event,
None => return None,
};

self.apply_event(round, event)
}

fn apply_timeout(&mut self, timeout: Timeout) -> Option<RoundMessage> {
let event = match timeout.step {
TimeoutStep::Propose => RoundEvent::TimeoutPropose,
TimeoutStep::Prevote => RoundEvent::TimeoutPrevote,
TimeoutStep::Precommit => RoundEvent::TimeoutPrecommit,
};

self.apply_event(timeout.round, event)
}

/// Apply the event, update the state.
fn apply_event(&mut self, round: Round, event: RoundEvent) -> Option<RoundMessage> {
// Get the round state, or create a new one
let round_state = self
.round_states
.remove(&round)
.unwrap_or_else(|| RoundState::new(self.height));

// Apply the event to the round state machine
let transition = round_state.apply_event(round, event);

// Update state
self.round_states.insert(round, transition.state);

// Return message, if any
transition.message
}
}
1 change: 1 addition & 0 deletions Code/consensus/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
pub mod executor;
3 changes: 0 additions & 3 deletions Code/consensus/src/main.rs

This file was deleted.

1 change: 1 addition & 0 deletions Code/round/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "malachite-round"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
malachite-common = { version = "0.1.0", path = "../common" }
6 changes: 6 additions & 0 deletions Code/round/src/state.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use crate::events::Event;
use crate::state_machine::Transition;
use crate::{Height, Round, Value};

/// A value and its associated round
Expand Down Expand Up @@ -83,4 +85,8 @@ impl State {
..self
}
}

pub fn apply_event(self, round: Round, event: Event) -> Transition {
crate::state_machine::apply_event(self, round, event)
}
}
2 changes: 1 addition & 1 deletion Code/round/src/state_machine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ fn is_valid_pol_round(state: &State, round: Round) -> bool {
/// Valid transitions result in at least a change to the state and/or an output message.
///
/// Commented numbers refer to line numbers in the spec paper.
pub fn handle(state: State, round: Round, event: Event) -> Transition {
pub fn apply_event(state: State, round: Round, event: Event) -> Transition {
let this_round = state.round == round;

match (state.step, event) {
Expand Down
1 change: 1 addition & 0 deletions Code/vote/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "malachite-vote"
version = "0.1.0"
edition = "2021"
publish = false

[dependencies]
malachite-common = { version = "0.1.0", path = "../common" }
Expand Down
33 changes: 16 additions & 17 deletions Code/vote/src/keeper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,8 @@ impl VoteKeeper {
}
}

/// Apply a vote. If it triggers an event, apply the event to the state machine,
/// returning the new state and any resulting message.
pub fn apply(&mut self, vote: Vote, weight: Weight) -> Option<Event> {
/// Apply a vote with a given weight, potentially triggering an event.
pub fn apply_vote(&mut self, vote: Vote, weight: Weight) -> Option<Event> {
let round = self
.rounds
.entry(vote.round)
Expand Down Expand Up @@ -76,13 +75,13 @@ mod tests {

let vote = Vote::new_prevote(Round::new(0), None);

let event = keeper.apply(vote.clone(), 1);
let event = keeper.apply_vote(vote.clone(), 1);
assert_eq!(event, None);

let event = keeper.apply(vote.clone(), 1);
let event = keeper.apply_vote(vote.clone(), 1);
assert_eq!(event, None);

let event = keeper.apply(vote, 1);
let event = keeper.apply_vote(vote, 1);
assert_eq!(event, Some(Event::PolkaNil));
}

Expand All @@ -92,13 +91,13 @@ mod tests {

let vote = Vote::new_precommit(Round::new(0), None);

let event = keeper.apply(vote.clone(), 1);
let event = keeper.apply_vote(vote.clone(), 1);
assert_eq!(event, None);

let event = keeper.apply(vote.clone(), 1);
let event = keeper.apply_vote(vote.clone(), 1);
assert_eq!(event, None);

let event = keeper.apply(vote, 1);
let event = keeper.apply_vote(vote, 1);
assert_eq!(event, None);
}

Expand All @@ -110,17 +109,17 @@ mod tests {
let val = Some(v.clone());
let vote = Vote::new_prevote(Round::new(0), val);

let event = keeper.apply(vote.clone(), 1);
let event = keeper.apply_vote(vote.clone(), 1);
assert_eq!(event, None);

let event = keeper.apply(vote.clone(), 1);
let event = keeper.apply_vote(vote.clone(), 1);
assert_eq!(event, None);

let vote_nil = Vote::new_prevote(Round::new(0), None);
let event = keeper.apply(vote_nil, 1);
let event = keeper.apply_vote(vote_nil, 1);
assert_eq!(event, Some(Event::PolkaAny));

let event = keeper.apply(vote, 1);
let event = keeper.apply_vote(vote, 1);
assert_eq!(event, Some(Event::PolkaValue(v)));
}

Expand All @@ -132,17 +131,17 @@ mod tests {
let val = Some(v.clone());
let vote = Vote::new_precommit(Round::new(0), val);

let event = keeper.apply(vote.clone(), 1);
let event = keeper.apply_vote(vote.clone(), 1);
assert_eq!(event, None);

let event = keeper.apply(vote.clone(), 1);
let event = keeper.apply_vote(vote.clone(), 1);
assert_eq!(event, None);

let vote_nil = Vote::new_precommit(Round::new(0), None);
let event = keeper.apply(vote_nil, 1);
let event = keeper.apply_vote(vote_nil, 1);
assert_eq!(event, Some(Event::PrecommitAny));

let event = keeper.apply(vote, 1);
let event = keeper.apply_vote(vote, 1);
assert_eq!(event, Some(Event::PrecommitValue(v)));
}
}

0 comments on commit 8ef7115

Please sign in to comment.