Remove tendermint engine support (#9980)
* Remove tendermint engine support * Remove tendermint test json spec * Fix ethcore test compile * Remove tendermint test in sync
This commit is contained in:
parent
7f5e6b3a0a
commit
f092c10de5
@ -1,58 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "TestBFT",
|
|
||||||
"engine": {
|
|
||||||
"tendermint": {
|
|
||||||
"params": {
|
|
||||||
"validators" : {
|
|
||||||
"list": [
|
|
||||||
"0x82a978b3f5962a5b0957d9ee9eef472ee55b42f1",
|
|
||||||
"0x7d577a597b2742b498cb5cf0c26cdcd726d39e6e"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"timeoutPropose": 10000,
|
|
||||||
"timeoutPrevote": 10000,
|
|
||||||
"timeoutPrecommit": 10000,
|
|
||||||
"timeoutCommit": 10000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"params": {
|
|
||||||
"gasLimitBoundDivisor": "0x0400",
|
|
||||||
"accountStartNonce": "0x0",
|
|
||||||
"maximumExtraDataSize": "0x20",
|
|
||||||
"minGasLimit": "0x1388",
|
|
||||||
"networkID" : "0x2323",
|
|
||||||
"eip140Transition": "0x0",
|
|
||||||
"eip211Transition": "0x0",
|
|
||||||
"eip214Transition": "0x0",
|
|
||||||
"eip658Transition": "0x0"
|
|
||||||
},
|
|
||||||
"genesis": {
|
|
||||||
"seal": {
|
|
||||||
"tendermint": {
|
|
||||||
"round": "0x0",
|
|
||||||
"proposal": "0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"precommits": [
|
|
||||||
"0x0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"difficulty": "0x20000",
|
|
||||||
"author": "0x0000000000000000000000000000000000000000",
|
|
||||||
"timestamp": "0x00",
|
|
||||||
"parentHash": "0x0000000000000000000000000000000000000000000000000000000000000000",
|
|
||||||
"extraData": "0x",
|
|
||||||
"gasLimit": "0x222222"
|
|
||||||
},
|
|
||||||
"accounts": {
|
|
||||||
"0000000000000000000000000000000000000001": { "balance": "1", "builtin": { "name": "ecrecover", "pricing": { "linear": { "base": 3000, "word": 0 } } } },
|
|
||||||
"0000000000000000000000000000000000000002": { "balance": "1", "builtin": { "name": "sha256", "pricing": { "linear": { "base": 60, "word": 12 } } } },
|
|
||||||
"0000000000000000000000000000000000000003": { "balance": "1", "builtin": { "name": "ripemd160", "pricing": { "linear": { "base": 600, "word": 120 } } } },
|
|
||||||
"0000000000000000000000000000000000000004": { "balance": "1", "builtin": { "name": "identity", "pricing": { "linear": { "base": 15, "word": 3 } } } },
|
|
||||||
"0000000000000000000000000000000000000005": { "balance": "1", "builtin": { "name": "modexp", "activate_at": 0, "pricing": { "modexp": { "divisor": 20 } } } },
|
|
||||||
"0000000000000000000000000000000000000006": { "balance": "1", "builtin": { "name": "alt_bn128_add", "activate_at": 0, "pricing": { "linear": { "base": 500, "word": 0 } } } },
|
|
||||||
"0000000000000000000000000000000000000007": { "balance": "1", "builtin": { "name": "alt_bn128_mul", "activate_at": 0, "pricing": { "linear": { "base": 40000, "word": 0 } } } },
|
|
||||||
"0000000000000000000000000000000000000008": { "balance": "1", "builtin": { "name": "alt_bn128_pairing", "activate_at": 0, "pricing": { "alt_bn128_pairing": { "base": 100000, "pair": 80000 } } } },
|
|
||||||
"9cce34f7ab185c7aba1b7c8140d620b4bda941d6": { "balance": "1606938044258990275541962092341162602522202993782792835301376" }
|
|
||||||
}
|
|
||||||
}
|
|
@ -21,10 +21,7 @@ mod basic_authority;
|
|||||||
mod instant_seal;
|
mod instant_seal;
|
||||||
mod null_engine;
|
mod null_engine;
|
||||||
mod signer;
|
mod signer;
|
||||||
mod tendermint;
|
|
||||||
mod transition;
|
|
||||||
mod validator_set;
|
mod validator_set;
|
||||||
mod vote_collector;
|
|
||||||
|
|
||||||
pub mod block_reward;
|
pub mod block_reward;
|
||||||
pub mod epoch;
|
pub mod epoch;
|
||||||
@ -34,7 +31,6 @@ pub use self::basic_authority::BasicAuthority;
|
|||||||
pub use self::epoch::{EpochVerifier, Transition as EpochTransition};
|
pub use self::epoch::{EpochVerifier, Transition as EpochTransition};
|
||||||
pub use self::instant_seal::{InstantSeal, InstantSealParams};
|
pub use self::instant_seal::{InstantSeal, InstantSealParams};
|
||||||
pub use self::null_engine::NullEngine;
|
pub use self::null_engine::NullEngine;
|
||||||
pub use self::tendermint::Tendermint;
|
|
||||||
|
|
||||||
use std::sync::{Weak, Arc};
|
use std::sync::{Weak, Arc};
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
|
@ -57,11 +57,6 @@ impl EngineSigner {
|
|||||||
self.address.clone()
|
self.address.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Check if the given address is the signing address.
|
|
||||||
pub fn is_address(&self, address: &Address) -> bool {
|
|
||||||
self.address.map_or(false, |a| a == *address)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Check if the signing address was set.
|
/// Check if the signing address was set.
|
||||||
pub fn is_some(&self) -> bool {
|
pub fn is_some(&self) -> bool {
|
||||||
self.address.is_some()
|
self.address.is_some()
|
||||||
|
@ -1,298 +0,0 @@
|
|||||||
// Copyright 2015-2018 Parity Technologies (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 bytes::Bytes;
|
|
||||||
use error::Error;
|
|
||||||
use ethereum_types::{H256, H520, Address};
|
|
||||||
use ethkey::{recover, public_to_address};
|
|
||||||
use hash::keccak;
|
|
||||||
use header::Header;
|
|
||||||
use rlp::{Rlp, RlpStream, Encodable, Decodable, DecoderError};
|
|
||||||
use std::cmp;
|
|
||||||
use super::{Height, View, BlockHash, Step};
|
|
||||||
use super::super::vote_collector::Message;
|
|
||||||
|
|
||||||
/// Message transmitted between consensus participants.
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
|
|
||||||
pub struct ConsensusMessage {
|
|
||||||
pub vote_step: VoteStep,
|
|
||||||
pub block_hash: Option<BlockHash>,
|
|
||||||
pub signature: H520,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Complete step of the consensus process.
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
|
|
||||||
pub struct VoteStep {
|
|
||||||
pub height: Height,
|
|
||||||
pub view: View,
|
|
||||||
pub step: Step,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VoteStep {
|
|
||||||
pub fn new(height: Height, view: View, step: Step) -> Self {
|
|
||||||
VoteStep { height: height, view: view, step: step }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_height(&self, height: Height) -> bool {
|
|
||||||
self.height == height
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn is_view(&self, height: Height, view: View) -> bool {
|
|
||||||
self.height == height && self.view == view
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Header consensus view.
|
|
||||||
pub fn consensus_view(header: &Header) -> Result<View, ::rlp::DecoderError> {
|
|
||||||
let view_rlp = header.seal().get(0).expect("seal passed basic verification; seal has 3 fields; qed");
|
|
||||||
Rlp::new(view_rlp.as_slice()).as_val()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Proposal signature.
|
|
||||||
pub fn proposal_signature(header: &Header) -> Result<H520, ::rlp::DecoderError> {
|
|
||||||
Rlp::new(header.seal().get(1).expect("seal passed basic verification; seal has 3 fields; qed").as_slice()).as_val()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Message for ConsensusMessage {
|
|
||||||
type Round = VoteStep;
|
|
||||||
|
|
||||||
fn signature(&self) -> H520 { self.signature }
|
|
||||||
|
|
||||||
fn block_hash(&self) -> Option<H256> { self.block_hash }
|
|
||||||
|
|
||||||
fn round(&self) -> &VoteStep { &self.vote_step }
|
|
||||||
|
|
||||||
fn is_broadcastable(&self) -> bool { self.vote_step.step.is_pre() }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConsensusMessage {
|
|
||||||
pub fn new(signature: H520, height: Height, view: View, step: Step, block_hash: Option<BlockHash>) -> Self {
|
|
||||||
ConsensusMessage {
|
|
||||||
signature: signature,
|
|
||||||
block_hash: block_hash,
|
|
||||||
vote_step: VoteStep::new(height, view, step),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_proposal(header: &Header) -> Result<Self, ::rlp::DecoderError> {
|
|
||||||
Ok(ConsensusMessage {
|
|
||||||
signature: proposal_signature(header)?,
|
|
||||||
vote_step: VoteStep::new(header.number() as Height, consensus_view(header)?, Step::Propose),
|
|
||||||
block_hash: Some(header.bare_hash()),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn verify(&self) -> Result<Address, Error> {
|
|
||||||
let full_rlp = ::rlp::encode(self);
|
|
||||||
let block_info = Rlp::new(&full_rlp).at(1)?;
|
|
||||||
let public_key = recover(&self.signature.into(), &keccak(block_info.as_raw()))?;
|
|
||||||
Ok(public_to_address(&public_key))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for VoteStep {
|
|
||||||
fn default() -> Self {
|
|
||||||
VoteStep::new(0, 0, Step::Propose)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialOrd for VoteStep {
|
|
||||||
fn partial_cmp(&self, m: &VoteStep) -> Option<cmp::Ordering> {
|
|
||||||
Some(self.cmp(m))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Ord for VoteStep {
|
|
||||||
fn cmp(&self, m: &VoteStep) -> cmp::Ordering {
|
|
||||||
if self.height != m.height {
|
|
||||||
self.height.cmp(&m.height)
|
|
||||||
} else if self.view != m.view {
|
|
||||||
self.view.cmp(&m.view)
|
|
||||||
} else {
|
|
||||||
self.step.number().cmp(&m.step.number())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Step {
|
|
||||||
fn number(&self) -> u8 {
|
|
||||||
match *self {
|
|
||||||
Step::Propose => 0,
|
|
||||||
Step::Prevote => 1,
|
|
||||||
Step::Precommit => 2,
|
|
||||||
Step::Commit => 3,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Decodable for Step {
|
|
||||||
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
|
|
||||||
match rlp.as_val()? {
|
|
||||||
0u8 => Ok(Step::Propose),
|
|
||||||
1 => Ok(Step::Prevote),
|
|
||||||
2 => Ok(Step::Precommit),
|
|
||||||
_ => Err(DecoderError::Custom("Invalid step.")),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encodable for Step {
|
|
||||||
fn rlp_append(&self, s: &mut RlpStream) {
|
|
||||||
s.append_internal(&self.number());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// (signature, (height, view, step, block_hash))
|
|
||||||
impl Decodable for ConsensusMessage {
|
|
||||||
fn decode(rlp: &Rlp) -> Result<Self, DecoderError> {
|
|
||||||
let m = rlp.at(1)?;
|
|
||||||
let block_message: H256 = m.val_at(3)?;
|
|
||||||
Ok(ConsensusMessage {
|
|
||||||
vote_step: VoteStep::new(m.val_at(0)?, m.val_at(1)?, m.val_at(2)?),
|
|
||||||
block_hash: match block_message.is_zero() {
|
|
||||||
true => None,
|
|
||||||
false => Some(block_message),
|
|
||||||
},
|
|
||||||
signature: rlp.val_at(0)?,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encodable for ConsensusMessage {
|
|
||||||
fn rlp_append(&self, s: &mut RlpStream) {
|
|
||||||
let info = message_info_rlp(&self.vote_step, self.block_hash);
|
|
||||||
s.begin_list(2)
|
|
||||||
.append(&self.signature)
|
|
||||||
.append_raw(&info, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn message_info_rlp(vote_step: &VoteStep, block_hash: Option<BlockHash>) -> Bytes {
|
|
||||||
let mut s = RlpStream::new_list(4);
|
|
||||||
s.append(&vote_step.height).append(&vote_step.view).append(&vote_step.step).append(&block_hash.unwrap_or_else(H256::zero));
|
|
||||||
s.out()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn message_full_rlp(signature: &H520, vote_info: &Bytes) -> Bytes {
|
|
||||||
let mut s = RlpStream::new_list(2);
|
|
||||||
s.append(signature).append_raw(vote_info, 1);
|
|
||||||
s.out()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn message_hash(vote_step: VoteStep, block_hash: H256) -> H256 {
|
|
||||||
keccak(message_info_rlp(&vote_step, Some(block_hash)))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::sync::Arc;
|
|
||||||
use hash::keccak;
|
|
||||||
use rlp::*;
|
|
||||||
use account_provider::AccountProvider;
|
|
||||||
use header::Header;
|
|
||||||
use super::super::Step;
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encode_step() {
|
|
||||||
let step = Step::Precommit;
|
|
||||||
|
|
||||||
let mut s = RlpStream::new_list(2);
|
|
||||||
s.append(&step);
|
|
||||||
assert!(!s.is_finished(), "List shouldn't finished yet");
|
|
||||||
s.append(&step);
|
|
||||||
assert!(s.is_finished(), "List should be finished now");
|
|
||||||
s.out();
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn encode_decode() {
|
|
||||||
let message = ConsensusMessage {
|
|
||||||
signature: H520::default(),
|
|
||||||
vote_step: VoteStep {
|
|
||||||
height: 10,
|
|
||||||
view: 123,
|
|
||||||
step: Step::Precommit,
|
|
||||||
},
|
|
||||||
block_hash: Some(keccak("1")),
|
|
||||||
};
|
|
||||||
let raw_rlp = ::rlp::encode(&message);
|
|
||||||
let rlp = Rlp::new(&raw_rlp);
|
|
||||||
assert_eq!(Ok(message), rlp.as_val());
|
|
||||||
|
|
||||||
let message = ConsensusMessage {
|
|
||||||
signature: H520::default(),
|
|
||||||
vote_step: VoteStep {
|
|
||||||
height: 1314,
|
|
||||||
view: 0,
|
|
||||||
step: Step::Prevote,
|
|
||||||
},
|
|
||||||
block_hash: None
|
|
||||||
};
|
|
||||||
let raw_rlp = ::rlp::encode(&message);
|
|
||||||
let rlp = Rlp::new(&raw_rlp);
|
|
||||||
assert_eq!(Ok(message), rlp.as_val());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn generate_and_verify() {
|
|
||||||
let tap = Arc::new(AccountProvider::transient_provider());
|
|
||||||
let addr = tap.insert_account(keccak("0").into(), &"0".into()).unwrap();
|
|
||||||
tap.unlock_account_permanently(addr, "0".into()).unwrap();
|
|
||||||
|
|
||||||
let mi = message_info_rlp(&VoteStep::new(123, 2, Step::Precommit), Some(H256::default()));
|
|
||||||
|
|
||||||
let raw_rlp = message_full_rlp(&tap.sign(addr, None, keccak(&mi)).unwrap().into(), &mi);
|
|
||||||
|
|
||||||
let rlp = Rlp::new(&raw_rlp);
|
|
||||||
let message: ConsensusMessage = rlp.as_val().unwrap();
|
|
||||||
match message.verify() { Ok(a) if a == addr => {}, _ => panic!(), };
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn proposal_message() {
|
|
||||||
let mut header = Header::default();
|
|
||||||
let seal = vec![
|
|
||||||
::rlp::encode(&0u8),
|
|
||||||
::rlp::encode(&H520::default()),
|
|
||||||
Vec::new()
|
|
||||||
];
|
|
||||||
|
|
||||||
header.set_seal(seal);
|
|
||||||
let message = ConsensusMessage::new_proposal(&header).unwrap();
|
|
||||||
assert_eq!(
|
|
||||||
message,
|
|
||||||
ConsensusMessage {
|
|
||||||
signature: Default::default(),
|
|
||||||
vote_step: VoteStep {
|
|
||||||
height: 0,
|
|
||||||
view: 0,
|
|
||||||
step: Step::Propose,
|
|
||||||
},
|
|
||||||
block_hash: Some(header.bare_hash())
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn step_ordering() {
|
|
||||||
assert!(VoteStep::new(10, 123, Step::Precommit) < VoteStep::new(11, 123, Step::Precommit));
|
|
||||||
assert!(VoteStep::new(10, 123, Step::Propose) < VoteStep::new(11, 123, Step::Precommit));
|
|
||||||
assert!(VoteStep::new(10, 122, Step::Propose) < VoteStep::new(11, 123, Step::Propose));
|
|
||||||
}
|
|
||||||
}
|
|
File diff suppressed because it is too large
Load Diff
@ -1,90 +0,0 @@
|
|||||||
// Copyright 2015-2018 Parity Technologies (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 specific parameters.
|
|
||||||
|
|
||||||
use ethjson;
|
|
||||||
use std::time::Duration;
|
|
||||||
use ethereum_types::U256;
|
|
||||||
use super::super::validator_set::{ValidatorSet, new_validator_set};
|
|
||||||
use super::super::transition::Timeouts;
|
|
||||||
use super::Step;
|
|
||||||
|
|
||||||
/// `Tendermint` params.
|
|
||||||
pub struct TendermintParams {
|
|
||||||
/// List of validators.
|
|
||||||
pub validators: Box<ValidatorSet>,
|
|
||||||
/// Timeout durations for different steps.
|
|
||||||
pub timeouts: TendermintTimeouts,
|
|
||||||
/// Reward per block in base units.
|
|
||||||
pub block_reward: U256,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Base timeout of each step in ms.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TendermintTimeouts {
|
|
||||||
pub propose: Duration,
|
|
||||||
pub prevote: Duration,
|
|
||||||
pub precommit: Duration,
|
|
||||||
pub commit: Duration,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for TendermintTimeouts {
|
|
||||||
fn default() -> Self {
|
|
||||||
TendermintTimeouts {
|
|
||||||
propose: Duration::from_millis(1000),
|
|
||||||
prevote: Duration::from_millis(1000),
|
|
||||||
precommit: Duration::from_millis(1000),
|
|
||||||
commit: Duration::from_millis(1000),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Timeouts<Step> for TendermintTimeouts {
|
|
||||||
fn initial(&self) -> Duration {
|
|
||||||
self.propose
|
|
||||||
}
|
|
||||||
|
|
||||||
fn timeout(&self, step: &Step) -> Duration {
|
|
||||||
match *step {
|
|
||||||
Step::Propose => self.propose,
|
|
||||||
Step::Prevote => self.prevote,
|
|
||||||
Step::Precommit => self.precommit,
|
|
||||||
Step::Commit => self.commit,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn to_duration(ms: ethjson::uint::Uint) -> Duration {
|
|
||||||
let ms: usize = ms.into();
|
|
||||||
Duration::from_millis(ms as u64)
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ethjson::spec::TendermintParams> for TendermintParams {
|
|
||||||
fn from(p: ethjson::spec::TendermintParams) -> Self {
|
|
||||||
let dt = TendermintTimeouts::default();
|
|
||||||
TendermintParams {
|
|
||||||
validators: new_validator_set(p.validators),
|
|
||||||
timeouts: TendermintTimeouts {
|
|
||||||
propose: p.timeout_propose.map_or(dt.propose, to_duration),
|
|
||||||
prevote: p.timeout_prevote.map_or(dt.prevote, to_duration),
|
|
||||||
precommit: p.timeout_precommit.map_or(dt.precommit, to_duration),
|
|
||||||
commit: p.timeout_commit.map_or(dt.commit, to_duration),
|
|
||||||
},
|
|
||||||
block_reward: p.block_reward.map_or(U256::default(), Into::into),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,83 +0,0 @@
|
|||||||
// Copyright 2015-2018 Parity Technologies (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/>.
|
|
||||||
|
|
||||||
//! Engine timeout transitioning calls `Engine.step()` on timeout.
|
|
||||||
|
|
||||||
use std::sync::Weak;
|
|
||||||
use std::time::Duration;
|
|
||||||
use io::{IoContext, IoHandler, TimerToken};
|
|
||||||
use engines::Engine;
|
|
||||||
use parity_machine::Machine;
|
|
||||||
|
|
||||||
/// Timeouts lookup
|
|
||||||
pub trait Timeouts<S: Sync + Send + Clone>: Send + Sync {
|
|
||||||
/// Return the first timeout.
|
|
||||||
fn initial(&self) -> Duration;
|
|
||||||
|
|
||||||
/// Get a timeout based on step.
|
|
||||||
fn timeout(&self, step: &S) -> Duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Timeout transition handling.
|
|
||||||
pub struct TransitionHandler<S: Sync + Send + Clone, M: Machine> {
|
|
||||||
engine: Weak<Engine<M>>,
|
|
||||||
timeouts: Box<Timeouts<S>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, M: Machine> TransitionHandler<S, M> where S: Sync + Send + Clone {
|
|
||||||
/// New step caller by timeouts.
|
|
||||||
pub fn new(engine: Weak<Engine<M>>, timeouts: Box<Timeouts<S>>) -> Self {
|
|
||||||
TransitionHandler {
|
|
||||||
engine: engine,
|
|
||||||
timeouts: timeouts,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Timer token representing the consensus step timeouts.
|
|
||||||
pub const ENGINE_TIMEOUT_TOKEN: TimerToken = 23;
|
|
||||||
|
|
||||||
fn set_timeout<S: Sync + Send + Clone>(io: &IoContext<S>, timeout: Duration) {
|
|
||||||
io.register_timer_once(ENGINE_TIMEOUT_TOKEN, timeout)
|
|
||||||
.unwrap_or_else(|e| warn!(target: "engine", "Failed to set consensus step timeout: {}.", e))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<S, M> IoHandler<S> for TransitionHandler<S, M>
|
|
||||||
where S: Sync + Send + Clone + 'static, M: Machine
|
|
||||||
{
|
|
||||||
fn initialize(&self, io: &IoContext<S>) {
|
|
||||||
let initial = self.timeouts.initial();
|
|
||||||
trace!(target: "engine", "Setting the initial timeout to {:?}.", initial);
|
|
||||||
set_timeout(io, initial);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Call step after timeout.
|
|
||||||
fn timeout(&self, _io: &IoContext<S>, timer: TimerToken) {
|
|
||||||
if timer == ENGINE_TIMEOUT_TOKEN {
|
|
||||||
if let Some(engine) = self.engine.upgrade() {
|
|
||||||
engine.step();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Set a new timer on message.
|
|
||||||
fn message(&self, io: &IoContext<S>, next: &S) {
|
|
||||||
if let Err(io_err) = io.clear_timer(ENGINE_TIMEOUT_TOKEN) {
|
|
||||||
warn!(target: "engine", "Could not remove consensus timer {}.", io_err)
|
|
||||||
}
|
|
||||||
set_timeout(io, self.timeouts.timeout(next));
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,349 +0,0 @@
|
|||||||
// Copyright 2015-2018 Parity Technologies (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/>.
|
|
||||||
|
|
||||||
//! Collects votes on hashes at each Message::Round.
|
|
||||||
|
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::collections::{BTreeMap, HashSet, HashMap};
|
|
||||||
use std::hash::Hash;
|
|
||||||
use ethereum_types::{H256, H520, Address};
|
|
||||||
use parking_lot:: RwLock;
|
|
||||||
use bytes::Bytes;
|
|
||||||
use rlp::{Encodable, RlpStream};
|
|
||||||
|
|
||||||
pub trait Message: Clone + PartialEq + Eq + Hash + Encodable + Debug {
|
|
||||||
type Round: Clone + PartialEq + Eq + Hash + Default + Debug + Ord;
|
|
||||||
|
|
||||||
fn signature(&self) -> H520;
|
|
||||||
|
|
||||||
fn block_hash(&self) -> Option<H256>;
|
|
||||||
|
|
||||||
fn round(&self) -> &Self::Round;
|
|
||||||
|
|
||||||
fn is_broadcastable(&self) -> bool;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Storing all Proposals, Prevotes and Precommits.
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct VoteCollector<M: Message> {
|
|
||||||
votes: RwLock<BTreeMap<M::Round, StepCollector<M>>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
|
||||||
struct StepCollector<M: Message> {
|
|
||||||
voted: HashMap<Address, M>,
|
|
||||||
block_votes: HashMap<Option<H256>, HashMap<H520, Address>>,
|
|
||||||
messages: HashSet<M>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct DoubleVote<M: Message> {
|
|
||||||
author: Address,
|
|
||||||
vote_one: M,
|
|
||||||
vote_two: M,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<M: Message> Encodable for DoubleVote<M> {
|
|
||||||
fn rlp_append(&self, s: &mut RlpStream) {
|
|
||||||
s.begin_list(2)
|
|
||||||
.append(&self.vote_one)
|
|
||||||
.append(&self.vote_two);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <M: Message> StepCollector<M> {
|
|
||||||
/// Returns Some(&Address) when validator is double voting.
|
|
||||||
fn insert(&mut self, message: M, address: Address) -> Option<DoubleVote<M>> {
|
|
||||||
// Do nothing when message was seen.
|
|
||||||
if self.messages.insert(message.clone()) {
|
|
||||||
if let Some(previous) = self.voted.insert(address, message.clone()) {
|
|
||||||
// Bad validator sent a different message.
|
|
||||||
return Some(DoubleVote {
|
|
||||||
author: address,
|
|
||||||
vote_one: previous,
|
|
||||||
vote_two: message
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
self
|
|
||||||
.block_votes
|
|
||||||
.entry(message.block_hash())
|
|
||||||
.or_insert_with(HashMap::new)
|
|
||||||
.insert(message.signature(), address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count all votes for the given block hash at this round.
|
|
||||||
fn count_block(&self, block_hash: &Option<H256>) -> usize {
|
|
||||||
self.block_votes.get(block_hash).map_or(0, HashMap::len)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count all votes collected for the given round.
|
|
||||||
fn count(&self) -> usize {
|
|
||||||
self.block_votes.values().map(HashMap::len).sum()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct SealSignatures {
|
|
||||||
pub proposal: H520,
|
|
||||||
pub votes: Vec<H520>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PartialEq for SealSignatures {
|
|
||||||
fn eq(&self, other: &SealSignatures) -> bool {
|
|
||||||
self.proposal == other.proposal
|
|
||||||
&& self.votes.iter().collect::<HashSet<_>>() == other.votes.iter().collect::<HashSet<_>>()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Eq for SealSignatures {}
|
|
||||||
|
|
||||||
impl <M: Message + Default> Default for VoteCollector<M> {
|
|
||||||
fn default() -> Self {
|
|
||||||
let mut collector = BTreeMap::new();
|
|
||||||
// Insert dummy entry to fulfill invariant: "only messages newer than the oldest are inserted".
|
|
||||||
collector.insert(Default::default(), Default::default());
|
|
||||||
VoteCollector { votes: RwLock::new(collector) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl <M: Message + Default + Encodable + Debug> VoteCollector<M> {
|
|
||||||
/// Insert vote if it is newer than the oldest one.
|
|
||||||
pub fn vote(&self, message: M, voter: Address) -> Option<DoubleVote<M>> {
|
|
||||||
self
|
|
||||||
.votes
|
|
||||||
.write()
|
|
||||||
.entry(message.round().clone())
|
|
||||||
.or_insert_with(Default::default)
|
|
||||||
.insert(message, voter)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Checks if the message should be ignored.
|
|
||||||
pub fn is_old_or_known(&self, message: &M) -> bool {
|
|
||||||
self
|
|
||||||
.votes
|
|
||||||
.read()
|
|
||||||
.get(&message.round())
|
|
||||||
.map_or(false, |c| {
|
|
||||||
let is_known = c.messages.contains(message);
|
|
||||||
if is_known { trace!(target: "engine", "Known message: {:?}.", message); }
|
|
||||||
is_known
|
|
||||||
})
|
|
||||||
|| {
|
|
||||||
let guard = self.votes.read();
|
|
||||||
let is_old = guard.keys().next().map_or(true, |oldest| message.round() <= oldest);
|
|
||||||
if is_old { trace!(target: "engine", "Old message {:?}.", message); }
|
|
||||||
is_old
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Throws out messages older than message, leaves message as marker for the oldest.
|
|
||||||
pub fn throw_out_old(&self, vote_round: &M::Round) {
|
|
||||||
let mut guard = self.votes.write();
|
|
||||||
let new_collector = guard.split_off(vote_round);
|
|
||||||
*guard = new_collector;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Collects the signatures for a given round and hash.
|
|
||||||
pub fn round_signatures(&self, round: &M::Round, block_hash: &H256) -> Vec<H520> {
|
|
||||||
let guard = self.votes.read();
|
|
||||||
guard
|
|
||||||
.get(round)
|
|
||||||
.and_then(|c| c.block_votes.get(&Some(*block_hash)))
|
|
||||||
.map(|votes| votes.keys().cloned().collect())
|
|
||||||
.unwrap_or_else(Vec::new)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count votes which agree with the given message.
|
|
||||||
pub fn count_aligned_votes(&self, message: &M) -> usize {
|
|
||||||
self
|
|
||||||
.votes
|
|
||||||
.read()
|
|
||||||
.get(&message.round())
|
|
||||||
.map_or(0, |m| m.count_block(&message.block_hash()))
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Count all votes collected for a given round.
|
|
||||||
pub fn count_round_votes(&self, vote_round: &M::Round) -> usize {
|
|
||||||
self.votes.read().get(vote_round).map_or(0, StepCollector::count)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get all messages older than the round.
|
|
||||||
pub fn get_up_to(&self, round: &M::Round) -> Vec<Bytes> {
|
|
||||||
let guard = self.votes.read();
|
|
||||||
guard
|
|
||||||
.iter()
|
|
||||||
.take_while(|&(r, _)| r <= round)
|
|
||||||
.map(|(_, c)| c.messages.iter().filter(|m| m.is_broadcastable()).map(|m| ::rlp::encode(m).to_vec()).collect::<Vec<_>>())
|
|
||||||
.fold(Vec::new(), |mut acc, mut messages| { acc.append(&mut messages); acc })
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Retrieve address from which the message was sent from cache.
|
|
||||||
pub fn get(&self, message: &M) -> Option<Address> {
|
|
||||||
let guard = self.votes.read();
|
|
||||||
guard.get(&message.round()).and_then(|c| c.block_votes.get(&message.block_hash())).and_then(|origins| origins.get(&message.signature()).cloned())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use hash::keccak;
|
|
||||||
use ethereum_types::{H160, H256};
|
|
||||||
use rlp::*;
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Clone, Hash, Default)]
|
|
||||||
struct TestMessage {
|
|
||||||
step: TestStep,
|
|
||||||
block_hash: Option<H256>,
|
|
||||||
signature: H520,
|
|
||||||
}
|
|
||||||
|
|
||||||
type TestStep = u64;
|
|
||||||
|
|
||||||
impl Message for TestMessage {
|
|
||||||
type Round = TestStep;
|
|
||||||
|
|
||||||
fn signature(&self) -> H520 { self.signature }
|
|
||||||
|
|
||||||
fn block_hash(&self) -> Option<H256> { self.block_hash }
|
|
||||||
|
|
||||||
fn round(&self) -> &TestStep { &self.step }
|
|
||||||
|
|
||||||
fn is_broadcastable(&self) -> bool { true }
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Encodable for TestMessage {
|
|
||||||
fn rlp_append(&self, s: &mut RlpStream) {
|
|
||||||
s.begin_list(3)
|
|
||||||
.append(&self.signature)
|
|
||||||
.append(&self.step)
|
|
||||||
.append(&self.block_hash.unwrap_or_else(H256::zero));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn random_vote(collector: &VoteCollector<TestMessage>, signature: H520, step: TestStep, block_hash: Option<H256>) -> bool {
|
|
||||||
full_vote(collector, signature, step, block_hash, H160::random())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn full_vote(collector: &VoteCollector<TestMessage>, signature: H520, step: TestStep, block_hash: Option<H256>, address: Address) -> bool {
|
|
||||||
collector.vote(TestMessage { signature: signature, step: step, block_hash: block_hash }, address).is_none()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn seal_retrieval() {
|
|
||||||
let collector = VoteCollector::default();
|
|
||||||
let bh = Some(keccak("1"));
|
|
||||||
let mut signatures = Vec::new();
|
|
||||||
for _ in 0..5 {
|
|
||||||
signatures.push(H520::random());
|
|
||||||
}
|
|
||||||
let propose_round = 3;
|
|
||||||
let commit_round = 5;
|
|
||||||
// Wrong round.
|
|
||||||
random_vote(&collector, signatures[4].clone(), 1, bh.clone());
|
|
||||||
// Good proposal
|
|
||||||
random_vote(&collector, signatures[0].clone(), propose_round.clone(), bh.clone());
|
|
||||||
// Wrong block proposal.
|
|
||||||
random_vote(&collector, signatures[0].clone(), propose_round.clone(), Some(keccak("0")));
|
|
||||||
// Wrong block commit.
|
|
||||||
random_vote(&collector, signatures[3].clone(), commit_round.clone(), Some(keccak("0")));
|
|
||||||
// Wrong round.
|
|
||||||
random_vote(&collector, signatures[0].clone(), 6, bh.clone());
|
|
||||||
// Wrong round.
|
|
||||||
random_vote(&collector, signatures[0].clone(), 4, bh.clone());
|
|
||||||
// Relevant commit.
|
|
||||||
random_vote(&collector, signatures[2].clone(), commit_round.clone(), bh.clone());
|
|
||||||
// Replicated vote.
|
|
||||||
random_vote(&collector, signatures[2].clone(), commit_round.clone(), bh.clone());
|
|
||||||
// Wrong round.
|
|
||||||
random_vote(&collector, signatures[4].clone(), 6, bh.clone());
|
|
||||||
// Relevant precommit.
|
|
||||||
random_vote(&collector, signatures[1].clone(), commit_round.clone(), bh.clone());
|
|
||||||
// Wrong round, same signature.
|
|
||||||
random_vote(&collector, signatures[1].clone(), 7, bh.clone());
|
|
||||||
|
|
||||||
assert_eq!(signatures[0..1].to_vec(), collector.round_signatures(&propose_round, &bh.unwrap()));
|
|
||||||
assert_eq!(signatures[1..3].iter().collect::<HashSet<_>>(), collector.round_signatures(&commit_round, &bh.unwrap()).iter().collect::<HashSet<_>>());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn count_votes() {
|
|
||||||
let collector = VoteCollector::default();
|
|
||||||
let round1 = 1;
|
|
||||||
let round3 = 3;
|
|
||||||
// good 1
|
|
||||||
random_vote(&collector, H520::random(), round1, Some(keccak("0")));
|
|
||||||
random_vote(&collector, H520::random(), 0, Some(keccak("0")));
|
|
||||||
// good 3
|
|
||||||
random_vote(&collector, H520::random(), round3, Some(keccak("0")));
|
|
||||||
random_vote(&collector, H520::random(), 2, Some(keccak("0")));
|
|
||||||
// good prevote
|
|
||||||
random_vote(&collector, H520::random(), round1, Some(keccak("1")));
|
|
||||||
// good prevote
|
|
||||||
let same_sig = H520::random();
|
|
||||||
random_vote(&collector, same_sig.clone(), round1, Some(keccak("1")));
|
|
||||||
random_vote(&collector, same_sig, round1, Some(keccak("1")));
|
|
||||||
// good precommit
|
|
||||||
random_vote(&collector, H520::random(), round3, Some(keccak("1")));
|
|
||||||
// good prevote
|
|
||||||
random_vote(&collector, H520::random(), round1, Some(keccak("0")));
|
|
||||||
random_vote(&collector, H520::random(), 4, Some(keccak("2")));
|
|
||||||
|
|
||||||
assert_eq!(collector.count_round_votes(&round1), 4);
|
|
||||||
assert_eq!(collector.count_round_votes(&round3), 2);
|
|
||||||
|
|
||||||
let message = TestMessage {
|
|
||||||
signature: H520::default(),
|
|
||||||
step: round1,
|
|
||||||
block_hash: Some(keccak("1"))
|
|
||||||
};
|
|
||||||
assert_eq!(collector.count_aligned_votes(&message), 2);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn remove_old() {
|
|
||||||
let collector = VoteCollector::default();
|
|
||||||
let vote = |round, hash| {
|
|
||||||
random_vote(&collector, H520::random(), round, hash);
|
|
||||||
};
|
|
||||||
vote(6, Some(keccak("0")));
|
|
||||||
vote(3, Some(keccak("0")));
|
|
||||||
vote(7, Some(keccak("0")));
|
|
||||||
vote(8, Some(keccak("1")));
|
|
||||||
vote(1, Some(keccak("1")));
|
|
||||||
|
|
||||||
collector.throw_out_old(&7);
|
|
||||||
assert_eq!(collector.count_round_votes(&1), 0);
|
|
||||||
assert_eq!(collector.count_round_votes(&3), 0);
|
|
||||||
assert_eq!(collector.count_round_votes(&6), 0);
|
|
||||||
assert_eq!(collector.count_round_votes(&7), 1);
|
|
||||||
assert_eq!(collector.count_round_votes(&8), 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn malicious_authority() {
|
|
||||||
let collector = VoteCollector::default();
|
|
||||||
let round = 3;
|
|
||||||
// Vote is inserted fine.
|
|
||||||
assert!(full_vote(&collector, H520::random(), round, Some(keccak("0")), Address::default()));
|
|
||||||
// Returns the double voting address.
|
|
||||||
assert!(!full_vote(&collector, H520::random(), round, Some(keccak("1")), Address::default()));
|
|
||||||
assert_eq!(collector.count_round_votes(&round), 1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -35,7 +35,7 @@ use builtin::Builtin;
|
|||||||
use encoded;
|
use encoded;
|
||||||
use engines::{
|
use engines::{
|
||||||
EthEngine, NullEngine, InstantSeal, InstantSealParams, BasicAuthority,
|
EthEngine, NullEngine, InstantSeal, InstantSealParams, BasicAuthority,
|
||||||
AuthorityRound, Tendermint, DEFAULT_BLOCKHASH_CONTRACT
|
AuthorityRound, DEFAULT_BLOCKHASH_CONTRACT
|
||||||
};
|
};
|
||||||
use error::Error;
|
use error::Error;
|
||||||
use executive::Executive;
|
use executive::Executive;
|
||||||
@ -607,8 +607,6 @@ impl Spec {
|
|||||||
ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(basic_authority.params.into(), machine)),
|
ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(basic_authority.params.into(), machine)),
|
||||||
ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(authority_round.params.into(), machine)
|
ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(authority_round.params.into(), machine)
|
||||||
.expect("Failed to start AuthorityRound consensus engine."),
|
.expect("Failed to start AuthorityRound consensus engine."),
|
||||||
ethjson::spec::Engine::Tendermint(tendermint) => Tendermint::new(tendermint.params.into(), machine)
|
|
||||||
.expect("Failed to start the Tendermint consensus engine."),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -955,14 +953,6 @@ impl Spec {
|
|||||||
load_bundled!("authority_round_block_reward_contract")
|
load_bundled!("authority_round_block_reward_contract")
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new Spec with Tendermint consensus which does internal sealing (not requiring
|
|
||||||
/// work).
|
|
||||||
/// Account keccak("0") and keccak("1") are a authorities.
|
|
||||||
#[cfg(any(test, feature = "test-helpers"))]
|
|
||||||
pub fn new_test_tendermint() -> Self {
|
|
||||||
load_bundled!("tendermint")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// TestList.sol used in both specs: https://github.com/paritytech/contracts/pull/30/files
|
/// TestList.sol used in both specs: https://github.com/paritytech/contracts/pull/30/files
|
||||||
/// Accounts with secrets keccak("0") and keccak("1") are initially the validators.
|
/// Accounts with secrets keccak("0") and keccak("1") are initially the validators.
|
||||||
/// Create a new Spec with BasicAuthority which uses a contract at address 5 to determine
|
/// Create a new Spec with BasicAuthority which uses a contract at address 5 to determine
|
||||||
|
@ -125,81 +125,3 @@ fn authority_round() {
|
|||||||
assert_eq!(ci1.best_block_number, 5);
|
assert_eq!(ci1.best_block_number, 5);
|
||||||
assert_eq!(ci0.best_block_hash, ci1.best_block_hash);
|
assert_eq!(ci0.best_block_hash, ci1.best_block_hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tendermint() {
|
|
||||||
let s0 = KeyPair::from_secret_slice(&keccak("1")).unwrap();
|
|
||||||
let s1 = KeyPair::from_secret_slice(&keccak("0")).unwrap();
|
|
||||||
let ap = Arc::new(AccountProvider::transient_provider());
|
|
||||||
ap.insert_account(s0.secret().clone(), &"".into()).unwrap();
|
|
||||||
ap.insert_account(s1.secret().clone(), &"".into()).unwrap();
|
|
||||||
|
|
||||||
let chain_id = Spec::new_test_tendermint().chain_id();
|
|
||||||
let mut net = TestNet::with_spec_and_accounts(2, SyncConfig::default(), Spec::new_test_tendermint, Some(ap));
|
|
||||||
let io_handler0: Arc<IoHandler<ClientIoMessage>> = Arc::new(TestIoHandler::new(net.peer(0).chain.clone()));
|
|
||||||
let io_handler1: Arc<IoHandler<ClientIoMessage>> = Arc::new(TestIoHandler::new(net.peer(1).chain.clone()));
|
|
||||||
// Push transaction to both clients. Only one of them issues a proposal.
|
|
||||||
net.peer(0).miner.set_author(s0.address(), Some("".into())).unwrap();
|
|
||||||
trace!(target: "poa", "Peer 0 is {}.", s0.address());
|
|
||||||
net.peer(1).miner.set_author(s1.address(), Some("".into())).unwrap();
|
|
||||||
trace!(target: "poa", "Peer 1 is {}.", s1.address());
|
|
||||||
net.peer(0).chain.engine().register_client(Arc::downgrade(&net.peer(0).chain) as _);
|
|
||||||
net.peer(1).chain.engine().register_client(Arc::downgrade(&net.peer(1).chain) as _);
|
|
||||||
net.peer(0).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler0)));
|
|
||||||
net.peer(1).chain.set_io_channel(IoChannel::to_handler(Arc::downgrade(&io_handler1)));
|
|
||||||
// Exhange statuses
|
|
||||||
net.sync();
|
|
||||||
// Propose
|
|
||||||
net.peer(0).miner.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 0.into(), chain_id)).unwrap();
|
|
||||||
net.sync();
|
|
||||||
// Propose timeout, synchronous for now
|
|
||||||
net.peer(0).chain.engine().step();
|
|
||||||
net.peer(1).chain.engine().step();
|
|
||||||
// Prevote, precommit and commit
|
|
||||||
net.sync();
|
|
||||||
|
|
||||||
assert_eq!(net.peer(0).chain.chain_info().best_block_number, 1);
|
|
||||||
assert_eq!(net.peer(1).chain.chain_info().best_block_number, 1);
|
|
||||||
|
|
||||||
net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 0.into(), chain_id)).unwrap();
|
|
||||||
// Commit timeout
|
|
||||||
net.peer(0).chain.engine().step();
|
|
||||||
net.peer(1).chain.engine().step();
|
|
||||||
// Propose
|
|
||||||
net.sync();
|
|
||||||
// Propose timeout
|
|
||||||
net.peer(0).chain.engine().step();
|
|
||||||
net.peer(1).chain.engine().step();
|
|
||||||
// Prevote, precommit and commit
|
|
||||||
net.sync();
|
|
||||||
assert_eq!(net.peer(0).chain.chain_info().best_block_number, 2);
|
|
||||||
assert_eq!(net.peer(1).chain.chain_info().best_block_number, 2);
|
|
||||||
|
|
||||||
net.peer(0).miner.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 1.into(), chain_id)).unwrap();
|
|
||||||
net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 1.into(), chain_id)).unwrap();
|
|
||||||
// Peers get disconnected.
|
|
||||||
// Commit
|
|
||||||
net.peer(0).chain.engine().step();
|
|
||||||
net.peer(1).chain.engine().step();
|
|
||||||
// Propose
|
|
||||||
net.peer(0).chain.engine().step();
|
|
||||||
net.peer(1).chain.engine().step();
|
|
||||||
net.peer(0).miner.import_own_transaction(&*net.peer(0).chain, new_tx(s0.secret(), 2.into(), chain_id)).unwrap();
|
|
||||||
net.peer(1).miner.import_own_transaction(&*net.peer(1).chain, new_tx(s1.secret(), 2.into(), chain_id)).unwrap();
|
|
||||||
// Send different prevotes
|
|
||||||
net.sync();
|
|
||||||
// Prevote timeout
|
|
||||||
net.peer(0).chain.engine().step();
|
|
||||||
net.peer(1).chain.engine().step();
|
|
||||||
// Precommit and commit
|
|
||||||
net.sync();
|
|
||||||
// Propose timeout
|
|
||||||
net.peer(0).chain.engine().step();
|
|
||||||
net.peer(1).chain.engine().step();
|
|
||||||
net.sync();
|
|
||||||
let ci0 = net.peer(0).chain.chain_info();
|
|
||||||
let ci1 = net.peer(1).chain.chain_info();
|
|
||||||
assert_eq!(ci0.best_block_number, 3);
|
|
||||||
assert_eq!(ci1.best_block_number, 3);
|
|
||||||
assert_eq!(ci0.best_block_hash, ci1.best_block_hash);
|
|
||||||
}
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
//! Engine deserialization.
|
//! Engine deserialization.
|
||||||
|
|
||||||
use super::{Ethash, BasicAuthority, AuthorityRound, Tendermint, NullEngine, InstantSeal};
|
use super::{Ethash, BasicAuthority, AuthorityRound, NullEngine, InstantSeal};
|
||||||
|
|
||||||
/// Engine deserialization.
|
/// Engine deserialization.
|
||||||
#[derive(Debug, PartialEq, Deserialize)]
|
#[derive(Debug, PartialEq, Deserialize)]
|
||||||
@ -34,8 +34,6 @@ pub enum Engine {
|
|||||||
BasicAuthority(BasicAuthority),
|
BasicAuthority(BasicAuthority),
|
||||||
/// AuthorityRound engine.
|
/// AuthorityRound engine.
|
||||||
AuthorityRound(AuthorityRound),
|
AuthorityRound(AuthorityRound),
|
||||||
/// Tendermint engine.
|
|
||||||
Tendermint(Tendermint)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -133,20 +131,5 @@ mod tests {
|
|||||||
Engine::AuthorityRound(_) => {}, // AuthorityRound is unit tested in its own file.
|
Engine::AuthorityRound(_) => {}, // AuthorityRound is unit tested in its own file.
|
||||||
_ => panic!(),
|
_ => panic!(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let s = r#"{
|
|
||||||
"tendermint": {
|
|
||||||
"params": {
|
|
||||||
"validators": {
|
|
||||||
"list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"#;
|
|
||||||
let deserialized: Engine = serde_json::from_str(s).unwrap();
|
|
||||||
match deserialized {
|
|
||||||
Engine::Tendermint(_) => {}, // Tendermint is unit tested in its own file.
|
|
||||||
_ => panic!(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,6 @@ pub mod ethash;
|
|||||||
pub mod validator_set;
|
pub mod validator_set;
|
||||||
pub mod basic_authority;
|
pub mod basic_authority;
|
||||||
pub mod authority_round;
|
pub mod authority_round;
|
||||||
pub mod tendermint;
|
|
||||||
pub mod null_engine;
|
pub mod null_engine;
|
||||||
pub mod instant_seal;
|
pub mod instant_seal;
|
||||||
pub mod hardcoded_sync;
|
pub mod hardcoded_sync;
|
||||||
@ -45,7 +44,6 @@ pub use self::ethash::{Ethash, EthashParams, BlockReward};
|
|||||||
pub use self::validator_set::ValidatorSet;
|
pub use self::validator_set::ValidatorSet;
|
||||||
pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams};
|
pub use self::basic_authority::{BasicAuthority, BasicAuthorityParams};
|
||||||
pub use self::authority_round::{AuthorityRound, AuthorityRoundParams};
|
pub use self::authority_round::{AuthorityRound, AuthorityRoundParams};
|
||||||
pub use self::tendermint::{Tendermint, TendermintParams};
|
|
||||||
pub use self::null_engine::{NullEngine, NullEngineParams};
|
pub use self::null_engine::{NullEngine, NullEngineParams};
|
||||||
pub use self::instant_seal::{InstantSeal, InstantSealParams};
|
pub use self::instant_seal::{InstantSeal, InstantSealParams};
|
||||||
pub use self::hardcoded_sync::HardcodedSync;
|
pub use self::hardcoded_sync::HardcodedSync;
|
||||||
|
@ -1,71 +0,0 @@
|
|||||||
// Copyright 2015-2018 Parity Technologies (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 params deserialization.
|
|
||||||
|
|
||||||
use uint::Uint;
|
|
||||||
use super::ValidatorSet;
|
|
||||||
|
|
||||||
/// Tendermint params deserialization.
|
|
||||||
#[derive(Debug, PartialEq, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
#[serde(rename_all = "camelCase")]
|
|
||||||
pub struct TendermintParams {
|
|
||||||
/// Valid validators.
|
|
||||||
pub validators: ValidatorSet,
|
|
||||||
/// Propose step timeout in milliseconds.
|
|
||||||
pub timeout_propose: Option<Uint>,
|
|
||||||
/// Prevote step timeout in milliseconds.
|
|
||||||
pub timeout_prevote: Option<Uint>,
|
|
||||||
/// Precommit step timeout in milliseconds.
|
|
||||||
pub timeout_precommit: Option<Uint>,
|
|
||||||
/// Commit step timeout in milliseconds.
|
|
||||||
pub timeout_commit: Option<Uint>,
|
|
||||||
/// Reward per block.
|
|
||||||
pub block_reward: Option<Uint>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Tendermint engine deserialization.
|
|
||||||
#[derive(Debug, PartialEq, Deserialize)]
|
|
||||||
#[serde(deny_unknown_fields)]
|
|
||||||
pub struct Tendermint {
|
|
||||||
/// Ethash params.
|
|
||||||
pub params: TendermintParams,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use serde_json;
|
|
||||||
use ethereum_types::H160;
|
|
||||||
use hash::Address;
|
|
||||||
use spec::tendermint::Tendermint;
|
|
||||||
use spec::validator_set::ValidatorSet;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn tendermint_deserialization() {
|
|
||||||
let s = r#"{
|
|
||||||
"params": {
|
|
||||||
"validators": {
|
|
||||||
"list": ["0xc6d9d2cd449a754c494264e1809c50e34d64562b"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}"#;
|
|
||||||
|
|
||||||
let deserialized: Tendermint = serde_json::from_str(s).unwrap();
|
|
||||||
let vs = ValidatorSet::List(vec![Address(H160::from("0xc6d9d2cd449a754c494264e1809c50e34d64562b"))]);
|
|
||||||
assert_eq!(deserialized.params.validators, vs);
|
|
||||||
}
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user