use core::fmt::Debug;
use std::{
sync::Arc,
time::{UNIX_EPOCH, SystemTime, Instant, Duration},
collections::HashMap,
};
use parity_scale_codec::{Encode, Decode};
use tokio::{
task::{JoinHandle, yield_now},
sync::mpsc::{self, error::TryRecvError},
time::sleep,
};
pub mod ext;
use ext::*;
mod message_log;
use message_log::MessageLog;
pub(crate) fn commit_msg(end_time: u64, id: &[u8]) -> Vec<u8> {
[&end_time.to_le_bytes(), id].concat().to_vec()
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Encode, Decode)]
enum Step {
Propose,
Prevote,
Precommit,
}
#[derive(Clone, Debug, Encode, Decode)]
enum Data<B: Block, S: Signature> {
Proposal(Option<Round>, B),
Prevote(Option<B::Id>),
Precommit(Option<(B::Id, S)>),
}
impl<B: Block, S: Signature> PartialEq for Data<B, S> {
fn eq(&self, other: &Data<B, S>) -> bool {
match (self, other) {
(Data::Proposal(r, b), Data::Proposal(r2, b2)) => (r == r2) && (b == b2),
(Data::Prevote(i), Data::Prevote(i2)) => i == i2,
(Data::Precommit(None), Data::Precommit(None)) => true,
(Data::Precommit(Some((i, _))), Data::Precommit(Some((i2, _)))) => i == i2,
_ => false,
}
}
}
impl<B: Block, S: Signature> Data<B, S> {
fn step(&self) -> Step {
match self {
Data::Proposal(..) => Step::Propose,
Data::Prevote(..) => Step::Prevote,
Data::Precommit(..) => Step::Precommit,
}
}
}
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
struct Message<V: ValidatorId, B: Block, S: Signature> {
sender: V,
number: BlockNumber,
round: Round,
data: Data<B, S>,
}
#[derive(Clone, PartialEq, Debug, Encode, Decode)]
pub struct SignedMessage<V: ValidatorId, B: Block, S: Signature> {
msg: Message<V, B, S>,
sig: S,
}
impl<V: ValidatorId, B: Block, S: Signature> SignedMessage<V, B, S> {
pub fn number(&self) -> BlockNumber {
self.msg.number
}
#[must_use]
pub fn verify_signature<Scheme: SignatureScheme<ValidatorId = V, Signature = S>>(
&self,
signer: &Scheme,
) -> bool {
signer.verify(self.msg.sender, &self.msg.encode(), &self.sig)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
enum TendermintError<V: ValidatorId> {
Malicious(V),
Temporal,
}
pub struct TendermintMachine<N: Network> {
network: N,
signer: <N::SignatureScheme as SignatureScheme>::Signer,
validators: N::SignatureScheme,
weights: Arc<N::Weights>,
validator_id: N::ValidatorId,
number: BlockNumber,
canonical_start_time: u64,
start_time: Instant,
personal_proposal: N::Block,
queue: Vec<(
bool,
Message<N::ValidatorId, N::Block, <N::SignatureScheme as SignatureScheme>::Signature>,
)>,
log: MessageLog<N>,
round: Round,
end_time: HashMap<Round, Instant>,
step: Step,
locked: Option<(Round, <N::Block as Block>::Id)>,
valid: Option<(Round, N::Block)>,
timeouts: HashMap<Step, Instant>,
}
pub struct TendermintHandle<N: Network> {
pub messages: mpsc::Sender<
SignedMessage<N::ValidatorId, N::Block, <N::SignatureScheme as SignatureScheme>::Signature>,
>,
pub handle: JoinHandle<()>,
}
impl<N: Network + 'static> TendermintMachine<N> {
fn canonical_end_time(&self, round: Round) -> u64 {
let mut time = self.canonical_start_time;
for r in 0 .. u64::from(round.0 + 1) {
time += (r + 1) * u64::from(N::BLOCK_TIME);
}
time
}
fn timeout(&self, step: Step) -> Instant {
let mut round_time = Duration::from_secs(N::BLOCK_TIME.into());
round_time *= self.round.0 + 1;
let step_time = round_time / 3;
let offset = match step {
Step::Propose => step_time,
Step::Prevote => step_time * 2,
Step::Precommit => step_time * 3,
};
self.start_time + offset
}
fn broadcast(
&mut self,
data: Data<N::Block, <N::SignatureScheme as SignatureScheme>::Signature>,
) {
let step = data.step();
self.step = step;
self.queue.push((
true,
Message { sender: self.validator_id, number: self.number, round: self.round, data },
));
}
fn round_propose(&mut self) -> bool {
if self.weights.proposer(self.number, self.round) == self.validator_id {
let (round, block) = self
.valid
.clone()
.map(|(r, b)| (Some(r), b))
.unwrap_or((None, self.personal_proposal.clone()));
self.broadcast(Data::Proposal(round, block));
true
} else {
self.timeouts.insert(Step::Propose, self.timeout(Step::Propose));
false
}
}
fn round(&mut self, round: Round) -> bool {
for r in self.round.0 .. round.0 {
let end = self.timeout(Step::Precommit);
self.end_time.insert(Round(r), end);
self.start_time = end;
}
self.timeouts = HashMap::new();
self.round = round;
self.end_time.insert(round, self.timeout(Step::Precommit));
self.step = Step::Propose;
self.round_propose()
}
async fn reset(&mut self, end_round: Round, proposal: N::Block) {
let round_end = self.end_time[&end_round];
sleep(round_end.saturating_duration_since(Instant::now())).await;
self.validator_id = self.signer.validator_id().await;
self.number.0 += 1;
self.canonical_start_time = self.canonical_end_time(end_round);
self.start_time = round_end;
self.personal_proposal = proposal;
self.queue = self.queue.drain(..).filter(|msg| msg.1.number == self.number).collect();
self.log = MessageLog::new(self.weights.clone());
self.end_time = HashMap::new();
self.locked = None;
self.valid = None;
self.round(Round(0));
}
#[allow(clippy::new_ret_no_self)]
pub fn new(network: N, last: (BlockNumber, u64), proposal: N::Block) -> TendermintHandle<N> {
let (msg_send, mut msg_recv) = mpsc::channel(100); TendermintHandle {
messages: msg_send,
handle: tokio::spawn(async move {
let last_end = UNIX_EPOCH + Duration::from_secs(last.1);
{
let now = SystemTime::now();
if last_end > now {
sleep(last_end.duration_since(now).unwrap_or(Duration::ZERO)).await;
}
}
let last_time = {
let instant_now = Instant::now();
let sys_now = SystemTime::now();
instant_now - sys_now.duration_since(last_end).unwrap_or(Duration::ZERO)
};
let signer = network.signer();
let validators = network.signature_scheme();
let weights = Arc::new(network.weights());
let validator_id = signer.validator_id().await;
let mut machine = TendermintMachine {
network,
signer,
validators,
weights: weights.clone(),
validator_id,
number: BlockNumber(last.0 .0 + 1),
canonical_start_time: last.1,
start_time: last_time,
personal_proposal: proposal,
queue: vec![],
log: MessageLog::new(weights),
round: Round(0),
end_time: HashMap::new(),
step: Step::Propose,
locked: None,
valid: None,
timeouts: HashMap::new(),
};
machine.round(Round(0));
loop {
let now = Instant::now();
let (t1, t2, t3) = {
let ready = |step| machine.timeouts.get(&step).unwrap_or(&now) < &now;
(ready(Step::Propose), ready(Step::Prevote), ready(Step::Precommit))
};
if t1 && (machine.step == Step::Propose) {
machine.broadcast(Data::Prevote(None));
}
if t2 && (machine.step == Step::Prevote) {
machine.broadcast(Data::Precommit(None));
}
if t3 {
machine.round(Round(machine.round.0.wrapping_add(1)));
}
let mut broken = false;
loop {
match msg_recv.try_recv() {
Ok(msg) => {
if !msg.verify_signature(&machine.validators) {
continue;
}
machine.queue.push((false, msg.msg));
}
Err(TryRecvError::Empty) => break,
Err(TryRecvError::Disconnected) => broken = true,
}
}
if broken {
break;
}
let mut queue = machine.queue.drain(..).collect::<Vec<_>>();
for (broadcast, msg) in queue.drain(..) {
let res = machine.message(msg.clone()).await;
if res.is_err() && broadcast {
panic!("honest node had invalid behavior");
}
match res {
Ok(None) => (),
Ok(Some(block)) => {
let mut validators = vec![];
let mut sigs = vec![];
for (v, sig) in machine.log.precommitted.iter().filter_map(|(k, (id, sig))| {
Some((*k, sig.clone())).filter(|_| id == &block.id())
}) {
validators.push(v);
sigs.push(sig);
}
let commit = Commit {
end_time: machine.canonical_end_time(msg.round),
validators,
signature: N::SignatureScheme::aggregate(&sigs),
};
debug_assert!(machine.network.verify_commit(block.id(), &commit));
let proposal = machine.network.add_block(block, commit).await;
machine.reset(msg.round, proposal).await;
}
Err(TendermintError::Malicious(validator)) => {
machine.network.slash(validator).await;
}
Err(TendermintError::Temporal) => (),
}
if broadcast {
let sig = machine.signer.sign(&msg.encode()).await;
machine.network.broadcast(SignedMessage { msg, sig }).await;
}
}
yield_now().await;
}
}),
}
}
async fn message(
&mut self,
msg: Message<N::ValidatorId, N::Block, <N::SignatureScheme as SignatureScheme>::Signature>,
) -> Result<Option<N::Block>, TendermintError<N::ValidatorId>> {
if msg.number != self.number {
Err(TendermintError::Temporal)?;
}
if let Data::Precommit(Some((id, sig))) = &msg.data {
if !self.validators.verify(
msg.sender,
&commit_msg(self.canonical_end_time(msg.round), id.as_ref()),
sig,
) {
Err(TendermintError::Malicious(msg.sender))?;
}
}
if matches!(msg.data, Data::Proposal(..)) &&
(msg.sender != self.weights.proposer(msg.number, msg.round))
{
Err(TendermintError::Malicious(msg.sender))?;
};
if !self.log.log(msg.clone())? {
return Ok(None);
}
if matches!(msg.data, Data::Proposal(..)) || matches!(msg.data, Data::Precommit(_)) {
let proposer = self.weights.proposer(self.number, msg.round);
if let Some(Data::Proposal(_, block)) = self.log.get(msg.round, proposer, Step::Propose) {
if self.log.has_consensus(
msg.round,
Data::Precommit(Some((block.id(), self.signer.sign(&[]).await))),
) {
return Ok(Some(block.clone()));
}
}
}
#[allow(clippy::comparison_chain)]
if msg.round.0 < self.round.0 {
return Ok(None);
} else if msg.round.0 > self.round.0 {
if self.log.round_participation(self.round) > self.weights.fault_thresold() {
if self.round(msg.round) {
return Ok(None);
}
} else {
return Ok(None);
}
}
if (self.step == Step::Prevote) && matches!(msg.data, Data::Prevote(_)) {
let (participation, weight) = self.log.message_instances(self.round, Data::Prevote(None));
if participation >= self.weights.threshold() {
let timeout = self.timeout(Step::Prevote);
self.timeouts.entry(Step::Prevote).or_insert(timeout);
}
if weight >= self.weights.threshold() {
self.broadcast(Data::Precommit(None));
return Ok(None);
}
}
if matches!(msg.data, Data::Precommit(_)) &&
self.log.has_participation(self.round, Step::Precommit)
{
let timeout = self.timeout(Step::Precommit);
self.timeouts.entry(Step::Precommit).or_insert(timeout);
}
let proposer = self.weights.proposer(self.number, self.round);
if let Some(Data::Proposal(vr, block)) = self.log.get(self.round, proposer, Step::Propose) {
if self.step == Step::Propose {
let (valid, err) = match self.network.validate(block).await {
Ok(_) => (true, Ok(None)),
Err(BlockError::Temporal) => (false, Ok(None)),
Err(BlockError::Fatal) => (false, Err(TendermintError::Malicious(proposer))),
};
let raw_vote = Some(block.id()).filter(|_| valid);
let locked = self.locked.as_ref().map(|(_, id)| id == &block.id()).unwrap_or(true);
let mut vote = raw_vote.filter(|_| locked);
if let Some(vr) = vr {
if vr.0 >= self.round.0 {
Err(TendermintError::Malicious(msg.sender))?;
}
if self.log.has_consensus(*vr, Data::Prevote(Some(block.id()))) {
if let Some((locked_round, _)) = self.locked.as_ref() {
vote = vote.or_else(|| raw_vote.filter(|_| locked_round.0 <= vr.0));
}
self.broadcast(Data::Prevote(vote));
return err;
}
} else {
self.broadcast(Data::Prevote(vote));
return err;
}
} else if self.valid.as_ref().map(|(round, _)| round != &self.round).unwrap_or(true) {
if self.log.has_consensus(self.round, Data::Prevote(Some(block.id()))) {
match self.network.validate(block).await {
Ok(_) => (),
Err(BlockError::Temporal) => (),
Err(BlockError::Fatal) => Err(TendermintError::Malicious(proposer))?,
};
self.valid = Some((self.round, block.clone()));
if self.step == Step::Prevote {
self.locked = Some((self.round, block.id()));
self.broadcast(Data::Precommit(Some((
block.id(),
self
.signer
.sign(&commit_msg(self.canonical_end_time(self.round), block.id().as_ref()))
.await,
))));
return Ok(None);
}
}
}
}
Ok(None)
}
}