ethcore: add clique engine (#9981)

* fix broken sync

* correct seal fields

* ethcore: fix comment

* parity: remove duplicate params

* clique: fix whitespaces

* ethcore: fix goerli chain spec

* refactor signer_snapshot into pending/finalized state

* move close_block_extra_data after seal is applied

* refactor most of the logic into the signer_snapshot

* clique: refactor locking logic out of the consensus engine interface

* Fix jsonspec and add an unittest

* Replace space with tabs

* Unbroke sync

* Fix broken sync

* 1/2 state tracking without votes

* 2/2 implement vote tracking

* ci: use travis for goerli

* ci: setup a clique network

* ci: sync a görli node

* add clique deploy script

* ci: fix paths in clique deploy script

* ci: use docker compose

* ci: fix travis job names

* ci: fix build deps

* ci: massively reduce tests

* Revert "ci: massively reduce tests"

This reverts commit 6369f0b069ed2607a7e9f2e1d85489bacdc43384.

* ci: run cargo test directly

* ci: separate build and test stages

* ci: cache rust installation

* ci: simplify ci stages

* ci: make clique deploy script executable

* ci: shutdown goerli sync after 20min

* ci: remove slow sync stage

* ci: use timeout to finish jobs

* ci: fix build path

* ci: use absolute paths to end this confusion

* ci: add geth and parity to path

* ci: be more verbose

* ci: allow for more relaxed caching timeout

* ci: update repositories for custom ppa

* ci: fix typo in file name

* ci: fix docker compose file

* ci: add ethkey to docker

* ci: make sure deploy script is up to date with upstream

* ci: stop docker container after certain time

* ci: force superuser to update permissions on docker files

* ci: reduce run time of script to ~30 min

* ci: remove duplicate caching in travis

* remove trace statements

* clique: add more validation involving the recent signer list

* ethcore: enable constantinople for rinkeby

* ethcore: fix whitespaces in rinkeby spec

* ethcore: reformat goerli.json

* Revert "ci: remove duplicate caching in travis"

This reverts commit a562838d3d194d37f9871dcbe00b783637978f89.

* tmp commit

* another tmp commit

* it builds!

* add sealing capabilities

* add seal_header hook to allow separation of block seal/importing code paths

* clique: remove populate_from_parent.

* add panic

* make turn delay random

* initialize OpenBlock properly in 'enact'

* misc: remove duplicate lines

* misc: fix license headers

* misc: convert spaces to tabs

* misc: fix tabs

* Update Cargo.toml

* Update Cargo.toml

* Update Cargo.toml

* clique: ensure validator restores state before trying to seal

* clique: make 'state' return an Error.  Make some error messages more clear

* Fix compile error after rebase & toolchain upgrade

* fix a bunch of import warnings

* Refactor code

* Fix permissions

* Refactoring syncing

* Implement full validator checks

* Refactor util functions to seperate file

* mining 1

* ethcore: add chainspec for kotti

* ethcore: rename pre-goerli configs

* ethcore: load kotti chain spec

* cli: add kotti to params

* Implement working local sealing

* making sealing & syncing work together

* Relax timestamp checking

* ethcore: prepare for the real goerli to launch

* Implement NOTURN wiggle properly & cleanupnup warnings

* Implement vote casting

* Update docs & skip signing if no signer

* Optimize step-service interval

* Record state on local sealed block

* Fix script filemode

* Cleaning up codebase

* restore enact trace logging

* Delete clique.sh and move sync.sh

* remove travis.yml

* Remove dead code

* Cleanup compile warning

* address review comments

* adding more comments and removing unwrap()

* ci: remove sync script

* Address review comments

* fix compile error

* adding better debugging for timing

* Implement an dedicated thread for sealing timing

* fix(add helper for timestamp overflows) (#10330)

* fix(add helper timestamp overflows)

* fix(simplify code)

* fix(make helper private)

* snap: official image / test (#10168)

* official image / test

* fix / test

* bit more necromancy

* fix paths

* add source bin/df /test

* add source bin/df /test2

* something w paths /test

* something w paths /test

* add source-type /test

* show paths /test

* copy plugin /test

* plugin -> nil

* install rhash

* no questions while installing rhash

* publish snap only for release

* fix(docker): fix not receives SIGINT (#10059)

* fix(docker): fix not receives SIGINT

* fix: update with reviews

* update with review

* update

* update

* Don't add discovery initiators to the node table (#10305)

* Don't add discovery initiators to the node table

* Use enums for tracking state of the nodes in discovery

* Dont try to ping ourselves

* Fix minor nits

* Update timeouts when observing an outdated node

* Extracted update_bucket_record from update_node

* Fixed typo

* Fix two final nits from @todr

* change docker image based on debian instead of ubuntu due to the chan… (#10336)

* change docker image based on debian instead of ubuntu due to the changes of the build container

* role back docker build image and docker deploy image to ubuntu:xenial based (#10338)

* Bundle protocol and packet_id together in chain sync (#10315)

Define a new `enum` where devp2p subprotocol packet ids (currently eth and par) are defined. Additionally provide functionality to query id value and protocol of a given id object.

* snap: prefix version and populate candidate channel (#10343)

* snap: populate candidate releases with beta snaps to avoid stale channel

* snap: prefix version with v*

* addressing review comments

* engine: fix copyright header

* scripts: restore permissions on sign command

* ethcore: enforce tabs

* ethcore: enforce tabs

* ethcore: enforce tabs

* addressing comments

* addressing comments

* addressing more comments

* addressing more comments

* addressing more comments

* addressing more comments

* addressing more comments

* json-spec: fix clique epoch to non-zero u64

* ci: enable travis for parity goerli

* ci: don't separate build and test step

* ci: don't run c++ tests on travis

* ci: simplify cargo test to squeeze into travis timeout

* ci: don't run tests on travis at all

* style(fixes)

* fix(add tests)

* fix(recent_signer bug)

* fix(complete all tests)

* fix(nits)

* fix(simplify asserts)

* fix(cliqueState): simplify code

* fix(nits)

* docs(comments what's need to fixed)

* fix(revert unintended changes)

* fix(tests)

* fix(logs): voting logs

* fix(readability + more logs)

* fix(sync)

* docs(add missing licens header)

* fix(log): info! -> trace!

* docs(fix nits) + fix(remove assert)

* perf(use counter instead of vec)

* fix(remove needless block in match)

* fix(faulty comment)

* grumbles(docs for tests)

* fix(nits)

* fix(revert_vote): only remove vote when votes == 0

* fix(vote counter): checked arithmetics

* fix(simplify tests)

* fix(nits)

* fix(clique): err types

* fix(clique utils): make use of errors

* fix(cleanup nits)

* fix(clique sealing): don't read state no signer

* fix(replace Vec<Signers> with BTreeSet<Signers>)

* fix(tests): BTreeSet and more generic helpers

* fix(nits)

* fix(ethcore_block_seal): remove needless `Box`

* fix(faulty log): info -> trace

* fix(checked SystemTime): prevent SystemTime panics

* style(chain cfg): space after `:`

* style(fn enact): fix whitespace

* docs(clique): StepService

* docs(nit): fix faulty comment

* docs(fix typo)

* style(fix bad indentation)

* fix(bad regex match)

* grumble(on_seal_block): make `&mut` to avoid clone

* docs(on_seal_block): fix faulty documentation

* Delete .travis.yml

* docs: remove eth hf references in spec

* Update client.rs

* fix(nits)

* fix(clique step): `RwLock` -> `AtomicBool`

* fix(clique): use `Duration::as_millis`

* Clean up some Clique documentation

Co-authored-by: soc1c <soc1c@users.noreply.github.com>
Co-authored-by: HCastano <HCastano@users.noreply.github.com>
Co-authored-by: niklasad1 <niklasad1@users.noreply.github.com>
Co-authored-by: jwasinger <jwasinger@users.noreply.github.com>
Co-authored-by: ChainSafe <ChainSafe@users.noreply.github.com>
Co-authored-by: thefallentree <thefallentree@users.noreply.github.com>
Co-authored-by: 5chdn <5chdn@users.noreply.github.com>
This commit is contained in:
5chdn
2019-03-26 23:31:52 +01:00
committed by soc1c
parent 9cb8606103
commit aa8487c1d0
22 changed files with 5055 additions and 37 deletions

View File

@@ -284,7 +284,6 @@ impl<'x> OpenBlock<'x> {
self.block.header.set_difficulty(*header.difficulty());
self.block.header.set_gas_limit(*header.gas_limit());
self.block.header.set_timestamp(header.timestamp());
self.block.header.set_author(*header.author());
self.block.header.set_uncles_hash(*header.uncles_hash());
self.block.header.set_transactions_root(*header.transactions_root());
// TODO: that's horrible. set only for backwards compatibility
@@ -405,15 +404,20 @@ impl LockedBlock {
/// Provide a valid seal in order to turn this into a `SealedBlock`.
///
/// NOTE: This does not check the validity of `seal` with the engine.
pub fn seal(self, engine: &EthEngine, seal: Vec<Bytes>) -> Result<SealedBlock, BlockError> {
let expected_seal_fields = engine.seal_fields(&self.block.header);
pub fn seal(self, engine: &EthEngine, seal: Vec<Bytes>) -> Result<SealedBlock, Error> {
let expected_seal_fields = engine.seal_fields(&self.header);
let mut s = self;
if seal.len() != expected_seal_fields {
return Err(BlockError::InvalidSealArity(
Mismatch { expected: expected_seal_fields, found: seal.len() }));
Err(BlockError::InvalidSealArity(Mismatch {
expected: expected_seal_fields,
found: seal.len()
}))?;
}
s.block.header.set_seal(seal);
engine.on_seal_block(&mut s.block)?;
s.block.header.compute_hash();
Ok(SealedBlock {
block: s.block
})
@@ -422,6 +426,7 @@ impl LockedBlock {
/// Provide a valid seal in order to turn this into a `SealedBlock`.
/// This does check the validity of `seal` with the engine.
/// Returns the `ClosedBlock` back again if the seal is no good.
/// TODO(https://github.com/paritytech/parity-ethereum/issues/10407): This is currently only used in POW chain call paths, we should really merge it with seal() above.
pub fn try_seal(
self,
engine: &EthEngine,
@@ -463,7 +468,7 @@ impl Drain for SealedBlock {
}
/// Enact the block given by block header, transactions and uncles
fn enact(
pub(crate) fn enact(
header: Header,
transactions: Vec<SignedTransaction>,
uncles: Vec<Header>,
@@ -476,13 +481,12 @@ fn enact(
is_epoch_begin: bool,
ancestry: &mut Iterator<Item=ExtendedHeader>,
) -> Result<LockedBlock, Error> {
{
if ::log::max_level() >= ::log::Level::Trace {
let s = State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(parent.number() + 1), factories.clone())?;
trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n",
header.number(), s.root(), header.author(), s.balance(&header.author())?);
}
}
// For trace log
let trace_state = if log_enabled!(target: "enact", ::log::Level::Trace) {
Some(State::from_existing(db.boxed_clone(), parent.state_root().clone(), engine.account_start_nonce(parent.number() + 1), factories.clone())?)
} else {
None
};
let mut b = OpenBlock::new(
engine,
@@ -491,13 +495,23 @@ fn enact(
db,
parent,
last_hashes,
Address::new(),
// Engine such as Clique will calculate author from extra_data.
// this is only important for executing contracts as the 'executive_author'.
engine.executive_author(&header)?,
(3141562.into(), 31415620.into()),
vec![],
is_epoch_begin,
ancestry,
)?;
if let Some(ref s) = trace_state {
let env = b.env_info();
let root = s.root();
let author_balance = s.balance(&env.author)?;
trace!(target: "enact", "num={}, root={}, author={}, author_balance={}\n",
b.block.header.number(), root, env.author, author_balance);
}
b.populate_from(&header);
b.push_transactions(transactions)?;
@@ -563,6 +577,7 @@ mod tests {
last_hashes: Arc<LastHashes>,
factories: Factories,
) -> Result<LockedBlock, Error> {
let block = Unverified::from_rlp(block_bytes)?;
let header = block.header;
let transactions: Result<Vec<_>, Error> = block
@@ -617,7 +632,7 @@ mod tests {
) -> Result<SealedBlock, Error> {
let header = Unverified::from_rlp(block_bytes.clone())?.header;
Ok(enact_bytes(block_bytes, engine, tracing, db, parent, last_hashes, factories)?
.seal(engine, header.seal().to_vec())?)
.seal(engine, header.seal().to_vec())?)
}
#[test]

View File

@@ -399,6 +399,7 @@ impl Importer {
let db = client.state_db.read().boxed_clone_canon(header.parent_hash());
let is_epoch_begin = chain.epoch_transition(parent.number(), *header.parent_hash()).is_some();
let enact_result = enact_verified(
block,
engine,
@@ -2515,7 +2516,11 @@ impl SnapshotClient for Client {}
impl Drop for Client {
fn drop(&mut self) {
self.engine.stop();
if let Some(c) = Arc::get_mut(&mut self.engine) {
c.stop()
} else {
warn!(target: "shutdown", "unable to get mut ref for engine for shutdown.");
}
}
}

View File

@@ -0,0 +1,369 @@
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
use std::collections::{HashMap, BTreeSet, VecDeque};
use std::fmt;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use engines::EngineError;
use engines::clique::util::{extract_signers, recover_creator};
use engines::clique::{VoteType, DIFF_INTURN, DIFF_NOTURN, NULL_AUTHOR, SIGNING_DELAY_NOTURN_MS};
use error::{Error, BlockError};
use ethereum_types::{Address, H64};
use rand::Rng;
use types::BlockNumber;
use types::header::Header;
use unexpected::Mismatch;
#[cfg(not(feature = "time_checked_add"))]
use time_utils::CheckedSystemTime;
/// Type that keeps track of the state for a given vote
// Votes that go against the proposal aren't counted since it's equivalent to not voting
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct VoteState {
kind: VoteType,
votes: u64,
}
/// Type that represent a vote
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub struct Vote {
block_number: BlockNumber,
beneficiary: Address,
kind: VoteType,
signer: Address,
reverted: bool,
}
/// Type that represent a pending vote
#[derive(Copy, Clone, Debug, Eq, Hash, PartialEq, PartialOrd)]
pub struct PendingVote {
signer: Address,
beneficiary: Address,
}
/// Clique state for each block.
#[cfg(not(test))]
#[derive(Clone, Debug, Default)]
pub struct CliqueBlockState {
/// Current votes for a beneficiary
votes: HashMap<PendingVote, VoteState>,
/// A list of all votes for the given epoch
votes_history: Vec<Vote>,
/// a list of all valid signer, sorted by ascending order.
signers: BTreeSet<Address>,
/// a deque of recent signer, new entry should be pushed front, apply() modifies this.
recent_signers: VecDeque<Address>,
/// inturn signing should wait until this time
pub next_timestamp_inturn: Option<SystemTime>,
/// noturn signing should wait until this time
pub next_timestamp_noturn: Option<SystemTime>,
}
#[cfg(test)]
#[derive(Clone, Debug, Default)]
pub struct CliqueBlockState {
/// All recorded votes for a given signer, `Vec<PendingVote>` is a stack of votes
pub votes: HashMap<PendingVote, VoteState>,
/// A list of all votes for the given epoch
pub votes_history: Vec<Vote>,
/// a list of all valid signer, sorted by ascending order.
pub signers: BTreeSet<Address>,
/// a deque of recent signer, new entry should be pushed front, apply() modifies this.
pub recent_signers: VecDeque<Address>,
/// inturn signing should wait until this time
pub next_timestamp_inturn: Option<SystemTime>,
/// noturn signing should wait until this time
pub next_timestamp_noturn: Option<SystemTime>,
}
impl fmt::Display for CliqueBlockState {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let signers: Vec<String> = self.signers.iter()
.map(|s|
format!("{} {:?}",
s,
self.votes.iter().map(|(v, s)| format!("[beneficiary {}, votes: {}]", v.beneficiary, s.votes))
.collect::<Vec<_>>()
)
)
.collect();
let recent_signers: Vec<String> = self.recent_signers.iter().map(|s| format!("{}", s)).collect();
let num_votes = self.votes_history.len();
let add_votes = self.votes_history.iter().filter(|v| v.kind == VoteType::Add).count();
let rm_votes = self.votes_history.iter().filter(|v| v.kind == VoteType::Remove).count();
let reverted_votes = self.votes_history.iter().filter(|v| v.reverted).count();
write!(f,
"Votes {{ \n signers: {:?} \n recent_signers: {:?} \n number of votes: {} \n number of add votes {}
\r number of remove votes {} \n number of reverted votes: {}}}",
signers, recent_signers, num_votes, add_votes, rm_votes, reverted_votes)
}
}
impl CliqueBlockState {
/// Create new state with given information, this is used creating new state from Checkpoint block.
pub fn new(signers: BTreeSet<Address>) -> Self {
CliqueBlockState {
signers,
..Default::default()
}
}
// see https://github.com/ethereum/go-ethereum/blob/master/consensus/clique/clique.go#L474
fn verify(&self, header: &Header) -> Result<Address, Error> {
let creator = recover_creator(header)?.clone();
// The signer is not authorized
if !self.signers.contains(&creator) {
trace!(target: "engine", "current state: {}", self);
Err(EngineError::NotAuthorized(creator))?
}
// The signer has signed a block too recently
if self.recent_signers.contains(&creator) {
trace!(target: "engine", "current state: {}", self);
Err(EngineError::CliqueTooRecentlySigned(creator))?
}
// Wrong difficulty
let inturn = self.is_inturn(header.number(), &creator);
if inturn && *header.difficulty() != DIFF_INTURN {
Err(BlockError::InvalidDifficulty(Mismatch {
expected: DIFF_INTURN,
found: *header.difficulty(),
}))?
}
if !inturn && *header.difficulty() != DIFF_NOTURN {
Err(BlockError::InvalidDifficulty(Mismatch {
expected: DIFF_NOTURN,
found: *header.difficulty(),
}))?
}
Ok(creator)
}
/// Verify and apply a new header to current state
pub fn apply(&mut self, header: &Header, is_checkpoint: bool) -> Result<Address, Error> {
let creator = self.verify(header)?;
self.recent_signers.push_front(creator);
self.rotate_recent_signers();
if is_checkpoint {
// checkpoint block should not affect previous tallying, so we check that.
let signers = extract_signers(header)?;
if self.signers != signers {
let invalid_signers: Vec<String> = signers.into_iter()
.filter(|s| !self.signers.contains(s))
.map(|s| format!("{}", s))
.collect();
Err(EngineError::CliqueFaultyRecoveredSigners(invalid_signers))?
};
// TODO(niklasad1): I'm not sure if we should shrink here because it is likely that next epoch
// will need some memory and might be better for allocation algorithm to decide whether to shrink or not
// (typically doubles or halves the allocted memory when necessary)
self.votes.clear();
self.votes_history.clear();
self.votes.shrink_to_fit();
self.votes_history.shrink_to_fit();
}
// Contains vote
if *header.author() != NULL_AUTHOR {
let decoded_seal = header.decode_seal::<Vec<_>>()?;
if decoded_seal.len() != 2 {
Err(BlockError::InvalidSealArity(Mismatch { expected: 2, found: decoded_seal.len() }))?
}
let nonce: H64 = decoded_seal[1].into();
self.update_signers_on_vote(VoteType::from_nonce(nonce)?, creator, *header.author(), header.number())?;
}
Ok(creator)
}
fn update_signers_on_vote(
&mut self,
kind: VoteType,
signer: Address,
beneficiary: Address,
block_number: u64
) -> Result<(), Error> {
trace!(target: "engine", "Attempt vote {:?} {:?}", kind, beneficiary);
let pending_vote = PendingVote { signer, beneficiary };
let reverted = if self.is_valid_vote(&beneficiary, kind) {
self.add_vote(pending_vote, kind)
} else {
// This case only happens if a `signer` wants to revert their previous vote
// (does nothing if no previous vote was found)
self.revert_vote(pending_vote)
};
// Add all votes to the history
self.votes_history.push(
Vote {
block_number,
beneficiary,
kind,
signer,
reverted,
});
// If no vote was found for the beneficiary return `early` but don't propogate an error
let (votes, vote_kind) = match self.get_current_votes_and_kind(beneficiary) {
Some((v, k)) => (v, k),
None => return Ok(()),
};
let threshold = self.signers.len() / 2;
debug!(target: "engine", "{}/{} votes to have consensus", votes, threshold + 1);
trace!(target: "engine", "votes: {:?}", votes);
if votes > threshold {
match vote_kind {
VoteType::Add => {
if self.signers.insert(beneficiary) {
debug!(target: "engine", "added new signer: {}", beneficiary);
}
}
VoteType::Remove => {
if self.signers.remove(&beneficiary) {
debug!(target: "engine", "removed signer: {}", beneficiary);
}
}
}
self.rotate_recent_signers();
self.remove_all_votes_from(beneficiary);
}
Ok(())
}
/// Calculate the next timestamp for `inturn` and `noturn` fails if any of them can't be represented as
/// `SystemTime`
// TODO(niklasad1): refactor this method to be in constructor of `CliqueBlockState` instead.
// This is a quite bad API because we must mutate both variables even when already `inturn` fails
// That's why we can't return early and must have the `if-else` in the end
pub fn calc_next_timestamp(&mut self, timestamp: u64, period: u64) -> Result<(), Error> {
let inturn = UNIX_EPOCH.checked_add(Duration::from_secs(timestamp.saturating_add(period)));
self.next_timestamp_inturn = inturn;
let delay = Duration::from_millis(
rand::thread_rng().gen_range(0u64, (self.signers.len() as u64 / 2 + 1) * SIGNING_DELAY_NOTURN_MS));
self.next_timestamp_noturn = inturn.map(|inturn| {
inturn + delay
});
if self.next_timestamp_inturn.is_some() && self.next_timestamp_noturn.is_some() {
Ok(())
} else {
Err(BlockError::TimestampOverflow)?
}
}
/// Returns true if the block difficulty should be `inturn`
pub fn is_inturn(&self, current_block_number: u64, author: &Address) -> bool {
if let Some(pos) = self.signers.iter().position(|x| *author == *x) {
return current_block_number % self.signers.len() as u64 == pos as u64;
}
false
}
/// Returns whether the signer is authorized to sign a block
pub fn is_authorized(&self, author: &Address) -> bool {
self.signers.contains(author) && !self.recent_signers.contains(author)
}
/// Returns whether it makes sense to cast the specified vote in the
/// current state (e.g. don't try to add an already authorized signer).
pub fn is_valid_vote(&self, address: &Address, vote_type: VoteType) -> bool {
let in_signer = self.signers.contains(address);
match vote_type {
VoteType::Add => !in_signer,
VoteType::Remove => in_signer,
}
}
/// Returns the list of current signers
pub fn signers(&self) -> &BTreeSet<Address> {
&self.signers
}
// Note this method will always return `true` but it is intended for a uniform `API`
fn add_vote(&mut self, pending_vote: PendingVote, kind: VoteType) -> bool {
self.votes.entry(pending_vote)
.and_modify(|state| {
state.votes = state.votes.saturating_add(1);
})
.or_insert_with(|| VoteState { kind, votes: 1 });
true
}
fn revert_vote(&mut self, pending_vote: PendingVote) -> bool {
let mut revert = false;
let mut remove = false;
self.votes.entry(pending_vote).and_modify(|state| {
if state.votes.saturating_sub(1) == 0 {
remove = true;
}
revert = true;
});
if remove {
self.votes.remove(&pending_vote);
}
revert
}
fn get_current_votes_and_kind(&self, beneficiary: Address) -> Option<(usize, VoteType)> {
let kind = self.votes.iter()
.find(|(v, _t)| v.beneficiary == beneficiary)
.map(|(_v, t)| t.kind)?;
let votes = self.votes.keys()
.filter(|vote| vote.beneficiary == beneficiary)
.count();
Some((votes, kind))
}
fn rotate_recent_signers(&mut self) {
if self.recent_signers.len() >= ( self.signers.len() / 2 ) + 1 {
self.recent_signers.pop_back();
}
}
fn remove_all_votes_from(&mut self, beneficiary: Address) {
self.votes = std::mem::replace(&mut self.votes, HashMap::new())
.into_iter()
.filter(|(v, _t)| v.signer != beneficiary && v.beneficiary != beneficiary)
.collect();
}
}

View File

@@ -0,0 +1,768 @@
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
//! Implementation of the Clique PoA Engine.
//!
//! File structure:
//! - mod.rs -> Provides the engine API implementation, with additional block state tracking
//! - block_state.rs -> Records the Clique state for given block.
//! - params.rs -> Contains the parameters for the Clique engine.
//! - step_service.rs -> An event loop to trigger sealing.
//! - util.rs -> Various standalone utility functions.
//! - tests.rs -> Consensus tests as defined in EIP-225.
/// How syncing works:
///
/// 1. Client will call:
/// - `Clique::verify_block_basic()`
/// - `Clique::verify_block_unordered()`
/// - `Clique::verify_block_family()`
/// 2. Using `Clique::state()` we try and retrieve the parent state. If this isn't found
/// we need to back-fill it from the last known checkpoint.
/// 3. Once we have a good state, we can record it using `CliqueBlockState::apply()`.
/// How sealing works:
///
/// 1. Set a signer using `Engine::set_signer()`. If a miner account was set up through
/// a config file or CLI flag `MinerService::set_author()` will eventually set the signer
/// 2. We check that the engine seals internally through `Clique::seals_internally()`
/// Note: This is always true for Clique
/// 3. Calling `Clique::new()` will spawn a `StepService` thread. This thread will call `Engine::step()`
/// periodically. Internally, the Clique `step()` function calls `Client::update_sealing()`, which is
/// what makes and seals a block.
/// 4. `Clique::generate_seal()` will then be called by `miner`. This will return a `Seal` which
/// is either a `Seal::None` or `Seal:Regular`. The following shows how a `Seal` variant is chosen:
/// a. We return `Seal::None` if no signer is available or the signer is not authorized.
/// b. If period == 0 and block has transactions, we return `Seal::Regular`, otherwise return `Seal::None`.
/// c. If we're `INTURN`, wait for at least `period` since last block before trying to seal.
/// d. If we're not `INTURN`, we wait for a random amount of time using the algorithm specified
/// in EIP-225 before trying to seal again.
/// 5. Miner will create new block, in process it will call several engine methods to do following:
/// a. `Clique::open_block_header_timestamp()` must set timestamp correctly.
/// b. `Clique::populate_from_parent()` must set difficulty to correct value.
/// Note: `Clique::populate_from_parent()` is used in both the syncing and sealing code paths.
/// 6. We call `Clique::on_seal_block()` which will allow us to modify the block header during seal generation.
/// 7. Finally, `Clique::verify_local_seal()` is called. After this, the syncing code path will be followed
/// in order to import the new block.
use std::cmp;
use std::collections::HashMap;
use std::collections::VecDeque;
use std::sync::{Arc, Weak};
use std::thread;
use std::time;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use block::ExecutedBlock;
use client::{BlockId, EngineClient};
use engines::clique::util::{extract_signers, recover_creator};
use engines::{Engine, EngineError, Seal};
use error::{BlockError, Error};
use ethereum_types::{Address, H64, H160, H256, U256};
use ethkey::Signature;
use hash::KECCAK_EMPTY_LIST_RLP;
use itertools::Itertools;
use lru_cache::LruCache;
use machine::{Call, EthereumMachine};
use parking_lot::RwLock;
use rand::Rng;
use super::signer::EngineSigner;
use unexpected::{Mismatch, OutOfBounds};
use types::BlockNumber;
use types::header::{ExtendedHeader, Header};
#[cfg(not(feature = "time_checked_add"))]
use time_utils::CheckedSystemTime;
use self::block_state::CliqueBlockState;
use self::params::CliqueParams;
use self::step_service::StepService;
mod params;
mod block_state;
mod step_service;
mod util;
// TODO(niklasad1): extract tester types into a separate mod to be shared in the code base
#[cfg(test)]
mod tests;
// Protocol constants
/// Fixed number of extra-data prefix bytes reserved for signer vanity
pub const VANITY_LENGTH: usize = 32;
/// Fixed number of extra-data suffix bytes reserved for signer signature
pub const SIGNATURE_LENGTH: usize = 65;
/// Address length of signer
pub const ADDRESS_LENGTH: usize = 20;
/// Nonce value for DROP vote
pub const NONCE_DROP_VOTE: H64 = H64([0; 8]);
/// Nonce value for AUTH vote
pub const NONCE_AUTH_VOTE: H64 = H64([0xff; 8]);
/// Difficulty for INTURN block
pub const DIFF_INTURN: U256 = U256([2, 0, 0, 0]);
/// Difficulty for NOTURN block
pub const DIFF_NOTURN: U256 = U256([1, 0, 0, 0]);
/// Default empty author field value
pub const NULL_AUTHOR: Address = H160([0x00; 20]);
/// Default empty nonce value
pub const NULL_NONCE: H64 = NONCE_DROP_VOTE;
/// Default value for mixhash
pub const NULL_MIXHASH: H256 = H256([0; 32]);
/// Default value for uncles hash
pub const NULL_UNCLES_HASH: H256 = KECCAK_EMPTY_LIST_RLP;
/// Default noturn block wiggle factor defined in spec.
pub const SIGNING_DELAY_NOTURN_MS: u64 = 500;
/// How many CliqueBlockState to cache in the memory.
pub const STATE_CACHE_NUM: usize = 128;
/// Vote to add or remove the beneficiary
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd)]
pub enum VoteType {
Add,
Remove,
}
impl VoteType {
/// Try to construct a `Vote` from a nonce
pub fn from_nonce(nonce: H64) -> Result<Self, Error> {
if nonce == NONCE_AUTH_VOTE {
Ok(VoteType::Add)
} else if nonce == NONCE_DROP_VOTE {
Ok(VoteType::Remove)
} else {
Err(EngineError::CliqueInvalidNonce(nonce))?
}
}
/// Get the rlp encoding of the vote
pub fn as_rlp(&self) -> Vec<Vec<u8>> {
match self {
VoteType::Add => vec![rlp::encode(&NULL_MIXHASH), rlp::encode(&NONCE_AUTH_VOTE)],
VoteType::Remove => vec![rlp::encode(&NULL_MIXHASH), rlp::encode(&NONCE_DROP_VOTE)],
}
}
}
/// Clique Engine implementation
// block_state_by_hash -> block state indexed by header hash.
#[cfg(not(test))]
pub struct Clique {
epoch_length: u64,
period: u64,
machine: EthereumMachine,
client: RwLock<Option<Weak<EngineClient>>>,
block_state_by_hash: RwLock<LruCache<H256, CliqueBlockState>>,
proposals: RwLock<HashMap<Address, VoteType>>,
signer: RwLock<Option<Box<EngineSigner>>>,
step_service: Option<Arc<StepService>>,
}
#[cfg(test)]
/// Test version of `CliqueEngine` to make all fields public
pub struct Clique {
pub epoch_length: u64,
pub period: u64,
pub machine: EthereumMachine,
pub client: RwLock<Option<Weak<EngineClient>>>,
pub block_state_by_hash: RwLock<LruCache<H256, CliqueBlockState>>,
pub proposals: RwLock<HashMap<Address, VoteType>>,
pub signer: RwLock<Option<Box<EngineSigner>>>,
pub step_service: Option<Arc<StepService>>,
}
impl Clique {
/// Initialize Clique engine from empty state.
pub fn new(our_params: CliqueParams, machine: EthereumMachine) -> Result<Arc<Self>, Error> {
let mut engine = Clique {
epoch_length: our_params.epoch,
period: our_params.period,
client: Default::default(),
block_state_by_hash: RwLock::new(LruCache::new(STATE_CACHE_NUM)),
proposals: Default::default(),
signer: Default::default(),
machine,
step_service: None,
};
let res = Arc::new(engine);
if our_params.period > 0 {
engine.step_service = Some(StepService::start(Arc::downgrade(&res) as Weak<Engine<_>>));
}
Ok(res)
}
#[cfg(test)]
/// Initialize test variant of `CliqueEngine`,
/// Note we need to `mock` the miner and it is introduced to test block verification to trigger new blocks
/// to mainly test consensus edge cases
pub fn with_test(epoch_length: u64, period: u64) -> Self {
use spec::Spec;
Self {
epoch_length,
period,
client: Default::default(),
block_state_by_hash: RwLock::new(LruCache::new(STATE_CACHE_NUM)),
proposals: Default::default(),
signer: Default::default(),
machine: Spec::new_test_machine(),
step_service: None,
}
}
fn sign_header(&self, header: &Header) -> Result<(Signature, H256), Error> {
match self.signer.read().as_ref() {
None => {
Err(EngineError::RequiresSigner)?
}
Some(signer) => {
let digest = header.hash();
match signer.sign(digest) {
Ok(sig) => Ok((sig, digest)),
Err(e) => Err(EngineError::Custom(e.into()))?,
}
}
}
}
/// Construct an new state from given checkpoint header.
fn new_checkpoint_state(&self, header: &Header) -> Result<CliqueBlockState, Error> {
debug_assert_eq!(header.number() % self.epoch_length, 0);
let mut state = CliqueBlockState::new(
extract_signers(header)?);
// TODO(niklasad1): refactor to perform this check in the `CliqueBlockState` constructor instead
state.calc_next_timestamp(header.timestamp(), self.period)?;
Ok(state)
}
fn state_no_backfill(&self, hash: &H256) -> Option<CliqueBlockState> {
self.block_state_by_hash.write().get_mut(hash).cloned()
}
/// Get `CliqueBlockState` for given header, backfill from last checkpoint if needed.
fn state(&self, header: &Header) -> Result<CliqueBlockState, Error> {
let mut block_state_by_hash = self.block_state_by_hash.write();
if let Some(state) = block_state_by_hash.get_mut(&header.hash()) {
return Ok(state.clone());
}
// If we are looking for an checkpoint block state, we can directly reconstruct it.
if header.number() % self.epoch_length == 0 {
let state = self.new_checkpoint_state(header)?;
block_state_by_hash.insert(header.hash(), state.clone());
return Ok(state);
}
// BlockState is not found in memory, which means we need to reconstruct state from last checkpoint.
match self.client.read().as_ref().and_then(|w| w.upgrade()) {
None => {
return Err(EngineError::RequiresClient)?;
}
Some(c) => {
let last_checkpoint_number = header.number() - header.number() % self.epoch_length as u64;
debug_assert_ne!(last_checkpoint_number, header.number());
let mut chain: &mut VecDeque<Header> = &mut VecDeque::with_capacity(
(header.number() - last_checkpoint_number + 1) as usize);
// Put ourselves in.
chain.push_front(header.clone());
// populate chain to last checkpoint
loop {
let (last_parent_hash, last_num) = {
let l = chain.front().expect("chain has at least one element; qed");
(*l.parent_hash(), l.number())
};
if last_num == last_checkpoint_number + 1 {
break;
}
match c.block_header(BlockId::Hash(last_parent_hash)) {
None => {
return Err(BlockError::UnknownParent(last_parent_hash))?;
}
Some(next) => {
chain.push_front(next.decode()?);
}
}
}
// Catching up state, note that we don't really store block state for intermediary blocks,
// for speed.
let backfill_start = time::Instant::now();
trace!(target: "engine",
"Back-filling block state. last_checkpoint_number: {}, target: {}({}).",
last_checkpoint_number, header.number(), header.hash());
// Get the state for last checkpoint.
let last_checkpoint_hash = *chain.front()
.expect("chain has at least one element; qed")
.parent_hash();
let last_checkpoint_header = match c.block_header(BlockId::Hash(last_checkpoint_hash)) {
None => return Err(EngineError::CliqueMissingCheckpoint(last_checkpoint_hash))?,
Some(header) => header.decode()?,
};
let last_checkpoint_state = match block_state_by_hash.get_mut(&last_checkpoint_hash) {
Some(state) => state.clone(),
None => self.new_checkpoint_state(&last_checkpoint_header)?,
};
block_state_by_hash.insert(last_checkpoint_header.hash(), last_checkpoint_state.clone());
// Backfill!
let mut new_state = last_checkpoint_state.clone();
for item in chain {
new_state.apply(item, false)?;
}
new_state.calc_next_timestamp(header.timestamp(), self.period)?;
block_state_by_hash.insert(header.hash(), new_state.clone());
let elapsed = backfill_start.elapsed();
trace!(target: "engine", "Back-filling succeed, took {} ms.", elapsed.as_millis());
Ok(new_state)
}
}
}
}
impl Engine<EthereumMachine> for Clique {
fn name(&self) -> &str { "Clique" }
fn machine(&self) -> &EthereumMachine { &self.machine }
// Clique use same fields, nonce + mixHash
fn seal_fields(&self, _header: &Header) -> usize { 2 }
fn maximum_uncle_count(&self, _block: BlockNumber) -> usize { 0 }
fn on_new_block(
&self,
_block: &mut ExecutedBlock,
_epoch_begin: bool,
_ancestry: &mut Iterator<Item=ExtendedHeader>,
) -> Result<(), Error> {
Ok(())
}
// Clique has no block reward.
fn on_close_block(&self, _block: &mut ExecutedBlock) -> Result<(), Error> {
Ok(())
}
fn on_seal_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> {
trace!(target: "engine", "on_seal_block");
let header = &mut block.header;
let state = self.state_no_backfill(header.parent_hash())
.ok_or_else(|| BlockError::UnknownParent(*header.parent_hash()))?;
let is_checkpoint = header.number() % self.epoch_length == 0;
header.set_author(NULL_AUTHOR);
// Cast a random Vote if not checkpoint
if !is_checkpoint {
// TODO(niklasad1): this will always be false because `proposals` is never written to
let votes = self.proposals.read().iter()
.filter(|(address, vote_type)| state.is_valid_vote(*address, **vote_type))
.map(|(address, vote_type)| (*address, *vote_type))
.collect_vec();
if !votes.is_empty() {
// Pick a random vote.
let random_vote = rand::thread_rng().gen_range(0 as usize, votes.len());
let (beneficiary, vote_type) = votes[random_vote];
trace!(target: "engine", "Casting vote: beneficiary {}, type {:?} ", beneficiary, vote_type);
header.set_author(beneficiary);
header.set_seal(vote_type.as_rlp());
}
}
// Work on clique seal.
let mut seal: Vec<u8> = Vec::with_capacity(VANITY_LENGTH + SIGNATURE_LENGTH);
// At this point, extra_data should only contain miner vanity.
if header.extra_data().len() != VANITY_LENGTH {
Err(BlockError::ExtraDataOutOfBounds(OutOfBounds {
min: Some(VANITY_LENGTH),
max: Some(VANITY_LENGTH),
found: header.extra_data().len()
}))?;
}
// vanity
{
seal.extend_from_slice(&header.extra_data()[0..VANITY_LENGTH]);
}
// If we are building an checkpoint block, add all signers now.
if is_checkpoint {
seal.reserve(state.signers().len() * 20);
state.signers().iter().foreach(|addr| {
seal.extend_from_slice(&addr[..]);
});
}
header.set_extra_data(seal.clone());
// append signature onto extra_data
let (sig, _msg) = self.sign_header(&header)?;
seal.extend_from_slice(&sig[..]);
header.set_extra_data(seal.clone());
header.compute_hash();
// locally sealed block don't go through valid_block_family(), so we have to record state here.
let mut new_state = state.clone();
new_state.apply(&header, is_checkpoint)?;
new_state.calc_next_timestamp(header.timestamp(), self.period)?;
self.block_state_by_hash.write().insert(header.hash(), new_state);
trace!(target: "engine", "on_seal_block: finished, final header: {:?}", header);
Ok(())
}
/// Clique doesn't require external work to seal, so we always return true here.
fn seals_internally(&self) -> Option<bool> {
Some(true)
}
/// Returns if we are ready to seal, the real sealing (signing extra_data) is actually done in `on_seal_block()`.
fn generate_seal(&self, block: &ExecutedBlock, parent: &Header) -> Seal {
trace!(target: "engine", "tried to generate_seal");
let null_seal = util::null_seal();
if block.header.number() == 0 {
trace!(target: "engine", "attempted to seal genesis block");
return Seal::None;
}
// if sealing period is 0, and not an checkpoint block, refuse to seal
if self.period == 0 {
if block.transactions.is_empty() && block.header.number() % self.epoch_length != 0 {
return Seal::None;
}
return Seal::Regular(null_seal);
}
// Check we actually have authority to seal.
if let Some(author) = self.signer.read().as_ref().map(|x| x.address()) {
// ensure the voting state exists
match self.state(&parent) {
Err(e) => {
warn!(target: "engine", "generate_seal: can't get parent state(number: {}, hash: {}): {} ",
parent.number(), parent.hash(), e);
return Seal::None;
}
Ok(state) => {
// Are we authorized to seal?
if !state.is_authorized(&author) {
trace!(target: "engine", "generate_seal: Not authorized to sign right now.");
// wait for one third of period to try again.
thread::sleep(Duration::from_secs(self.period / 3 + 1));
return Seal::None;
}
let inturn = state.is_inturn(block.header.number(), &author);
let now = SystemTime::now();
let limit = match inturn {
true => state.next_timestamp_inturn.unwrap_or(now),
false => state.next_timestamp_noturn.unwrap_or(now),
};
// Wait for the right moment.
if now < limit {
trace!(target: "engine",
"generate_seal: sleeping to sign: inturn: {}, now: {:?}, to: {:?}.",
inturn, now, limit);
match limit.duration_since(SystemTime::now()) {
Ok(duration) => {
thread::sleep(duration);
},
Err(e) => {
warn!(target:"engine", "generate_seal: unable to sleep, err: {}", e);
return Seal::None;
}
}
}
trace!(target: "engine", "generate_seal: seal ready for block {}, txs: {}.",
block.header.number(), block.transactions.len());
return Seal::Regular(null_seal);
}
}
}
Seal::None
}
fn verify_local_seal(&self, _header: &Header) -> Result<(), Error> { Ok(()) }
fn verify_block_basic(&self, header: &Header) -> Result<(), Error> {
// Largely same as https://github.com/ethereum/go-ethereum/blob/master/consensus/clique/clique.go#L275
// Ignore genesis block.
if header.number() == 0 {
return Ok(());
}
// Don't waste time checking blocks from the future
{
let limit = SystemTime::now().checked_add(Duration::from_secs(self.period))
.ok_or(BlockError::TimestampOverflow)?;
// This should succeed under the contraints that the system clock works
let limit_as_dur = limit.duration_since(UNIX_EPOCH).map_err(|e| {
Box::new(format!("Converting SystemTime to Duration failed: {}", e))
})?;
let hdr = Duration::from_secs(header.timestamp());
if hdr > limit_as_dur {
let found = UNIX_EPOCH.checked_add(hdr).ok_or(BlockError::TimestampOverflow)?;
Err(BlockError::TemporarilyInvalid(OutOfBounds {
min: None,
max: Some(limit),
found,
}))?
}
}
let is_checkpoint = header.number() % self.epoch_length == 0;
if is_checkpoint && *header.author() != NULL_AUTHOR {
return Err(EngineError::CliqueWrongAuthorCheckpoint(Mismatch {
expected: 0.into(),
found: *header.author(),
}))?;
}
let seal_fields = header.decode_seal::<Vec<_>>()?;
if seal_fields.len() != 2 {
Err(BlockError::InvalidSealArity(Mismatch {
expected: 2,
found: seal_fields.len(),
}))?
}
let mixhash: H256 = seal_fields[0].into();
let nonce: H64 = seal_fields[1].into();
// Nonce must be 0x00..0 or 0xff..f
if nonce != NONCE_DROP_VOTE && nonce != NONCE_AUTH_VOTE {
Err(EngineError::CliqueInvalidNonce(nonce))?;
}
if is_checkpoint && nonce != NULL_NONCE {
Err(EngineError::CliqueInvalidNonce(nonce))?;
}
// Ensure that the mix digest is zero as Clique don't have fork protection currently
if mixhash != NULL_MIXHASH {
Err(BlockError::MismatchedH256SealElement(Mismatch {
expected: NULL_MIXHASH,
found: mixhash,
}))?
}
let extra_data_len = header.extra_data().len();
if extra_data_len < VANITY_LENGTH {
Err(EngineError::CliqueMissingVanity)?
}
if extra_data_len < VANITY_LENGTH + SIGNATURE_LENGTH {
Err(EngineError::CliqueMissingSignature)?
}
let signers = extra_data_len - (VANITY_LENGTH + SIGNATURE_LENGTH);
// Checkpoint blocks must at least contain one signer
if is_checkpoint && signers == 0 {
Err(EngineError::CliqueCheckpointNoSigner)?
}
// Addresses must be be divisable by 20
if is_checkpoint && signers % ADDRESS_LENGTH != 0 {
Err(EngineError::CliqueCheckpointInvalidSigners(signers))?
}
// Ensure that the block doesn't contain any uncles which are meaningless in PoA
if *header.uncles_hash() != NULL_UNCLES_HASH {
Err(BlockError::InvalidUnclesHash(Mismatch {
expected: NULL_UNCLES_HASH,
found: *header.uncles_hash(),
}))?
}
// Ensure that the block's difficulty is meaningful (may not be correct at this point)
if *header.difficulty() != DIFF_INTURN && *header.difficulty() != DIFF_NOTURN {
Err(BlockError::DifficultyOutOfBounds(OutOfBounds {
min: Some(DIFF_NOTURN),
max: Some(DIFF_INTURN),
found: *header.difficulty(),
}))?
}
// All basic checks passed, continue to next phase
Ok(())
}
fn verify_block_unordered(&self, _header: &Header) -> Result<(), Error> {
// Nothing to check here.
Ok(())
}
/// Verify block family by looking up parent state (backfill if needed), then try to apply current header.
/// see https://github.com/ethereum/go-ethereum/blob/master/consensus/clique/clique.go#L338
fn verify_block_family(&self, header: &Header, parent: &Header) -> Result<(), Error> {
// Ignore genesis block.
if header.number() == 0 {
return Ok(());
}
// parent sanity check
if parent.hash() != *header.parent_hash() || header.number() != parent.number() + 1 {
Err(BlockError::UnknownParent(parent.hash()))?
}
// Ensure that the block's timestamp isn't too close to it's parent
let limit = parent.timestamp().saturating_add(self.period);
if limit > header.timestamp() {
let max = UNIX_EPOCH.checked_add(Duration::from_secs(header.timestamp()));
let found = UNIX_EPOCH.checked_add(Duration::from_secs(limit))
.ok_or(BlockError::TimestampOverflow)?;
Err(BlockError::InvalidTimestamp(OutOfBounds {
min: None,
max,
found,
}))?
}
// Retrieve the parent state
let parent_state = self.state(&parent)?;
// Try to apply current state, apply() will further check signer and recent signer.
let mut new_state = parent_state.clone();
new_state.apply(header, header.number() % self.epoch_length == 0)?;
new_state.calc_next_timestamp(header.timestamp(), self.period)?;
self.block_state_by_hash.write().insert(header.hash(), new_state);
Ok(())
}
fn genesis_epoch_data(&self, header: &Header, _call: &Call) -> Result<Vec<u8>, String> {
let mut state = self.new_checkpoint_state(header).expect("Unable to parse genesis data.");
state.calc_next_timestamp(header.timestamp(), self.period).map_err(|e| format!("{}", e))?;
self.block_state_by_hash.write().insert(header.hash(), state);
// no proof.
Ok(Vec::new())
}
// Our task here is to set difficulty
fn populate_from_parent(&self, header: &mut Header, parent: &Header) {
// TODO(https://github.com/paritytech/parity-ethereum/issues/10410): this is a horrible hack,
// it is due to the fact that enact and miner both use OpenBlock::new() which will both call
// this function. more refactoring is definitely needed.
if header.extra_data().len() < VANITY_LENGTH + SIGNATURE_LENGTH {
trace!(target: "engine", "populate_from_parent in sealing");
// It's unclear how to prevent creating new blocks unless we are authorized, the best way (and geth does this too)
// it's just to ignore setting an correct difficulty here, we will check authorization in next step in generate_seal anyway.
if let Some(signer) = self.signer.read().as_ref() {
let state = match self.state(&parent) {
Err(e) => {
trace!(target: "engine", "populate_from_parent: Unable to find parent state: {}, ignored.", e);
return;
}
Ok(state) => state,
};
if state.is_authorized(&signer.address()) {
if state.is_inturn(header.number(), &signer.address()) {
header.set_difficulty(DIFF_INTURN);
} else {
header.set_difficulty(DIFF_NOTURN);
}
}
} else {
trace!(target: "engine", "populate_from_parent: no signer registered");
}
}
}
fn set_signer(&self, signer: Box<EngineSigner>) {
trace!(target: "engine", "set_signer: {}", signer.address());
*self.signer.write() = Some(signer);
}
fn register_client(&self, client: Weak<EngineClient>) {
*self.client.write() = Some(client.clone());
}
fn step(&self) {
if self.signer.read().is_some() {
if let Some(ref weak) = *self.client.read() {
if let Some(c) = weak.upgrade() {
c.update_sealing();
}
}
}
}
fn stop(&mut self) {
if let Some(mut s) = self.step_service.as_mut() {
Arc::get_mut(&mut s).map(|x| x.stop());
} else {
warn!(target: "engine", "Stopping `CliqueStepService` failed requires mutable access");
}
}
/// Clique timestamp is set to parent + period , or current time which ever is higher.
fn open_block_header_timestamp(&self, parent_timestamp: u64) -> u64 {
let now = time::SystemTime::now().duration_since(time::UNIX_EPOCH).unwrap_or_default();
cmp::max(now.as_secs() as u64, parent_timestamp.saturating_add(self.period))
}
fn is_timestamp_valid(&self, header_timestamp: u64, parent_timestamp: u64) -> bool {
header_timestamp >= parent_timestamp.saturating_add(self.period)
}
fn fork_choice(&self, new: &ExtendedHeader, current: &ExtendedHeader) -> super::ForkChoice {
super::total_difficulty_fork_choice(new, current)
}
// Clique uses the author field for voting, the real author is hidden in the `extra_data` field.
// So when executing tx's (like in `enact()`) we want to use the executive author
fn executive_author(&self, header: &Header) -> Result<Address, Error> {
recover_creator(header)
}
}

View File

@@ -0,0 +1,41 @@
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
//! Clique specific parameters.
use ethjson;
/// `Clique` params.
pub struct CliqueParams {
/// Period as defined in EIP
pub period: u64,
/// Epoch length as defined in EIP
pub epoch: u64,
}
impl From<ethjson::spec::CliqueParams> for CliqueParams {
fn from(p: ethjson::spec::CliqueParams) -> Self {
let period = p.period.map_or_else(|| 30000 as u64, Into::into);
let epoch = p.epoch.map_or_else(|| 15 as u64, Into::into);
assert!(epoch > 0);
CliqueParams {
period,
epoch,
}
}
}

View File

@@ -0,0 +1,77 @@
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
use std::sync::Weak;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
use std::thread;
use std::sync::Arc;
use engines::Engine;
use machine::Machine;
/// Service that is managing the engine
pub struct StepService {
shutdown: Arc<AtomicBool>,
thread: Option<thread::JoinHandle<()>>,
}
impl StepService {
/// Start the `StepService`
pub fn start<M: Machine + 'static>(engine: Weak<Engine<M>>) -> Arc<Self> {
let shutdown = Arc::new(AtomicBool::new(false));
let s = shutdown.clone();
let thread = thread::Builder::new()
.name("CliqueStepService".into())
.spawn(move || {
// startup delay.
thread::sleep(Duration::from_secs(5));
loop {
// see if we are in shutdown.
if shutdown.load(Ordering::Acquire) {
trace!(target: "miner", "CliqueStepService: received shutdown signal!");
break;
}
trace!(target: "miner", "CliqueStepService: triggering sealing");
// Try sealing
engine.upgrade().map(|x| x.step());
// Yield
thread::sleep(Duration::from_millis(2000));
}
trace!(target: "miner", "CliqueStepService: shutdown.");
}).expect("CliqueStepService thread failed");
Arc::new(StepService {
shutdown: s,
thread: Some(thread),
})
}
/// Stop the `StepService`
pub fn stop(&mut self) {
trace!(target: "miner", "CliqueStepService: shutting down.");
self.shutdown.store(true, Ordering::Release);
if let Some(t) = self.thread.take() {
t.join().expect("CliqueStepService thread panicked!");
}
}
}

View File

@@ -0,0 +1,804 @@
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
//! Consensus tests for `PoA Clique Engine`, see http://eips.ethereum.org/EIPS/eip-225 for more information
use block::*;
use engines::Engine;
use error::{Error, ErrorKind};
use ethereum_types::{Address, H256};
use ethkey::{Secret, KeyPair};
use state_db::StateDB;
use super::*;
use test_helpers::get_temp_state_db;
use std::sync::Arc;
use std::collections::HashMap;
/// Possible signers
pub const SIGNER_TAGS: [char; 6] = ['A', 'B', 'C', 'D', 'E', 'F'];
/// Clique block types
pub enum CliqueBlockType {
/// Epoch transition block must contain list of signers
Checkpoint,
/// Block with no votes
Empty,
/// Vote
Vote(VoteType),
}
/// Clique tester
pub struct CliqueTester {
/// Mocked Clique
pub clique: Clique,
/// Mocked genesis state
pub genesis: Header,
/// StateDB
pub db: StateDB,
/// List of signers
pub signers: HashMap<char, KeyPair>,
}
impl CliqueTester {
/// Create a `Clique` tester with settings
pub fn with(epoch: u64, period: u64, initial_signers: Vec<char>) -> Self {
assert_eq!(initial_signers.iter().all(|s| SIGNER_TAGS.contains(s)), true,
"Not all the initial signers is in SIGNER_TAGS, possible keys are 'A' ..= 'F'");
let clique = Clique::with_test(epoch, period);
let mut genesis = Header::default();
let mut signers = HashMap::new();
let call = |_a, _b| {
unimplemented!("Clique doesn't use Engine::Call");
};
let mut extra_data = vec![0; VANITY_LENGTH];
for &signer in SIGNER_TAGS.iter() {
let secret = Secret::from(H256::from(signer as u64));
let keypair = KeyPair::from_secret(secret).unwrap();
if initial_signers.contains(&signer) {
extra_data.extend(&*keypair.address());
}
signers.insert(signer, keypair);
}
// append dummy signature
extra_data.extend(std::iter::repeat(0).take(SIGNATURE_LENGTH));
genesis.set_extra_data(extra_data);
genesis.set_gas_limit(U256::from(0xa00000));
genesis.set_difficulty(U256::from(1));
genesis.set_seal(util::null_seal());
clique.genesis_epoch_data(&genesis, &call).expect("Create genesis failed");
Self {clique, genesis, db: get_temp_state_db(), signers}
}
/// Get difficulty for a given block
pub fn get_difficulty(&self, block_num: BlockNumber, header: &Header, signer: &Address) -> U256 {
let state = self.clique.state(header).unwrap();
if state.is_inturn(block_num, signer) {
DIFF_INTURN
} else {
DIFF_NOTURN
}
}
/// Get the state of a given block
// Note, this will read the cache and `will` not work with more than 128 blocks
pub fn get_state_at_block(&self, hash: &H256) -> CliqueBlockState {
self.clique.block_state_by_hash.write()
.get_mut(hash)
.expect("CliqueBlockState not found tested failed")
.clone()
}
/// Get signers after a certain state
// This is generally used to fetch the state after a test has been executed and checked against
// the intial list of signers provided in the test
pub fn clique_signers(&self, hash: &H256) -> impl Iterator<Item = Address> {
self.get_state_at_block(hash).signers().clone().into_iter()
}
/// Fetches all addresses at current `block` and converts them back to `tags (char)` and sorts them
/// Addresses are supposed sorted based on address but these tests are using `tags` just for simplicity
/// and the order is not important!
pub fn into_tags<T: Iterator<Item = Address>>(&self, addr: T) -> Vec<char> {
let mut tags: Vec<char> = addr.filter_map(|addr| {
for (t, kp) in self.signers.iter() {
if addr == kp.address() {
return Some(*t)
}
}
None
})
.collect();
tags.sort();
tags
}
/// Create a new `Clique` block and import
pub fn new_block_and_import(
&self,
block_type: CliqueBlockType,
last_header: &Header,
beneficary: Option<Address>,
signer: char,
) -> Result<Header, Error> {
let mut extra_data = vec![0; VANITY_LENGTH];
let mut seal = util::null_seal();
let last_hash = last_header.hash();
match block_type {
CliqueBlockType::Checkpoint => {
let signers = self.clique.state(&last_header).unwrap().signers().clone();
for signer in signers {
extra_data.extend(&*signer);
}
}
CliqueBlockType::Vote(v) => seal = v.as_rlp(),
CliqueBlockType::Empty => (),
};
let db = self.db.boxed_clone();
let mut block = OpenBlock::new(
&self.clique,
Default::default(),
false,
db,
&last_header.clone(),
Arc::new(vec![last_hash]),
beneficary.unwrap_or_default(),
(3141562.into(), 31415620.into()),
extra_data,
false,
None,
).unwrap();
{
let difficulty = self.get_difficulty(block.header.number(), last_header, &self.signers[&signer].address());
let b = block.block_mut();
b.header.set_timestamp(last_header.timestamp() + self.clique.period);
b.header.set_difficulty(difficulty);
b.header.set_seal(seal);
let sign = ethkey::sign(self.signers[&signer].secret(), &b.header.hash()).unwrap();
let mut extra_data = b.header.extra_data().clone();
extra_data.extend_from_slice(&*sign);
b.header.set_extra_data(extra_data);
}
let current_header = &block.header;
self.clique.verify_block_basic(current_header)?;
self.clique.verify_block_family(current_header, &last_header)?;
Ok(current_header.clone())
}
}
#[test]
fn one_signer_with_no_votes() {
let tester = CliqueTester::with(10, 1, vec!['A']);
let empty_block = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'A').unwrap();
let tags = tester.into_tags(tester.clique_signers(&empty_block.hash()));
assert_eq!(&tags, &['A']);
}
#[test]
fn one_signer_two_votes() {
let tester = CliqueTester::with(10, 1, vec!['A']);
// Add a vote for `B` signed by `A`
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
Some(tester.signers[&'B'].address()), 'A').unwrap();
let tags = tester.into_tags(tester.clique_signers(&vote.hash()));
assert_eq!(&tags, &['A', 'B']);
// Add a empty block signed by `B`
let empty = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap();
// Add vote for `C` signed by A but should not be accepted
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &empty,
Some(tester.signers[&'C'].address()), 'A').unwrap();
let tags = tester.into_tags(tester.clique_signers(&vote.hash()));
assert_eq!(&tags, &['A', 'B']);
}
#[test]
fn two_signers_six_votes_deny_last() {
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
let mut prev_header = tester.genesis.clone();
// Add two votes for `C` signed by `A` and `B`
for &signer in SIGNER_TAGS.iter().take(2) {
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
Some(tester.signers[&'C'].address()), signer).unwrap();
prev_header = vote.clone();
}
// Add two votes for `D` signed by `A` and `B`
for &signer in SIGNER_TAGS.iter().take(2) {
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
Some(tester.signers[&'D'].address()), signer).unwrap();
prev_header = vote.clone();
}
// Add a empty block signed by `C`
let empty = tester.new_block_and_import(CliqueBlockType::Empty, &prev_header, None, 'C').unwrap();
prev_header = empty.clone();
// Add two votes for `E` signed by `A` and `B`
for &signer in SIGNER_TAGS.iter().take(2) {
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
Some(tester.signers[&'E'].address()), signer).unwrap();
prev_header = vote.clone();
}
let tags = tester.into_tags(tester.clique_signers(&prev_header.hash()));
assert_eq!(&tags, &['A', 'B', 'C', 'D']);
}
#[test]
fn one_signer_dropping_itself() {
let tester = CliqueTester::with(10, 1, vec!['A']);
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
Some(tester.signers[&'A'].address()), 'A').unwrap();
let signers = tester.clique_signers(&vote.hash());
assert!(signers.count() == 0);
}
#[test]
fn two_signers_one_remove_vote_no_consensus() {
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
Some(tester.signers[&'B'].address()), 'A').unwrap();
let tags = tester.into_tags(tester.clique_signers(&vote.hash()));
assert_eq!(&tags, &['A', 'B']);
}
#[test]
fn two_signers_consensus_remove_b() {
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
let first_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
Some(tester.signers[&'B'].address()), 'A').unwrap();
let second_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &first_vote,
Some(tester.signers[&'B'].address()), 'B').unwrap();
let tags = tester.into_tags(tester.clique_signers(&second_vote.hash()));
assert_eq!(&tags, &['A']);
}
#[test]
fn three_signers_consensus_remove_c() {
let tester = CliqueTester::with(10, 1, vec!['A', 'B', 'C']);
let first_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
Some(tester.signers[&'C'].address()), 'A').unwrap();
let second_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &first_vote,
Some(tester.signers[&'C'].address()), 'B').unwrap();
let tags = tester.into_tags(tester.clique_signers(&second_vote.hash()));
assert_eq!(&tags, &['A', 'B']);
}
#[test]
fn four_signers_half_no_consensus() {
let tester = CliqueTester::with(10, 1, vec!['A', 'B', 'C', 'D']);
let first_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
Some(tester.signers[&'C'].address()), 'A').unwrap();
let second_vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &first_vote,
Some(tester.signers[&'C'].address()), 'B').unwrap();
let tags = tester.into_tags(tester.clique_signers(&second_vote.hash()));
assert_eq!(&tags, &['A', 'B', 'C', 'D']);
}
#[test]
fn four_signers_three_consensus_rm() {
let tester = CliqueTester::with(10, 1, vec!['A', 'B', 'C', 'D']);
let mut prev_header = tester.genesis.clone();
// Three votes to remove `D` signed by ['A', 'B', 'C']
for signer in SIGNER_TAGS.iter().take(3) {
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header,
Some(tester.signers[&'D'].address()), *signer).unwrap();
prev_header = vote.clone();
}
let tags = tester.into_tags(tester.clique_signers(&prev_header.hash()));
assert_eq!(&tags, &['A', 'B', 'C']);
}
#[test]
fn vote_add_only_counted_once_per_signer() {
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
// Add a vote for `C` signed by `A`
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
Some(tester.signers[&'C'].address()), 'A').unwrap();
// Empty block signed by B`
let empty = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap();
// Add a vote for `C` signed by `A`
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &empty,
Some(tester.signers[&'C'].address()), 'A').unwrap();
// Empty block signed by `B`
let empty = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap();
// Add a vote for `C` signed by `A`
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &empty,
Some(tester.signers[&'C'].address()), 'A').unwrap();
let tags = tester.into_tags(tester.clique_signers(&vote.hash()));
assert_eq!(&tags, &['A', 'B']);
}
#[test]
fn vote_add_concurrently_is_permitted() {
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
// Add a vote for `C` signed by `A`
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
Some(tester.signers[&'C'].address()), 'A').unwrap();
// Empty block signed by `B`
let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'B').unwrap();
// Add a vote for `D` signed by `A`
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &b,
Some(tester.signers[&'D'].address()), 'A').unwrap();
// Empty block signed by `B`
let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'B').unwrap();
// Empty block signed by `A`
let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'A').unwrap();
// Add a vote for `D` signed by `B`
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &b,
Some(tester.signers[&'D'].address()), 'B').unwrap();
// Empty block signed by `A`
let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'A').unwrap();
// Add a vote for `C` signed by `B`
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &b,
Some(tester.signers[&'C'].address()), 'B').unwrap();
let tags = tester.into_tags(tester.clique_signers(&b.hash()));
assert_eq!(&tags, &['A', 'B', 'C', 'D']);
}
#[test]
fn vote_rm_only_counted_once_per_signer() {
let tester = CliqueTester::with(10, 1, vec!['A', 'B']);
let mut prev_header = tester.genesis.clone();
for _ in 0..2 {
// Vote to remove `B` signed by `A`
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header,
Some(tester.signers[&'B'].address()), 'A').unwrap();
// Empty block signed by `B`
let b = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'B').unwrap();
prev_header = b.clone();
}
// Add a vote for `B` signed by `A`
let b = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header,
Some(tester.signers[&'B'].address()), 'A').unwrap();
let tags = tester.into_tags(tester.clique_signers(&b.hash()));
assert_eq!(&tags, &['A', 'B']);
}
#[test]
fn vote_rm_concurrently_is_permitted() {
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']);
// Add a vote for `C` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
Some(tester.signers[&'C'].address()), 'A').unwrap();
// Empty block signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
// Empty block signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
// Add a vote for `D` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'A').unwrap();
// Empty block signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
// Empty block signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
// Empty block signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
// Add a vote for `D` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'B').unwrap();
// Add a vote for `D` signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'C').unwrap();
// Empty block signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
// Add a vote for `C` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'C'].address()), 'B').unwrap();
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
assert_eq!(&tags, &['A', 'B']);
}
#[test]
fn vote_to_rm_are_immediate_and_ensure_votes_are_rm() {
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C']);
// Vote to remove `B` signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
Some(tester.signers[&'B'].address()), 'C').unwrap();
// Vote to remove `C` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'C'].address()), 'A').unwrap();
// Vote to remove `C` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'C'].address()), 'B').unwrap();
// Vote to remove `B` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'B'].address()), 'A').unwrap();
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
assert_eq!(&tags, &['A', 'B']);
}
#[test]
fn vote_to_rm_are_immediate_and_votes_should_be_dropped_from_kicked_signer() {
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C']);
// Vote to add `D` signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
Some(tester.signers[&'D'].address()), 'C').unwrap();
// Vote to remove `C` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'C'].address()), 'A').unwrap();
// Vote to remove `C` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'C'].address()), 'B').unwrap();
// Vote to add `D` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block,
Some(tester.signers[&'D'].address()), 'A').unwrap();
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
assert_eq!(&tags, &['A', 'B']);
}
#[test]
fn cascading_not_allowed() {
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']);
// Vote against `C` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
Some(tester.signers[&'C'].address()), 'A').unwrap();
// Empty block signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
// Empty block signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
// Vote against `D` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'A').unwrap();
// Vote against `C` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'C'].address()), 'B').unwrap();
// Empty block signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
// Empty block signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
// Vote against `D` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'B').unwrap();
// Vote against `D` signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'C').unwrap();
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
assert_eq!(&tags, &['A', 'B', 'C']);
}
#[test]
fn consensus_out_of_bounds_consensus_execute_on_touch() {
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']);
// Vote against `C` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
Some(tester.signers[&'C'].address()), 'A').unwrap();
// Empty block signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
// Empty block signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
// Vote against `D` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'A').unwrap();
// Vote against `C` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'C'].address()), 'B').unwrap();
// Empty block signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
// Empty block signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
// Vote against `D` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'B').unwrap();
// Vote against `D` signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'C').unwrap();
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
assert_eq!(&tags, &['A', 'B', 'C'], "D should have been removed after 3/4 remove votes");
// Empty block signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
// Vote for `C` signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block,
Some(tester.signers[&'C'].address()), 'C').unwrap();
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
assert_eq!(&tags, &['A', 'B']);
}
#[test]
fn consensus_out_of_bounds_first_touch() {
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']);
// Vote against `C` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &tester.genesis,
Some(tester.signers[&'C'].address()), 'A').unwrap();
// Empty block signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
// Empty block signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
// Vote against `D` signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'A').unwrap();
// Vote against `C` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'C'].address()), 'B').unwrap();
// Empty block signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'C').unwrap();
// Empty block signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
// Vote against `D` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'B').unwrap();
// Vote against `D` signed by `C`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &block,
Some(tester.signers[&'D'].address()), 'C').unwrap();
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
assert_eq!(&tags, &['A', 'B', 'C']);
// Empty block signed by `A`
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap();
// Vote for `C` signed by `B`
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block,
Some(tester.signers[&'C'].address()), 'B').unwrap();
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
assert_eq!(&tags, &['A', 'B', 'C']);
}
#[test]
fn pending_votes_doesnt_survive_authorization_changes() {
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D', 'E']);
let mut prev_header = tester.genesis.clone();
// Vote for `F` from [`A`, `B`, `C`]
for sign in SIGNER_TAGS.iter().take(3) {
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
Some(tester.signers[&'F'].address()), *sign).unwrap();
prev_header = block.clone();
}
let tags = tester.into_tags(tester.clique_signers(&prev_header.hash()));
assert_eq!(&tags, &['A', 'B', 'C', 'D', 'E', 'F'], "F should have been added");
// Vote against `F` from [`D`, `E`, `B`, `C`]
for sign in SIGNER_TAGS.iter().skip(3).chain(SIGNER_TAGS.iter().skip(1).take(2)) {
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header,
Some(tester.signers[&'F'].address()), *sign).unwrap();
prev_header = block.clone();
}
let tags = tester.into_tags(tester.clique_signers(&prev_header.hash()));
assert_eq!(&tags, &['A', 'B', 'C', 'D', 'E'], "F should have been removed");
// Vote for `F` from [`D`, `E`]
for sign in SIGNER_TAGS.iter().skip(3).take(2) {
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
Some(tester.signers[&'F'].address()), *sign).unwrap();
prev_header = block.clone();
}
// Vote against `A` from [`B`, `C`, `D`]
for sign in SIGNER_TAGS.iter().skip(1).take(3) {
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Remove), &prev_header,
Some(tester.signers[&'A'].address()), *sign).unwrap();
prev_header = block.clone();
}
let tags = tester.into_tags(tester.clique_signers(&prev_header.hash()));
assert_eq!(&tags, &['B', 'C', 'D', 'E'], "A should have been removed");
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &prev_header,
Some(tester.signers[&'F'].address()), 'B').unwrap();
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
assert_eq!(&tags, &['B', 'C', 'D', 'E', 'F'], "F should have been added again");
}
#[test]
fn epoch_transition_reset_all_votes() {
let tester = CliqueTester::with(3, 1, vec!['A', 'B']);
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
Some(tester.signers[&'C'].address()), 'A').unwrap();
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
let block = tester.new_block_and_import(CliqueBlockType::Checkpoint, &block, None, 'A').unwrap();
let block = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &block,
Some(tester.signers[&'C'].address()), 'B').unwrap();
let tags = tester.into_tags(tester.clique_signers(&block.hash()));
assert_eq!(&tags, &['A', 'B'], "Votes should have been reset after checkpoint");
}
#[test]
fn unauthorized_signer_should_not_be_able_to_sign_block() {
let tester = CliqueTester::with(3, 1, vec!['A']);
let err = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'B').unwrap_err();
match err.kind() {
ErrorKind::Engine(EngineError::NotAuthorized(_)) => (),
_ => assert!(true == false, "Wrong error kind"),
}
}
#[test]
fn signer_should_not_be_able_to_sign_two_consequtive_blocks() {
let tester = CliqueTester::with(3, 1, vec!['A', 'B']);
let b = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'A').unwrap();
let err = tester.new_block_and_import(CliqueBlockType::Empty, &b, None, 'A').unwrap_err();
match err.kind() {
ErrorKind::Engine(EngineError::CliqueTooRecentlySigned(_)) => (),
_ => assert!(true == false, "Wrong error kind"),
}
}
#[test]
fn recent_signers_should_not_reset_on_checkpoint() {
let tester = CliqueTester::with(3, 1, vec!['A', 'B', 'C']);
let block = tester.new_block_and_import(CliqueBlockType::Empty, &tester.genesis, None, 'A').unwrap();
let block = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'B').unwrap();
let block = tester.new_block_and_import(CliqueBlockType::Checkpoint, &block, None, 'A').unwrap();
let err = tester.new_block_and_import(CliqueBlockType::Empty, &block, None, 'A').unwrap_err();
match err.kind() {
ErrorKind::Engine(EngineError::CliqueTooRecentlySigned(_)) => (),
_ => assert!(true == false, "Wrong error kind"),
}
}
// Not part of http://eips.ethereum.org/EIPS/eip-225
#[test]
fn bonus_consensus_should_keep_track_of_votes_before_latest_per_signer() {
let tester = CliqueTester::with(100, 1, vec!['A', 'B', 'C', 'D']);
// Add a vote for `E` signed by `A`
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &tester.genesis,
Some(tester.signers[&'E'].address()), 'A').unwrap();
// Empty block signed by `B`
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'B').unwrap();
// Empty block signed by `C`
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'C').unwrap();
// Empty block signed by `D`
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'D').unwrap();
// Add a vote for `F` signed by `A`
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote,
Some(tester.signers[&'F'].address()), 'A').unwrap();
// Empty block signed by `C`
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'C').unwrap();
// Empty block signed by `D`
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'D').unwrap();
// Add a vote for `E` signed by `B`
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote,
Some(tester.signers[&'E'].address()), 'B').unwrap();
// Empty block signed by `A`
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'A').unwrap();
// Empty block signed by `C`
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'C').unwrap();
// Empty block signed by `D`
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'D').unwrap();
// Add a vote for `F` signed by `B`
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote,
Some(tester.signers[&'F'].address()), 'B').unwrap();
// Empty block signed by A`
let vote = tester.new_block_and_import(CliqueBlockType::Empty, &vote, None, 'A').unwrap();
// Add a vote for `E` signed by `C`
let vote = tester.new_block_and_import(CliqueBlockType::Vote(VoteType::Add), &vote,
Some(tester.signers[&'E'].address()), 'C').unwrap();
let tags = tester.into_tags(tester.clique_signers(&vote.hash()));
assert_eq!(&tags, &['A', 'B', 'C', 'D', 'E']);
}

View File

@@ -0,0 +1,115 @@
// Copyright 2015-2019 Parity Technologies (UK) Ltd.
// This file is part of Parity Ethereum.
// Parity Ethereum 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 Ethereum 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 Ethereum. If not, see <http://www.gnu.org/licenses/>.
use std::collections::BTreeSet;
use engines::EngineError;
use engines::clique::{ADDRESS_LENGTH, SIGNATURE_LENGTH, VANITY_LENGTH, NULL_NONCE, NULL_MIXHASH};
use error::Error;
use ethereum_types::{Address, H256};
use ethkey::{public_to_address, recover as ec_recover, Signature};
use lru_cache::LruCache;
use parking_lot::RwLock;
use rlp::encode;
use types::header::Header;
/// How many recovered signature to cache in the memory.
pub const CREATOR_CACHE_NUM: usize = 4096;
lazy_static! {
/// key: header hash
/// value: creator address
static ref CREATOR_BY_HASH: RwLock<LruCache<H256, Address>> = RwLock::new(LruCache::new(CREATOR_CACHE_NUM));
}
/// Recover block creator from signature
pub fn recover_creator(header: &Header) -> Result<Address, Error> {
// Initialization
let mut cache = CREATOR_BY_HASH.write();
if let Some(creator) = cache.get_mut(&header.hash()) {
return Ok(*creator);
}
let data = header.extra_data();
if data.len() < VANITY_LENGTH {
Err(EngineError::CliqueMissingVanity)?
}
if data.len() < VANITY_LENGTH + SIGNATURE_LENGTH {
Err(EngineError::CliqueMissingSignature)?
}
// Split `signed_extra data` and `signature`
let (signed_data_slice, signature_slice) = data.split_at(data.len() - SIGNATURE_LENGTH);
// convert `&[u8]` to `[u8; 65]`
let signature = {
let mut s = [0; SIGNATURE_LENGTH];
s.copy_from_slice(signature_slice);
s
};
// modify header and hash it
let unsigned_header = &mut header.clone();
unsigned_header.set_extra_data(signed_data_slice.to_vec());
let msg = unsigned_header.hash();
let pubkey = ec_recover(&Signature::from(signature), &msg)?;
let creator = public_to_address(&pubkey);
cache.insert(header.hash(), creator.clone());
Ok(creator)
}
/// Extract signer list from extra_data.
///
/// Layout of extra_data:
/// ----
/// VANITY: 32 bytes
/// Signers: N * 32 bytes as hex encoded (20 characters)
/// Signature: 65 bytes
/// --
pub fn extract_signers(header: &Header) -> Result<BTreeSet<Address>, Error> {
let data = header.extra_data();
if data.len() <= VANITY_LENGTH + SIGNATURE_LENGTH {
Err(EngineError::CliqueCheckpointNoSigner)?
}
// extract only the portion of extra_data which includes the signer list
let signers_raw = &data[(VANITY_LENGTH)..data.len() - (SIGNATURE_LENGTH)];
if signers_raw.len() % ADDRESS_LENGTH != 0 {
Err(EngineError::CliqueCheckpointInvalidSigners(signers_raw.len()))?
}
let num_signers = signers_raw.len() / 20;
let signers: BTreeSet<Address> = (0..num_signers)
.map(|i| {
let start = i * ADDRESS_LENGTH;
let end = start + ADDRESS_LENGTH;
signers_raw[start..end].into()
})
.collect();
Ok(signers)
}
/// Retrieve `null_seal`
pub fn null_seal() -> Vec<Vec<u8>> {
vec![encode(&NULL_MIXHASH.to_vec()), encode(&NULL_NONCE.to_vec())]
}

View File

@@ -18,6 +18,7 @@
mod authority_round;
mod basic_authority;
mod clique;
mod instant_seal;
mod null_engine;
mod validator_set;
@@ -30,6 +31,7 @@ pub use self::basic_authority::BasicAuthority;
pub use self::instant_seal::{InstantSeal, InstantSealParams};
pub use self::null_engine::NullEngine;
pub use self::signer::EngineSigner;
pub use self::clique::Clique;
// TODO [ToDr] Remove re-export (#10130)
pub use types::engines::ForkChoice;
@@ -50,7 +52,7 @@ use types::transaction::{self, UnverifiedTransaction, SignedTransaction};
use ethkey::{Signature};
use machine::{self, Machine, AuxiliaryRequest, AuxiliaryData};
use ethereum_types::{H256, U256, Address};
use ethereum_types::{H64, H256, U256, Address};
use unexpected::{Mismatch, OutOfBounds};
use bytes::Bytes;
use types::ancestry_action::AncestryAction;
@@ -85,12 +87,45 @@ pub enum EngineError {
RequiresClient,
/// Invalid engine specification or implementation.
InvalidEngine,
/// Requires signer ref, but none registered.
RequiresSigner,
/// Checkpoint is missing
CliqueMissingCheckpoint(H256),
/// Missing vanity data
CliqueMissingVanity,
/// Missing signature
CliqueMissingSignature,
/// Missing signers
CliqueCheckpointNoSigner,
/// List of signers is invalid
CliqueCheckpointInvalidSigners(usize),
/// Wrong author on a checkpoint
CliqueWrongAuthorCheckpoint(Mismatch<Address>),
/// Wrong checkpoint authors recovered
CliqueFaultyRecoveredSigners(Vec<String>),
/// Invalid nonce (should contain vote)
CliqueInvalidNonce(H64),
/// The signer signed a block to recently
CliqueTooRecentlySigned(Address),
/// Custom
Custom(String),
}
impl fmt::Display for EngineError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
use self::EngineError::*;
let msg = match *self {
CliqueMissingCheckpoint(ref hash) => format!("Missing checkpoint block: {}", hash),
CliqueMissingVanity => format!("Extra data is missing vanity data"),
CliqueMissingSignature => format!("Extra data is missing signature"),
CliqueCheckpointInvalidSigners(len) => format!("Checkpoint block list was of length: {} of checkpoint but
it needs to be bigger than zero and a divisible by 20", len),
CliqueCheckpointNoSigner => format!("Checkpoint block list of signers was empty"),
CliqueInvalidNonce(ref mis) => format!("Unexpected nonce {} expected {} or {}", mis, 0_u64, u64::max_value()),
CliqueWrongAuthorCheckpoint(ref oob) => format!("Unexpected checkpoint author: {}", oob),
CliqueFaultyRecoveredSigners(ref mis) => format!("Faulty recovered signers {:?}", mis),
CliqueTooRecentlySigned(ref address) => format!("The signer: {} has signed a block too recently", address),
Custom(ref s) => s.clone(),
DoubleVote(ref address) => format!("Author {} issued too many blocks.", address),
NotProposer(ref mis) => format!("Author is not a current proposer: {}", mis),
NotAuthorized(ref address) => format!("Signer {} is not authorized.", address),
@@ -100,6 +135,7 @@ impl fmt::Display for EngineError {
FailedSystemCall(ref msg) => format!("Failed to make system call: {}", msg),
MalformedMessage(ref msg) => format!("Received malformed consensus message: {}", msg),
RequiresClient => format!("Call requires client but none registered"),
RequiresSigner => format!("Call requires signer but none registered"),
InvalidEngine => format!("Invalid engine specification or implementation"),
};
@@ -120,7 +156,7 @@ pub enum Seal {
Proposal(Vec<Bytes>),
/// Regular block seal; should be part of the blockchain.
Regular(Vec<Bytes>),
/// Engine does generate seal for this block right now.
/// Engine does not generate seal for this block right now.
None,
}
@@ -263,6 +299,9 @@ pub trait Engine<M: Machine>: Sync + Send {
Ok(())
}
/// Allow mutating the header during seal generation. Currently only used by Clique.
fn on_seal_block(&self, _block: &mut ExecutedBlock) -> Result<(), Error> { Ok(()) }
/// None means that it requires external input (e.g. PoW) to seal a block.
/// Some(true) means the engine is currently prime for seal generation (i.e. node is the current validator).
/// Some(false) means that the node might seal internally but is not qualified now.
@@ -387,7 +426,7 @@ pub trait Engine<M: Machine>: Sync + Send {
fn step(&self) {}
/// Stops any services that the may hold the Engine and makes it safe to drop.
fn stop(&self) {}
fn stop(&mut self) {}
/// Create a factory for building snapshot chunks and restoring from them.
/// Returning `None` indicates that this engine doesn't support snapshot creation.
@@ -421,6 +460,11 @@ pub trait Engine<M: Machine>: Sync + Send {
/// Check whether the given new block is the best block, after finalization check.
fn fork_choice(&self, new: &ExtendedHeader, best: &ExtendedHeader) -> ForkChoice;
/// Returns author should used when executing tx's for this block.
fn executive_author(&self, header: &Header) -> Result<Address, Error> {
Ok(*header.author())
}
}
/// Check whether a given block is the best block based on the default total difficulty rule.

View File

@@ -94,6 +94,11 @@ pub fn new_mix<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
load(params.into(), include_bytes!("../../res/ethereum/mix.json"))
}
/// Create a new Callisto chain spec
pub fn new_callisto<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
load(params.into(), include_bytes!("../../res/ethereum/callisto.json"))
}
/// Create a new Morden testnet chain spec.
pub fn new_morden<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
load(params.into(), include_bytes!("../../res/ethereum/morden.json"))
@@ -109,16 +114,26 @@ pub fn new_kovan<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
load(params.into(), include_bytes!("../../res/ethereum/kovan.json"))
}
/// Create a new Rinkeby testnet chain spec.
pub fn new_rinkeby<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
load(params.into(), include_bytes!("../../res/ethereum/rinkeby.json"))
}
/// Create a new Görli testnet chain spec.
pub fn new_goerli<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
load(params.into(), include_bytes!("../../res/ethereum/goerli.json"))
}
/// Create a new Kotti testnet chain spec.
pub fn new_kotti<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
load(params.into(), include_bytes!("../../res/ethereum/kotti.json"))
}
/// Create a new POA Sokol testnet chain spec.
pub fn new_sokol<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
load(params.into(), include_bytes!("../../res/ethereum/poasokol.json"))
}
/// Create a new Callisto chaun spec
pub fn new_callisto<'a, T: Into<SpecParams<'a>>>(params: T) -> Spec {
load(params.into(), include_bytes!("../../res/ethereum/callisto.json"))
}
// For tests
/// Create a new Foundation Frontier-era chain spec as though it never changes to Homestead.

View File

@@ -214,7 +214,6 @@ impl Author {
}
}
struct SealingWork {
queue: UsingQueue<ClosedBlock>,
enabled: bool,
@@ -630,7 +629,10 @@ impl Miner {
}
}
/// Attempts to perform internal sealing (one that does not require work) and handles the result depending on the type of Seal.
// TODO: (https://github.com/paritytech/parity-ethereum/issues/10407)
// This is only used in authority_round path, and should be refactored to merge with the other seal() path.
// Attempts to perform internal sealing (one that does not require work) and handles the result depending on the
// type of Seal.
fn seal_and_import_block_internally<C>(&self, chain: &C, block: ClosedBlock) -> bool
where C: BlockChain + SealedBlockImporter,
{
@@ -1142,7 +1144,7 @@ impl miner::MinerService for Miner {
if block.header.number() == 1 {
if let Some(name) = self.engine.params().nonzero_bugfix_hard_fork() {
warn!("Your chain specification contains one or more hard forks which are required to be \
on by default. Please remove these forks and start your chain again: {}.", name);
on by default. Please remove these forks and start your chain again: {}.", name);
return;
}
}

View File

@@ -35,7 +35,7 @@ use vm::{EnvInfo, CallType, ActionValue, ActionParams, ParamsType};
use builtin::Builtin;
use engines::{
EthEngine, NullEngine, InstantSeal, InstantSealParams, BasicAuthority,
EthEngine, NullEngine, InstantSeal, InstantSealParams, BasicAuthority, Clique,
AuthorityRound, DEFAULT_BLOCKHASH_CONTRACT
};
use error::Error;
@@ -99,9 +99,9 @@ pub struct CommonParams {
pub validate_receipts_transition: BlockNumber,
/// Validate transaction chain id.
pub validate_chain_id_transition: BlockNumber,
/// Number of first block where EIP-140 (Metropolis: REVERT opcode) rules begin.
/// Number of first block where EIP-140 rules begin.
pub eip140_transition: BlockNumber,
/// Number of first block where EIP-210 (Metropolis: BLOCKHASH changes) rules begin.
/// Number of first block where EIP-210 rules begin.
pub eip210_transition: BlockNumber,
/// EIP-210 Blockhash contract address.
pub eip210_contract_address: Address,
@@ -109,8 +109,7 @@ pub struct CommonParams {
pub eip210_contract_code: Bytes,
/// Gas allocated for EIP-210 blockhash update.
pub eip210_contract_gas: U256,
/// Number of first block where EIP-211 (Metropolis: RETURNDATASIZE/RETURNDATACOPY) rules
/// begin.
/// Number of first block where EIP-211 rules begin.
pub eip211_transition: BlockNumber,
/// Number of first block where EIP-214 rules begin.
pub eip214_transition: BlockNumber,
@@ -611,6 +610,8 @@ impl Spec {
ethjson::spec::Engine::InstantSeal(Some(instant_seal)) => Arc::new(InstantSeal::new(instant_seal.params.into(), machine)),
ethjson::spec::Engine::InstantSeal(None) => Arc::new(InstantSeal::new(InstantSealParams::default(), machine)),
ethjson::spec::Engine::BasicAuthority(basic_authority) => Arc::new(BasicAuthority::new(basic_authority.params.into(), machine)),
ethjson::spec::Engine::Clique(clique) => Clique::new(clique.params.into(), machine)
.expect("Failed to start Clique consensus engine."),
ethjson::spec::Engine::AuthorityRound(authority_round) => AuthorityRound::new(authority_round.params.into(), machine)
.expect("Failed to start AuthorityRound consensus engine."),
}
@@ -827,7 +828,6 @@ impl Spec {
ethjson::spec::Spec::load(reader)
.map_err(fmt_err)
.map(load_machine_from)
}
/// Loads spec from json file. Provide factories for executing contracts and ensuring