use malachitebft_core_types::{Context, NilOrVal, Proposal, Round, TimeoutKind, Value};
use crate::debug_trace;
use crate::input::Input;
use crate::output::Output;
use crate::state::{State, Step};
use crate::transition::Transition;
pub struct Info<'a, Ctx>
where
Ctx: Context,
{
pub input_round: Round,
pub address: &'a Ctx::Address,
pub proposer: &'a Ctx::Address,
}
impl<'a, Ctx> Info<'a, Ctx>
where
Ctx: Context,
{
pub fn new(input_round: Round, address: &'a Ctx::Address, proposer: &'a Ctx::Address) -> Self {
Self {
input_round,
address,
proposer,
}
}
pub fn new_proposer(input_round: Round, address: &'a Ctx::Address) -> Self {
Self {
input_round,
address,
proposer: address,
}
}
pub fn is_proposer(&self) -> bool {
self.address == self.proposer
}
}
fn is_valid_pol_round<Ctx>(state: &State<Ctx>, pol_round: Round) -> bool
where
Ctx: Context,
{
pol_round.is_defined() && pol_round < state.round
}
pub fn apply<Ctx>(
ctx: &Ctx,
mut state: State<Ctx>,
info: &Info<Ctx>,
input: Input<Ctx>,
) -> Transition<Ctx>
where
Ctx: Context,
{
let this_round = state.round == info.input_round;
match (state.step, input) {
(Step::Unstarted, Input::NewRound(round)) if info.is_proposer() => {
state.round = round;
debug_trace!(state, Line::L11Proposer);
propose_valid_or_get_value(ctx, state, info.address)
}
(Step::Unstarted, Input::NewRound(round)) => {
state.round = round;
debug_trace!(state, Line::L11NonProposer);
schedule_timeout_propose(state)
}
(Step::Propose, Input::ProposeValue(value)) if this_round => {
debug_assert!(info.is_proposer());
propose(ctx, state, value, info.address)
}
(Step::Propose, Input::Proposal(proposal))
if this_round && proposal.pol_round().is_nil() =>
{
debug_trace!(state, Line::L22);
prevote(ctx, state, info.address, &proposal)
}
(Step::Propose, Input::InvalidProposal) if this_round => {
prevote_nil(ctx, state, info.address)
}
(Step::Propose, Input::ProposalAndPolkaPrevious(proposal))
if this_round && is_valid_pol_round(&state, proposal.pol_round()) =>
{
debug_trace!(state, Line::L28ValidProposal);
prevote_previous(ctx, state, info.address, &proposal)
}
(Step::Propose, Input::InvalidProposalAndPolkaPrevious(proposal))
if this_round && is_valid_pol_round(&state, proposal.pol_round()) =>
{
debug_trace!(state, Line::L28InvalidProposal);
debug_trace!(state, Line::L32InvalidValue);
prevote_nil(ctx, state, info.address)
}
(Step::Propose, Input::TimeoutPropose) if this_round && info.is_proposer() => {
debug_trace!(state, Line::L59Proposer);
prevote_nil(ctx, state, info.address)
}
(Step::Propose, Input::TimeoutPropose) if this_round => {
debug_trace!(state, Line::L59NonProposer);
prevote_nil(ctx, state, info.address)
}
(Step::Prevote, Input::PolkaAny) if this_round => {
debug_trace!(state, Line::L34);
debug_trace!(state, Line::L35);
schedule_timeout_prevote(state)
}
(Step::Prevote, Input::PolkaNil) if this_round => {
debug_trace!(state, Line::L45);
precommit_nil(ctx, state, info.address)
}
(Step::Prevote, Input::ProposalAndPolkaCurrent(proposal)) if this_round => {
debug_trace!(state, Line::L36ValidProposal);
precommit(ctx, state, info.address, proposal)
}
(Step::Prevote, Input::TimeoutPrevote) if this_round => {
debug_trace!(state, Line::L61);
precommit_nil(ctx, state, info.address)
}
(Step::Precommit, Input::ProposalAndPolkaCurrent(proposal)) if this_round => {
debug_trace!(state, Line::L36ValidProposal);
set_valid_value(state, &proposal)
}
(Step::Commit, _) => Transition::invalid(state),
(_, Input::PrecommitAny) if this_round => {
debug_trace!(state, Line::L48);
schedule_timeout_precommit(state)
}
(_, Input::TimeoutPrecommit) if this_round => {
debug_trace!(state, Line::L67);
round_skip(state, info.input_round.increment())
}
(_, Input::SkipRound(round)) if state.round < round => {
debug_trace!(state, Line::L55);
round_skip(state, round)
}
(_, Input::ProposalAndPrecommitValue(proposal)) => {
let round = state.round;
debug_trace!(state, Line::L49);
commit(state, round, proposal)
}
_ => Transition::invalid(state),
}
}
pub fn propose_valid_or_get_value<Ctx>(
ctx: &Ctx,
mut state: State<Ctx>,
address: &Ctx::Address,
) -> Transition<Ctx>
where
Ctx: Context,
{
debug_trace!(state, Line::L14);
match &state.valid {
Some(round_value) => {
let pol_round = round_value.round;
let proposal = Output::proposal(
ctx,
state.height,
state.round,
round_value.value.clone(),
pol_round,
address.clone(),
);
debug_trace!(state, Line::L16);
debug_trace!(state, Line::L19);
Transition::to(state.with_step(Step::Propose)).with_output(proposal)
}
None => {
let output = Output::get_value_and_schedule_timeout(
state.height,
state.round,
TimeoutKind::Propose,
);
debug_trace!(state, Line::L18);
Transition::to(state.with_step(Step::Propose)).with_output(output)
}
}
}
pub fn propose<Ctx>(
ctx: &Ctx,
mut state: State<Ctx>,
value: Ctx::Value,
address: &Ctx::Address,
) -> Transition<Ctx>
where
Ctx: Context,
{
let proposal = Output::proposal(
ctx,
state.height,
state.round,
value,
Round::Nil,
address.clone(),
);
debug_trace!(state, Line::L19);
Transition::to(state.with_step(Step::Propose)).with_output(proposal)
}
pub fn prevote<Ctx>(
ctx: &Ctx,
mut state: State<Ctx>,
address: &Ctx::Address,
proposal: &Ctx::Proposal,
) -> Transition<Ctx>
where
Ctx: Context,
{
let vr = proposal.pol_round();
assert_eq!(vr, Round::Nil);
let proposed = proposal.value().id();
let value = match &state.locked {
Some(locked) if locked.value.id() == proposed => {
debug_trace!(state, Line::L24ValidAndLockedValue);
NilOrVal::Val(proposed)
}
Some(_) => {
debug_trace!(state, Line::L26ValidAndLockedValue);
NilOrVal::Nil
}
None => {
debug_trace!(state, Line::L24ValidNoLockedRound);
NilOrVal::Val(proposed)
}
};
let output = Output::prevote(ctx, state.height, state.round, value, address.clone());
Transition::to(state.with_step(Step::Prevote)).with_output(output)
}
pub fn prevote_previous<Ctx>(
ctx: &Ctx,
mut state: State<Ctx>,
address: &Ctx::Address,
proposal: &Ctx::Proposal,
) -> Transition<Ctx>
where
Ctx: Context,
{
let vr = proposal.pol_round();
assert!(vr >= Round::Some(0));
assert!(vr < proposal.round());
let proposed = proposal.value().id();
let value = match &state.locked {
Some(locked) if locked.round <= vr => {
debug_trace!(state, Line::L30ValidLockedRound);
NilOrVal::Val(proposed)
}
Some(locked) if locked.value.id() == proposed => {
debug_trace!(state, Line::L30ValidLockedValue);
NilOrVal::Val(proposed)
}
Some(_) => {
debug_trace!(state, Line::L32InvalidValue);
NilOrVal::Nil
}
None => {
debug_trace!(state, Line::L30ValidNoLockedRound);
NilOrVal::Val(proposed)
}
};
let output = Output::Vote(ctx.new_prevote(state.height, state.round, value, address.clone()));
Transition::to(state.with_step(Step::Prevote)).with_output(output)
}
pub fn prevote_nil<Ctx>(ctx: &Ctx, state: State<Ctx>, address: &Ctx::Address) -> Transition<Ctx>
where
Ctx: Context,
{
let output =
Output::Vote(ctx.new_prevote(state.height, state.round, NilOrVal::Nil, address.clone()));
Transition::to(state.with_step(Step::Prevote)).with_output(output)
}
pub fn precommit<Ctx>(
ctx: &Ctx,
state: State<Ctx>,
address: &Ctx::Address,
proposal: Ctx::Proposal,
) -> Transition<Ctx>
where
Ctx: Context,
{
if state.step != Step::Prevote {
return Transition::to(state);
}
let value = proposal.value();
let output = Output::precommit(
ctx,
state.height,
state.round,
NilOrVal::Val(value.id()),
address.clone(),
);
let next = state
.set_locked(value.clone())
.set_valid(value.clone())
.with_step(Step::Precommit);
Transition::to(next).with_output(output)
}
pub fn precommit_nil<Ctx>(ctx: &Ctx, state: State<Ctx>, address: &Ctx::Address) -> Transition<Ctx>
where
Ctx: Context,
{
let output =
Output::Vote(ctx.new_precommit(state.height, state.round, NilOrVal::Nil, address.clone()));
Transition::to(state.with_step(Step::Precommit)).with_output(output)
}
pub fn schedule_timeout_propose<Ctx>(mut state: State<Ctx>) -> Transition<Ctx>
where
Ctx: Context,
{
debug_trace!(state, Line::L21ProposeTimeoutScheduled);
let timeout = Output::schedule_timeout(state.round, TimeoutKind::Propose);
Transition::to(state.with_step(Step::Propose)).with_output(timeout)
}
pub fn schedule_timeout_prevote<Ctx>(state: State<Ctx>) -> Transition<Ctx>
where
Ctx: Context,
{
let output = Output::schedule_timeout(state.round, TimeoutKind::Prevote);
Transition::to(state).with_output(output)
}
pub fn schedule_timeout_precommit<Ctx>(state: State<Ctx>) -> Transition<Ctx>
where
Ctx: Context,
{
let output = Output::schedule_timeout(state.round, TimeoutKind::Precommit);
Transition::to(state).with_output(output)
}
pub fn set_valid_value<Ctx>(state: State<Ctx>, proposal: &Ctx::Proposal) -> Transition<Ctx>
where
Ctx: Context,
{
Transition::to(state.set_valid(proposal.value().clone()))
}
pub fn round_skip<Ctx>(state: State<Ctx>, round: Round) -> Transition<Ctx>
where
Ctx: Context,
{
let new_state = state.with_round(round).with_step(Step::Unstarted);
Transition::to(new_state).with_output(Output::NewRound(round))
}
pub fn commit<Ctx>(state: State<Ctx>, round: Round, proposal: Ctx::Proposal) -> Transition<Ctx>
where
Ctx: Context,
{
match &state.decision {
Some(decision) if decision.value.id() == proposal.value().id() => Transition::to(state),
Some(_) => Transition::invalid(state),
None => {
let new_state = state
.set_decision(proposal.round(), proposal.value().clone())
.with_step(Step::Commit);
let output = Output::decision(round, proposal.clone());
Transition::to(new_state).with_output(output)
}
}
}