add Vote generation
This commit is contained in:
parent
cb2c9938a1
commit
096b71feb2
@ -25,10 +25,12 @@ pub type LogBloom = H2048;
|
|||||||
pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]);
|
pub static ZERO_LOGBLOOM: LogBloom = H2048([0x00; 256]);
|
||||||
|
|
||||||
#[cfg_attr(feature="dev", allow(enum_variant_names))]
|
#[cfg_attr(feature="dev", allow(enum_variant_names))]
|
||||||
/// Semantic boolean for when a seal/signature is included.
|
/// Enum for when a seal/signature is included.
|
||||||
pub enum Seal {
|
pub enum Seal {
|
||||||
/// The seal/signature is included.
|
/// The seal/signature is included.
|
||||||
With,
|
With,
|
||||||
/// The seal/signature is not included.
|
/// The seal/signature is not included.
|
||||||
Without,
|
Without,
|
||||||
|
/// First N fields of seal are included.
|
||||||
|
WithSome(usize),
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
mod message;
|
mod message;
|
||||||
mod timeout;
|
mod timeout;
|
||||||
mod params;
|
mod params;
|
||||||
|
mod vote;
|
||||||
|
|
||||||
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
use std::sync::atomic::{AtomicUsize, Ordering as AtomicOrdering};
|
||||||
use common::*;
|
use common::*;
|
||||||
@ -28,20 +29,22 @@ use account_provider::AccountProvider;
|
|||||||
use block::*;
|
use block::*;
|
||||||
use spec::CommonParams;
|
use spec::CommonParams;
|
||||||
use engines::{Engine, EngineError, ProposeCollect};
|
use engines::{Engine, EngineError, ProposeCollect};
|
||||||
|
use blockchain::extras::BlockDetails;
|
||||||
use evm::Schedule;
|
use evm::Schedule;
|
||||||
use io::IoService;
|
use io::IoService;
|
||||||
use self::message::ConsensusMessage;
|
use self::message::ConsensusMessage;
|
||||||
use self::timeout::{TimerHandler, NextStep};
|
use self::timeout::{TimerHandler, NextStep};
|
||||||
use self::params::TendermintParams;
|
use self::params::TendermintParams;
|
||||||
|
use self::vote::Vote;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Step {
|
enum Step {
|
||||||
Propose,
|
Propose,
|
||||||
Prevote(ProposeCollect),
|
Prevote(ProposeCollect),
|
||||||
/// Precommit step storing the precommit vote and accumulating seal.
|
/// Precommit step storing the precommit vote and accumulating seal.
|
||||||
Precommit(ProposeCollect, Seal),
|
Precommit(ProposeCollect, Signatures),
|
||||||
/// Commit step storing a complete valid seal.
|
/// Commit step storing a complete valid seal.
|
||||||
Commit(BlockHash, Seal)
|
Commit(BlockHash, Signatures)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub type Height = usize;
|
pub type Height = usize;
|
||||||
@ -49,7 +52,7 @@ pub type Round = usize;
|
|||||||
pub type BlockHash = H256;
|
pub type BlockHash = H256;
|
||||||
|
|
||||||
pub type AtomicMs = AtomicUsize;
|
pub type AtomicMs = AtomicUsize;
|
||||||
type Seal = Vec<Bytes>;
|
type Signatures = Vec<Bytes>;
|
||||||
|
|
||||||
/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain.
|
/// Engine using `Tendermint` consensus algorithm, suitable for EVM chain.
|
||||||
pub struct Tendermint {
|
pub struct Tendermint {
|
||||||
@ -57,6 +60,8 @@ pub struct Tendermint {
|
|||||||
our_params: TendermintParams,
|
our_params: TendermintParams,
|
||||||
builtins: BTreeMap<Address, Builtin>,
|
builtins: BTreeMap<Address, Builtin>,
|
||||||
timeout_service: IoService<NextStep>,
|
timeout_service: IoService<NextStep>,
|
||||||
|
/// Address to be used as authority.
|
||||||
|
authority: RwLock<Address>,
|
||||||
/// Consensus round.
|
/// Consensus round.
|
||||||
r: AtomicUsize,
|
r: AtomicUsize,
|
||||||
/// Consensus step.
|
/// Consensus step.
|
||||||
@ -77,6 +82,7 @@ impl Tendermint {
|
|||||||
our_params: our_params,
|
our_params: our_params,
|
||||||
builtins: builtins,
|
builtins: builtins,
|
||||||
timeout_service: IoService::<NextStep>::start().expect("Error creating engine timeout service"),
|
timeout_service: IoService::<NextStep>::start().expect("Error creating engine timeout service"),
|
||||||
|
authority: RwLock::new(Address::default()),
|
||||||
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)
|
||||||
@ -86,22 +92,26 @@ impl Tendermint {
|
|||||||
engine
|
engine
|
||||||
}
|
}
|
||||||
|
|
||||||
fn proposer(&self) -> Address {
|
|
||||||
let ref p = self.our_params;
|
|
||||||
p.validators.get(self.proposer_nonce.load(AtomicOrdering::Relaxed)%p.validator_n).unwrap().clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_proposer(&self, address: &Address) -> bool {
|
fn is_proposer(&self, address: &Address) -> bool {
|
||||||
self.proposer() == *address
|
self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_validator(&self, address: &Address) -> bool {
|
fn nonce_proposer(&self, proposer_nonce: usize) -> &Address {
|
||||||
self.our_params.validators.contains(address)
|
let ref p = self.our_params;
|
||||||
|
p.authorities.get(proposer_nonce%p.authority_n).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_nonce_proposer(&self, proposer_nonce: usize, address: &Address) -> bool {
|
||||||
|
self.nonce_proposer(proposer_nonce) == address
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_authority(&self, address: &Address) -> bool {
|
||||||
|
self.our_params.authorities.contains(address)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_vote(&self, proposal: BlockHash) -> ProposeCollect {
|
fn new_vote(&self, proposal: BlockHash) -> ProposeCollect {
|
||||||
ProposeCollect::new(proposal,
|
ProposeCollect::new(proposal,
|
||||||
self.our_params.validators.iter().cloned().collect(),
|
self.our_params.authorities.iter().cloned().collect(),
|
||||||
self.threshold())
|
self.threshold())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,7 +202,7 @@ impl Tendermint {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn threshold(&self) -> usize {
|
fn threshold(&self) -> usize {
|
||||||
self.our_params.validator_n*2/3
|
self.our_params.authority_n*2/3
|
||||||
}
|
}
|
||||||
|
|
||||||
fn next_timeout(&self) -> u64 {
|
fn next_timeout(&self) -> u64 {
|
||||||
@ -203,8 +213,8 @@ impl Tendermint {
|
|||||||
impl Engine for Tendermint {
|
impl Engine for Tendermint {
|
||||||
fn name(&self) -> &str { "Tendermint" }
|
fn name(&self) -> &str { "Tendermint" }
|
||||||
fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) }
|
fn version(&self) -> SemanticVersion { SemanticVersion::new(1, 0, 0) }
|
||||||
/// Possibly signatures of all validators.
|
/// (consensus round, proposal signature, authority signatures)
|
||||||
fn seal_fields(&self) -> usize { 2 }
|
fn seal_fields(&self) -> usize { 3 }
|
||||||
|
|
||||||
fn params(&self) -> &CommonParams { &self.params }
|
fn params(&self) -> &CommonParams { &self.params }
|
||||||
fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins }
|
fn builtins(&self) -> &BTreeMap<Address, Builtin> { &self.builtins }
|
||||||
@ -229,34 +239,62 @@ impl Engine for Tendermint {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply the block reward on finalisation of the block.
|
/// Get the address to be used as authority.
|
||||||
|
fn on_new_block(&self, block: &mut ExecutedBlock) {
|
||||||
|
if let Some(mut authority) = self.authority.try_write() {
|
||||||
|
*authority = *block.header().author()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set author to proposer.
|
||||||
/// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current).
|
/// This assumes that all uncles are valid uncles (i.e. of at least one generation before the current).
|
||||||
fn on_close_block(&self, _block: &mut ExecutedBlock) {}
|
fn on_close_block(&self, _block: &mut ExecutedBlock) {}
|
||||||
|
|
||||||
/// Attempt to seal the block internally using all available signatures.
|
/// Attempt to seal the block internally using all available signatures.
|
||||||
///
|
///
|
||||||
/// None is returned if not enough signatures can be collected.
|
/// None is returned if not enough signatures can be collected.
|
||||||
fn generate_seal(&self, block: &ExecutedBlock, _accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> {
|
fn generate_seal(&self, block: &ExecutedBlock, accounts: Option<&AccountProvider>) -> Option<Vec<Bytes>> {
|
||||||
self.s.try_read().and_then(|s| match *s {
|
if let (Some(ap), Some(step)) = (accounts, self.s.try_read()) {
|
||||||
Step::Commit(hash, ref seal) if hash == block.header().bare_hash() => Some(seal.clone()),
|
let header = block.header();
|
||||||
_ => None,
|
let author = header.author();
|
||||||
})
|
match *step {
|
||||||
|
Step::Commit(hash, ref seal) if hash == header.bare_hash() =>
|
||||||
|
// Commit the block using a complete signature set.
|
||||||
|
return Some(seal.clone()),
|
||||||
|
Step::Propose if self.is_proposer(header.author()) =>
|
||||||
|
// Seal block with propose signature.
|
||||||
|
if let Some(proposal) = Vote::propose(header, &ap) {
|
||||||
|
return Some(vec![::rlp::encode(&proposal).to_vec(), Vec::new()])
|
||||||
|
} else {
|
||||||
|
trace!(target: "basicauthority", "generate_seal: FAIL: accounts secret key unavailable");
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
trace!(target: "basicauthority", "generate_seal: FAIL: accounts not provided");
|
||||||
|
}
|
||||||
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result<Bytes, Error> {
|
fn handle_message(&self, sender: Address, signature: H520, message: UntrustedRlp) -> Result<Bytes, Error> {
|
||||||
let c: ConsensusMessage = try!(message.as_val());
|
let message: ConsensusMessage = try!(message.as_val());
|
||||||
println!("{:?}", c);
|
try!(Err(EngineError::UnknownStep))
|
||||||
|
//match message {
|
||||||
|
// ConsensusMessage::Prevote
|
||||||
|
//}
|
||||||
|
|
||||||
|
|
||||||
// Check if correct round.
|
// Check if correct round.
|
||||||
if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) {
|
//if self.r.load(AtomicOrdering::Relaxed) != try!(message.val_at(0)) {
|
||||||
try!(Err(EngineError::WrongRound))
|
// try!(Err(EngineError::WrongRound))
|
||||||
}
|
//}
|
||||||
// Handle according to step.
|
// Handle according to step.
|
||||||
match try!(message.val_at(1)) {
|
// match try!(message.val_at(1)) {
|
||||||
0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))),
|
// 0u8 if self.is_proposer(&sender) => self.propose_message(try!(message.at(2))),
|
||||||
1 if self.is_validator(&sender) => self.prevote_message(sender, try!(message.at(2))),
|
// 1 if self.is_authority(&sender) => self.prevote_message(sender, try!(message.at(2))),
|
||||||
2 if self.is_validator(&sender) => self.precommit_message(sender, signature, try!(message.at(2))),
|
// 2 if self.is_authority(&sender) => self.precommit_message(sender, signature, try!(message.at(2))),
|
||||||
_ => try!(Err(EngineError::UnknownStep)),
|
// _ => try!(Err(EngineError::UnknownStep)),
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
fn verify_block_basic(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
@ -270,18 +308,19 @@ impl Engine for Tendermint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Also transitions to Prevote if verifying Proposal.
|
||||||
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
fn verify_block_unordered(&self, header: &Header, _block: Option<&[u8]>) -> Result<(), Error> {
|
||||||
let to_address = |b: &Vec<u8>| {
|
let to_address = |b: &Vec<u8>| {
|
||||||
let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val());
|
let sig: H520 = try!(UntrustedRlp::new(b.as_slice()).as_val());
|
||||||
Ok(public_to_address(&try!(recover(&sig.into(), &header.bare_hash()))))
|
Ok(public_to_address(&try!(recover(&sig.into(), &header.bare_hash()))))
|
||||||
};
|
};
|
||||||
let validator_set = self.our_params.validators.iter().cloned().collect();
|
let authority_set = self.our_params.authorities.iter().cloned().collect();
|
||||||
let seal_set = try!(header
|
let seal_set = try!(header
|
||||||
.seal()
|
.seal()
|
||||||
.iter()
|
.iter()
|
||||||
.map(to_address)
|
.map(to_address)
|
||||||
.collect::<Result<HashSet<_>, Error>>());
|
.collect::<Result<HashSet<_>, Error>>());
|
||||||
if seal_set.intersection(&validator_set).count() <= self.threshold() {
|
if seal_set.intersection(&authority_set).count() <= self.threshold() {
|
||||||
try!(Err(BlockError::InvalidSeal))
|
try!(Err(BlockError::InvalidSeal))
|
||||||
} else {
|
} else {
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -315,6 +354,10 @@ impl Engine for Tendermint {
|
|||||||
fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> {
|
fn verify_transaction(&self, t: &SignedTransaction, _header: &Header) -> Result<(), Error> {
|
||||||
t.sender().map(|_|()) // Perform EC recovery and cache sender
|
t.sender().map(|_|()) // Perform EC recovery and cache sender
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn is_new_best_block(&self, _best_total_difficulty: U256, best_header: HeaderView, _parent_details: &BlockDetails, new_header: &HeaderView) -> bool {
|
||||||
|
new_header.seal().get(1).expect("Tendermint seal should have two elements.").len() > best_header.seal().get(1).expect("Tendermint seal should have two elements.").len()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -465,9 +508,9 @@ mod tests {
|
|||||||
let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new());
|
let tender = Tendermint::new(engine.params().clone(), TendermintParams::default(), BTreeMap::new());
|
||||||
|
|
||||||
let genesis_header = spec.genesis_header();
|
let genesis_header = spec.genesis_header();
|
||||||
let mut db_result = get_temp_journal_db();
|
let mut db_result = get_temp_state_db();
|
||||||
let mut db = db_result.take();
|
let mut db = db_result.take();
|
||||||
spec.ensure_db_good(db.as_hashdb_mut()).unwrap();
|
spec.ensure_db_good(&mut db).unwrap();
|
||||||
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
let last_hashes = Arc::new(vec![genesis_header.hash()]);
|
||||||
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
let b = OpenBlock::new(engine, Default::default(), false, db, &genesis_header, last_hashes, Address::default(), (3141562.into(), 31415620.into()), vec![]).unwrap();
|
||||||
let b = b.close_and_lock();
|
let b = b.close_and_lock();
|
||||||
@ -484,8 +527,8 @@ mod tests {
|
|||||||
let tap = AccountProvider::transient_provider();
|
let tap = AccountProvider::transient_provider();
|
||||||
let r = 0;
|
let r = 0;
|
||||||
|
|
||||||
let not_validator_addr = tap.insert_account("101".sha3(), "101").unwrap();
|
let not_authority_addr = tap.insert_account("101".sha3(), "101").unwrap();
|
||||||
assert!(propose_default(&engine, r, not_validator_addr).is_err());
|
assert!(propose_default(&engine, r, not_authority_addr).is_err());
|
||||||
|
|
||||||
let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap();
|
let not_proposer_addr = tap.insert_account("0".sha3(), "0").unwrap();
|
||||||
assert!(propose_default(&engine, r, not_proposer_addr).is_err());
|
assert!(propose_default(&engine, r, not_proposer_addr).is_err());
|
||||||
|
63
ethcore/src/engines/tendermint/vote.rs
Normal file
63
ethcore/src/engines/tendermint/vote.rs
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
// 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 block seal.
|
||||||
|
|
||||||
|
use common::{H256, Address, H520, Header};
|
||||||
|
use util::Hashable;
|
||||||
|
use account_provider::AccountProvider;
|
||||||
|
use rlp::{View, DecoderError, Decodable, Decoder, Encodable, RlpStream, Stream};
|
||||||
|
use basic_types::Seal;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Vote {
|
||||||
|
signature: H520
|
||||||
|
}
|
||||||
|
|
||||||
|
fn message(header: &Header) -> H256 {
|
||||||
|
header.rlp(Seal::WithSome(1)).sha3()
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Vote {
|
||||||
|
fn new(signature: H520) -> Vote { Vote { signature: signature }}
|
||||||
|
|
||||||
|
/// Try to use the author address to create a vote.
|
||||||
|
pub fn propose(header: &Header, accounts: &AccountProvider) -> Option<Vote> {
|
||||||
|
accounts.sign(*header.author(), message(&header)).ok().map(Into::into).map(Self::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Use any unlocked validator account to create a vote.
|
||||||
|
pub fn validate(header: &Header, accounts: &AccountProvider, validator: Address) -> Option<Vote> {
|
||||||
|
accounts.sign(validator, message(&header)).ok().map(Into::into).map(Self::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decodable for Vote {
|
||||||
|
fn decode<D>(decoder: &D) -> Result<Self, DecoderError> where D: Decoder {
|
||||||
|
let rlp = decoder.as_rlp();
|
||||||
|
if decoder.as_raw().len() != try!(rlp.payload_info()).total() {
|
||||||
|
return Err(DecoderError::RlpIsTooBig);
|
||||||
|
}
|
||||||
|
rlp.as_val().map(Self::new)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encodable for Vote {
|
||||||
|
fn rlp_append(&self, s: &mut RlpStream) {
|
||||||
|
let Vote { ref signature } = *self;
|
||||||
|
s.append(signature);
|
||||||
|
}
|
||||||
|
}
|
@ -226,7 +226,8 @@ impl Header {
|
|||||||
// TODO: make these functions traity
|
// TODO: make these functions traity
|
||||||
/// Place this header into an RLP stream `s`, optionally `with_seal`.
|
/// Place this header into an RLP stream `s`, optionally `with_seal`.
|
||||||
pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) {
|
pub fn stream_rlp(&self, s: &mut RlpStream, with_seal: Seal) {
|
||||||
s.begin_list(13 + match with_seal { Seal::With => self.seal.len(), _ => 0 });
|
let seal_n = match with_seal { Seal::With => self.seal.len(), Seal::WithSome(n) => n, _ => 0 };
|
||||||
|
s.begin_list(13 + seal_n);
|
||||||
s.append(&self.parent_hash);
|
s.append(&self.parent_hash);
|
||||||
s.append(&self.uncles_hash);
|
s.append(&self.uncles_hash);
|
||||||
s.append(&self.author);
|
s.append(&self.author);
|
||||||
@ -240,10 +241,8 @@ impl Header {
|
|||||||
s.append(&self.gas_used);
|
s.append(&self.gas_used);
|
||||||
s.append(&self.timestamp);
|
s.append(&self.timestamp);
|
||||||
s.append(&self.extra_data);
|
s.append(&self.extra_data);
|
||||||
if let Seal::With = with_seal {
|
for b in self.seal.iter().take(seal_n) {
|
||||||
for b in &self.seal {
|
s.append_raw(b, 1);
|
||||||
s.append_raw(b, 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user