add tendermint message types and deserialization
This commit is contained in:
parent
fd6900bbb3
commit
6cbb859bd2
81
ethcore/src/engines/tendermint/message.rs
Normal file
81
ethcore/src/engines/tendermint/message.rs
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Tendermint message handling.
|
||||||
|
|
||||||
|
use super::{Height, Round, BlockHash};
|
||||||
|
use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream};
|
||||||
|
|
||||||
|
pub enum ConsensusMessage {
|
||||||
|
Prevote(Height, Round, BlockHash),
|
||||||
|
Precommit(Height, Round, BlockHash),
|
||||||
|
Commit(Height, BlockHash),
|
||||||
|
}
|
||||||
|
|
||||||
|
/// (height, step, ...)
|
||||||
|
impl Decodable for ConsensusMessage {
|
||||||
|
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||||
|
// Handle according to step.
|
||||||
|
let rlp = decoder.as_rlp();
|
||||||
|
if decoder.as_raw().len() != try!(rlp.payload_info()).total() {
|
||||||
|
return Err(DecoderError::RlpIsTooBig);
|
||||||
|
}
|
||||||
|
let height = try!(rlp.val_at(0));
|
||||||
|
Ok(match try!(rlp.val_at(1)) {
|
||||||
|
0u8 => ConsensusMessage::Prevote(
|
||||||
|
height,
|
||||||
|
try!(rlp.val_at(2)),
|
||||||
|
try!(rlp.val_at(3))
|
||||||
|
),
|
||||||
|
1 => ConsensusMessage::Precommit(
|
||||||
|
height,
|
||||||
|
try!(rlp.val_at(2)),
|
||||||
|
try!(rlp.val_at(3))
|
||||||
|
),
|
||||||
|
2 => ConsensusMessage::Commit(
|
||||||
|
height,
|
||||||
|
try!(rlp.val_at(2))),
|
||||||
|
_ => return Err(DecoderError::Custom("Unknown step.")),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encodable for ConsensusMessage {
|
||||||
|
fn rlp_append(&self, s: &mut RlpStream) {
|
||||||
|
match *self {
|
||||||
|
ConsensusMessage::Prevote(h, r, hash) => {
|
||||||
|
s.begin_list(4);
|
||||||
|
s.append(&h);
|
||||||
|
s.append(&0u8);
|
||||||
|
s.append(&r);
|
||||||
|
s.append(&hash);
|
||||||
|
},
|
||||||
|
ConsensusMessage::Precommit(h, r, hash) => {
|
||||||
|
s.begin_list(4);
|
||||||
|
s.append(&h);
|
||||||
|
s.append(&1u8);
|
||||||
|
s.append(&r);
|
||||||
|
s.append(&hash);
|
||||||
|
},
|
||||||
|
ConsensusMessage::Commit(h, hash) => {
|
||||||
|
s.begin_list(3);
|
||||||
|
s.append(&h);
|
||||||
|
s.append(&2u8);
|
||||||
|
s.append(&hash);
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,8 +16,10 @@
|
|||||||
|
|
||||||
//! Tendermint BFT consensus engine with round robin proof-of-authority.
|
//! Tendermint BFT consensus engine with round robin proof-of-authority.
|
||||||
|
|
||||||
|
mod message;
|
||||||
|
mod timeout;
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||||
use std::sync::Weak;
|
|
||||||
use common::*;
|
use common::*;
|
||||||
use rlp::{UntrustedRlp, View, encode};
|
use rlp::{UntrustedRlp, View, encode};
|
||||||
use ethkey::{recover, public_to_address};
|
use ethkey::{recover, public_to_address};
|
||||||
@ -27,8 +29,9 @@ use spec::CommonParams;
|
|||||||
use engines::{Engine, EngineError, ProposeCollect};
|
use engines::{Engine, EngineError, ProposeCollect};
|
||||||
use evm::Schedule;
|
use evm::Schedule;
|
||||||
use ethjson;
|
use ethjson;
|
||||||
use io::{IoContext, IoHandler, TimerToken, IoService};
|
use io::IoService;
|
||||||
use time::get_time;
|
use self::message::ConsensusMessage;
|
||||||
|
use self::timeout::{TimerHandler, NextStep, DefaultTimeouts};
|
||||||
|
|
||||||
/// `Tendermint` params.
|
/// `Tendermint` params.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
@ -63,9 +66,16 @@ enum Step {
|
|||||||
/// Precommit step storing the precommit vote and accumulating seal.
|
/// Precommit step storing the precommit vote and accumulating seal.
|
||||||
Precommit(ProposeCollect, Seal),
|
Precommit(ProposeCollect, Seal),
|
||||||
/// Commit step storing a complete valid seal.
|
/// Commit step storing a complete valid seal.
|
||||||
Commit(H256, Seal)
|
Commit(BlockHash, Seal)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type Height = usize;
|
||||||
|
pub type Round = usize;
|
||||||
|
pub type BlockHash = H256;
|
||||||
|
|
||||||
|
pub type AtomicMs = AtomicUsize;
|
||||||
|
type Seal = Vec<Bytes>;
|
||||||
|
|
||||||
impl From<ethjson::spec::TendermintParams> for TendermintParams {
|
impl From<ethjson::spec::TendermintParams> for TendermintParams {
|
||||||
fn from(p: ethjson::spec::TendermintParams) -> Self {
|
fn from(p: ethjson::spec::TendermintParams) -> Self {
|
||||||
let val: Vec<_> = p.validators.into_iter().map(Into::into).collect();
|
let val: Vec<_> = p.validators.into_iter().map(Into::into).collect();
|
||||||
@ -79,15 +89,12 @@ impl From<ethjson::spec::TendermintParams> for TendermintParams {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
struct StepMessage;
|
|
||||||
|
|
||||||
/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain.
|
/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain.
|
||||||
pub struct Tendermint {
|
pub struct Tendermint {
|
||||||
params: CommonParams,
|
params: CommonParams,
|
||||||
our_params: TendermintParams,
|
our_params: TendermintParams,
|
||||||
builtins: BTreeMap<Address, Builtin>,
|
builtins: BTreeMap<Address, Builtin>,
|
||||||
timeout_service: IoService<StepMessage>,
|
timeout_service: IoService<NextStep>,
|
||||||
/// Consensus round.
|
/// Consensus round.
|
||||||
r: AtomicUsize,
|
r: AtomicUsize,
|
||||||
/// Consensus step.
|
/// Consensus step.
|
||||||
@ -98,25 +105,21 @@ pub struct Tendermint {
|
|||||||
proposer_nonce: AtomicUsize,
|
proposer_nonce: AtomicUsize,
|
||||||
}
|
}
|
||||||
|
|
||||||
struct TimerHandler {
|
|
||||||
engine: Weak<Tendermint>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Tendermint {
|
impl Tendermint {
|
||||||
/// Create a new instance of Tendermint engine
|
/// Create a new instance of Tendermint engine
|
||||||
pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap<Address, Builtin>) -> Arc<Self> {
|
pub fn new(params: CommonParams, our_params: TendermintParams, builtins: BTreeMap<Address, Builtin>) -> Arc<Self> {
|
||||||
let engine = Arc::new(
|
let engine = Arc::new(
|
||||||
Tendermint {
|
Tendermint {
|
||||||
params: params,
|
params: params,
|
||||||
timeout: AtomicUsize::new(our_params.timeouts.propose),
|
timeout: AtomicUsize::new(our_params.timeouts.propose()),
|
||||||
our_params: our_params,
|
our_params: our_params,
|
||||||
builtins: builtins,
|
builtins: builtins,
|
||||||
timeout_service: IoService::<StepMessage>::start().expect("Error creating engine timeout service"),
|
timeout_service: IoService::<NextStep>::start().expect("Error creating engine timeout service"),
|
||||||
r: AtomicUsize::new(0),
|
r: AtomicUsize::new(0),
|
||||||
s: RwLock::new(Step::Propose),
|
s: RwLock::new(Step::Propose),
|
||||||
proposer_nonce: AtomicUsize::new(0)
|
proposer_nonce: AtomicUsize::new(0)
|
||||||
});
|
});
|
||||||
let handler = TimerHandler { engine: Arc::downgrade(&engine) };
|
let handler = TimerHandler::new(Arc::downgrade(&engine));
|
||||||
engine.timeout_service.register_handler(Arc::new(handler)).expect("Error creating engine timeout service");
|
engine.timeout_service.register_handler(Arc::new(handler)).expect("Error creating engine timeout service");
|
||||||
engine
|
engine
|
||||||
}
|
}
|
||||||
@ -134,7 +137,7 @@ impl Tendermint {
|
|||||||
self.our_params.validators.contains(address)
|
self.our_params.validators.contains(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_vote(&self, proposal: H256) -> ProposeCollect {
|
fn new_vote(&self, proposal: BlockHash) -> ProposeCollect {
|
||||||
ProposeCollect::new(proposal,
|
ProposeCollect::new(proposal,
|
||||||
self.our_params.validators.iter().cloned().collect(),
|
self.our_params.validators.iter().cloned().collect(),
|
||||||
self.threshold())
|
self.threshold())
|
||||||
@ -163,7 +166,7 @@ impl Tendermint {
|
|||||||
Ok(message.as_raw().to_vec())
|
Ok(message.as_raw().to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_prevote(&self, proposal: H256) {
|
fn to_prevote(&self, proposal: BlockHash) {
|
||||||
trace!(target: "tendermint", "step: entering prevote");
|
trace!(target: "tendermint", "step: entering prevote");
|
||||||
println!("step: entering prevote");
|
println!("step: entering prevote");
|
||||||
// Proceed to the prevote step.
|
// Proceed to the prevote step.
|
||||||
@ -195,7 +198,7 @@ impl Tendermint {
|
|||||||
Ok(message.as_raw().to_vec())
|
Ok(message.as_raw().to_vec())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn to_precommit(&self, proposal: H256) {
|
fn to_precommit(&self, proposal: BlockHash) {
|
||||||
trace!(target: "tendermint", "step: entering precommit");
|
trace!(target: "tendermint", "step: entering precommit");
|
||||||
println!("step: entering precommit");
|
println!("step: entering precommit");
|
||||||
self.to_step(Step::Precommit(self.new_vote(proposal), Vec::new()));
|
self.to_step(Step::Precommit(self.new_vote(proposal), Vec::new()));
|
||||||
@ -349,72 +352,6 @@ impl Engine for Tendermint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Base timeout of each step in ms.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
struct DefaultTimeouts {
|
|
||||||
propose: Ms,
|
|
||||||
prevote: Ms,
|
|
||||||
precommit: Ms,
|
|
||||||
commit: Ms
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DefaultTimeouts {
|
|
||||||
fn default() -> Self {
|
|
||||||
DefaultTimeouts {
|
|
||||||
propose: 1000,
|
|
||||||
prevote: 1000,
|
|
||||||
precommit: 1000,
|
|
||||||
commit: 1000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type Ms = usize;
|
|
||||||
type Seal = Vec<Bytes>;
|
|
||||||
type AtomicMs = AtomicUsize;
|
|
||||||
|
|
||||||
/// Timer token representing the consensus step timeouts.
|
|
||||||
pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0;
|
|
||||||
|
|
||||||
impl IoHandler<StepMessage> for TimerHandler {
|
|
||||||
fn initialize(&self, io: &IoContext<StepMessage>) {
|
|
||||||
if let Some(engine) = self.engine.upgrade() {
|
|
||||||
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Error registering engine timeout");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timeout(&self, io: &IoContext<StepMessage>, timer: TimerToken) {
|
|
||||||
if timer == ENGINE_TIMEOUT_TOKEN {
|
|
||||||
if let Some(engine) = self.engine.upgrade() {
|
|
||||||
println!("Timeout: {:?}", get_time());
|
|
||||||
// Can you release entering a clause?
|
|
||||||
let next_step = match *engine.s.try_read().unwrap() {
|
|
||||||
Step::Propose => Step::Propose,
|
|
||||||
Step::Prevote(_) => Step::Propose,
|
|
||||||
Step::Precommit(_, _) => Step::Propose,
|
|
||||||
Step::Commit(_, _) => {
|
|
||||||
engine.r.fetch_add(1, AtomicOrdering::Relaxed);
|
|
||||||
Step::Propose
|
|
||||||
},
|
|
||||||
};
|
|
||||||
match next_step {
|
|
||||||
Step::Propose => engine.to_propose(),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn message(&self, io: &IoContext<StepMessage>, _net_message: &StepMessage) {
|
|
||||||
if let Some(engine) = self.engine.upgrade() {
|
|
||||||
println!("Message: {:?}", get_time().sec);
|
|
||||||
io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer.");
|
|
||||||
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use common::*;
|
use common::*;
|
104
ethcore/src/engines/tendermint/timeout.rs
Normal file
104
ethcore/src/engines/tendermint/timeout.rs
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
// Copyright 2015, 2016 Ethcore (UK) Ltd.
|
||||||
|
// This file is part of Parity.
|
||||||
|
|
||||||
|
// Parity is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
|
||||||
|
// Parity is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with Parity. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
//! Tendermint BFT consensus engine with round robin proof-of-authority.
|
||||||
|
|
||||||
|
use std::sync::atomic::{Ordering as AtomicOrdering};
|
||||||
|
use std::sync::Weak;
|
||||||
|
use io::{IoContext, IoHandler, TimerToken};
|
||||||
|
use super::{Tendermint, Step};
|
||||||
|
use time::get_time;
|
||||||
|
|
||||||
|
pub struct TimerHandler {
|
||||||
|
engine: Weak<Tendermint>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TimerHandler {
|
||||||
|
pub fn new(engine: Weak<Tendermint>) -> Self {
|
||||||
|
TimerHandler { engine: engine }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Base timeout of each step in ms.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct DefaultTimeouts {
|
||||||
|
propose: Ms,
|
||||||
|
prevote: Ms,
|
||||||
|
precommit: Ms,
|
||||||
|
commit: Ms
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DefaultTimeouts {
|
||||||
|
pub fn propose(&self) -> usize { self.propose }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for DefaultTimeouts {
|
||||||
|
fn default() -> Self {
|
||||||
|
DefaultTimeouts {
|
||||||
|
propose: 1000,
|
||||||
|
prevote: 1000,
|
||||||
|
precommit: 1000,
|
||||||
|
commit: 1000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Ms = usize;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct NextStep;
|
||||||
|
|
||||||
|
/// Timer token representing the consensus step timeouts.
|
||||||
|
pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 0;
|
||||||
|
|
||||||
|
impl IoHandler<NextStep> for TimerHandler {
|
||||||
|
fn initialize(&self, io: &IoContext<NextStep>) {
|
||||||
|
if let Some(engine) = self.engine.upgrade() {
|
||||||
|
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Error registering engine timeout");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn timeout(&self, io: &IoContext<NextStep>, timer: TimerToken) {
|
||||||
|
if timer == ENGINE_TIMEOUT_TOKEN {
|
||||||
|
if let Some(engine) = self.engine.upgrade() {
|
||||||
|
println!("Timeout: {:?}", get_time());
|
||||||
|
// Can you release entering a clause?
|
||||||
|
let next_step = match *engine.s.try_read().unwrap() {
|
||||||
|
Step::Propose => Step::Propose,
|
||||||
|
Step::Prevote(_) => Step::Propose,
|
||||||
|
Step::Precommit(_, _) => Step::Propose,
|
||||||
|
Step::Commit(_, _) => {
|
||||||
|
engine.r.fetch_add(1, AtomicOrdering::Relaxed);
|
||||||
|
Step::Propose
|
||||||
|
},
|
||||||
|
};
|
||||||
|
match next_step {
|
||||||
|
Step::Propose => engine.to_propose(),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message(&self, io: &IoContext<NextStep>, _net_message: &NextStep) {
|
||||||
|
if let Some(engine) = self.engine.upgrade() {
|
||||||
|
println!("Message: {:?}", get_time().sec);
|
||||||
|
io.clear_timer(ENGINE_TIMEOUT_TOKEN).expect("Failed to restart consensus step timer.");
|
||||||
|
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, engine.next_timeout()).expect("Failed to restart consensus step timer.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user