Skip to content
This repository has been archived by the owner on Nov 6, 2020. It is now read-only.

Commit

Permalink
Merge branch 'dp/chore/extract-spec-from-ethcore' into dp/chore/sort-…
Browse files Browse the repository at this point in the history
…out-ClientIoMessage

* dp/chore/extract-spec-from-ethcore:
  double semi
  Extract engines to own crates (#10966)
  Fix import
  missing import
  Configuration map of block reward contract addresses (#10875)
  Update ethcore/src/snapshot/consensus/mod.rs
  Add a 2/3 quorum option to Authority Round. (#10909)
  Missing import
  Rename supports_warp to snapshot_mode
  Introduce Snapshotting enum to distinguish the type of snapshots a chain uses
  Add an EngineType enum to tighten up Engine.name()
  signers is already a ref
  Update ethcore/engines/clique/src/lib.rs
  Update ethcore/engines/ethash/Cargo.toml
  Update ethcore/engines/basic-authority/Cargo.toml
  Update ethcore/block-reward/Cargo.toml
  • Loading branch information
dvdplm committed Aug 23, 2019
2 parents 0c4991a + b34cfc4 commit 8a8e90a
Show file tree
Hide file tree
Showing 25 changed files with 465 additions and 207 deletions.
135 changes: 68 additions & 67 deletions Cargo.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion ethcore/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ kvdb-memorydb = "0.1"
kvdb-rocksdb = "0.1.3"
lazy_static = { version = "1.3" }
machine = { path = "./machine", features = ["test-helpers"] }
macros = { path = "../util/macros"}
macros = { path = "../util/macros" }
null-engine = { path = "./engines/null-engine" }
parity-runtime = { path = "../util/runtime" }
pod = { path = "pod" }
Expand Down
2 changes: 1 addition & 1 deletion ethcore/block-reward/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[package]
description = "Block rewards"
description = "A crate to interact with the block rewards contract."
name = "block-reward"
version = "0.1.0"
authors = ["Parity Technologies <admin@parity.io>"]
Expand Down
5 changes: 3 additions & 2 deletions ethcore/engine/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ use common_types::{
machine::{AuxiliaryData, AuxiliaryRequest},
},
errors::{EthcoreError as Error, EngineError},
snapshot::Snapshotting,
transaction::{self, UnverifiedTransaction},
};
use client_traits::EngineClient;
Expand Down Expand Up @@ -303,8 +304,8 @@ pub trait Engine: Sync + Send {
/// Trigger next step of the consensus engine.
fn step(&self) {}

/// Whether this engine supports warp sync.
fn supports_warp(&self) -> bool { false }
/// Snapshot mode for the engine: Unsupported, PoW or PoA
fn snapshot_mode(&self) -> Snapshotting { Snapshotting::Unsupported }

/// Return a new open block header timestamp based on the parent timestamp.
fn open_block_header_timestamp(&self, parent_timestamp: u64) -> u64 {
Expand Down
1 change: 1 addition & 0 deletions ethcore/engines/authority-round/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ ethcore = { path = "../..", features = ["test-helpers"] }
spec = { path = "../../spec" }
state-db = { path = "../../state-db" }
validator-set = { path = "../validator-set", features = ["test-helpers"] }
serde_json = "1"
204 changes: 147 additions & 57 deletions ethcore/engines/authority-round/src/finality.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
use std::collections::{VecDeque};
use std::collections::hash_map::{HashMap, Entry};

use common_types::BlockNumber;
use ethereum_types::{H256, Address};
use log::{trace, warn};
use validator_set::SimpleList;
Expand All @@ -30,21 +31,24 @@ pub struct UnknownValidator;
/// Rolling finality checker for authority round consensus.
/// Stores a chain of unfinalized hashes that can be pushed onto.
pub struct RollingFinality {
headers: VecDeque<(H256, Vec<Address>)>,
headers: VecDeque<(H256, BlockNumber, Vec<Address>)>,
signers: SimpleList,
sign_count: HashMap<Address, usize>,
last_pushed: Option<H256>,
/// First block for which a 2/3 quorum (instead of 1/2) is required.
two_thirds_majority_transition: BlockNumber,
}

impl RollingFinality {
/// Create a blank finality checker under the given validator set.
pub fn blank(signers: Vec<Address>) -> Self {
pub fn blank(signers: Vec<Address>, two_thirds_majority_transition: BlockNumber) -> Self {
trace!(target: "finality", "Instantiating blank RollingFinality with {} signers: {:?}", signers.len(), signers);
RollingFinality {
headers: VecDeque::new(),
signers: SimpleList::new(signers),
sign_count: HashMap::new(),
last_pushed: None,
two_thirds_majority_transition,
}
}

Expand All @@ -53,38 +57,28 @@ impl RollingFinality {
///
/// Fails if any provided signature isn't part of the signers set.
pub fn build_ancestry_subchain<I>(&mut self, iterable: I) -> Result<(), UnknownValidator>
where I: IntoIterator<Item=(H256, Vec<Address>)>
where I: IntoIterator<Item=(H256, BlockNumber, Vec<Address>)>,
{
self.clear();
for (hash, signers) in iterable {
for (hash, number, signers) in iterable {
if signers.iter().any(|s| !self.signers.contains(s)) { return Err(UnknownValidator) }
if self.last_pushed.is_none() { self.last_pushed = Some(hash) }

self.add_signers(&signers);
self.headers.push_front((hash, number, signers));
// break when we've got our first finalized block.
{
let current_signed = self.sign_count.len();

let new_signers = signers.iter().filter(|s| !self.sign_count.contains_key(s)).count();
let would_be_finalized = (current_signed + new_signers) * 2 > self.signers.len();

if would_be_finalized {
trace!(target: "finality", "Encountered already finalized block {}", hash);
break
}

for signer in signers.iter() {
*self.sign_count.entry(*signer).or_insert(0) += 1;
}
if self.is_finalized() {
let (hash, _, signers) = self.headers.pop_front().expect("we just pushed a block; qed");
self.remove_signers(&signers);
trace!(target: "finality", "Encountered already finalized block {}", hash);
break
}

self.headers.push_front((hash, signers));
}

trace!(target: "finality", "Rolling finality state: {:?}", self.headers);
Ok(())
}

/// Clear the finality status, but keeps the validator set.
/// Clears the finality status, but keeps the validator set.
pub fn clear(&mut self) {
self.headers.clear();
self.sign_count.clear();
Expand All @@ -99,7 +93,7 @@ impl RollingFinality {
/// Get an iterator over stored hashes in order.
#[cfg(test)]
pub fn unfinalized_hashes(&self) -> impl Iterator<Item=&H256> {
self.headers.iter().map(|(h, _)| h)
self.headers.iter().map(|(h, _, _)| h)
}

/// Get the validator set.
Expand All @@ -110,97 +104,127 @@ impl RollingFinality {
/// Fails if `signer` isn't a member of the active validator set.
/// Returns a list of all newly finalized headers.
// TODO: optimize with smallvec.
pub fn push_hash(&mut self, head: H256, signers: Vec<Address>) -> Result<Vec<H256>, UnknownValidator> {
pub fn push_hash(&mut self, head: H256, number: BlockNumber, signers: Vec<Address>)
-> Result<Vec<H256>, UnknownValidator>
{
for their_signer in signers.iter() {
if !self.signers.contains(their_signer) {
warn!(target: "finality", "Unknown validator: {}", their_signer);
return Err(UnknownValidator)
}
}

for signer in signers.iter() {
*self.sign_count.entry(*signer).or_insert(0) += 1;
}

self.headers.push_back((head, signers));
self.add_signers(&signers);
self.headers.push_back((head, number, signers));

let mut newly_finalized = Vec::new();

while self.sign_count.len() * 2 > self.signers.len() {
let (hash, signers) = self.headers.pop_front()
while self.is_finalized() {
let (hash, _, signers) = self.headers.pop_front()
.expect("headers length always greater than sign count length; qed");

self.remove_signers(&signers);
newly_finalized.push(hash);

for signer in signers {
match self.sign_count.entry(signer) {
Entry::Occupied(mut entry) => {
// decrement count for this signer and purge on zero.
*entry.get_mut() -= 1;

if *entry.get() == 0 {
entry.remove();
}
}
Entry::Vacant(_) => panic!("all hashes in `header` should have entries in `sign_count` for their signers; qed"),
}
}
}

trace!(target: "finality", "{} Blocks finalized by {:?}: {:?}", newly_finalized.len(), head, newly_finalized);

self.last_pushed = Some(head);
Ok(newly_finalized)
}

/// Returns the first block for which a 2/3 quorum (instead of 1/2) is required.
pub fn two_thirds_majority_transition(&self) -> BlockNumber {
self.two_thirds_majority_transition
}

/// Returns whether the first entry in `self.headers` is finalized.
fn is_finalized(&self) -> bool {
match self.headers.front() {
None => false,
Some((_, number, _)) if *number < self.two_thirds_majority_transition => {
self.sign_count.len() * 2 > self.signers.len()
}
Some((_, _, _)) => {
self.sign_count.len() * 3 > self.signers.len() * 2
}
}
}

/// Adds the signers to the sign count.
fn add_signers(&mut self, signers: &[Address]) {
for signer in signers {
*self.sign_count.entry(*signer).or_insert(0) += 1;
}
}

/// Removes the signers from the sign count.
fn remove_signers(&mut self, signers: &[Address]) {
for signer in signers {
match self.sign_count.entry(*signer) {
Entry::Occupied(mut entry) => {
// decrement count for this signer and purge on zero.
if *entry.get() <= 1 {
entry.remove();
} else {
*entry.get_mut() -= 1;
}
}
Entry::Vacant(_) => {
panic!("all hashes in `header` should have entries in `sign_count` for their signers; qed");
}
}
}
}
}

#[cfg(test)]
mod tests {
use common_types::BlockNumber;
use ethereum_types::{H256, Address};
use super::RollingFinality;

#[test]
fn rejects_unknown_signers() {
let signers = (0..3).map(|_| Address::random()).collect::<Vec<_>>();
let mut finality = RollingFinality::blank(signers.clone());
assert!(finality.push_hash(H256::random(), vec![signers[0], Address::random()]).is_err());
let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value());
assert!(finality.push_hash(H256::random(), 0, vec![signers[0], Address::random()]).is_err());
}

#[test]
fn finalize_multiple() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();

let mut finality = RollingFinality::blank(signers.clone());
let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value());
let hashes: Vec<_> = (0..7).map(|_| H256::random()).collect();

// 3 / 6 signers is < 51% so no finality.
for (i, hash) in hashes.iter().take(6).cloned().enumerate() {
let i = i % 3;
assert!(finality.push_hash(hash, vec![signers[i]]).unwrap().len() == 0);
assert!(finality.push_hash(hash, i as u64, vec![signers[i]]).unwrap().len() == 0);
}

// after pushing a block signed by a fourth validator, the first four
// blocks of the unverified chain become verified.
assert_eq!(finality.push_hash(hashes[6], vec![signers[4]]).unwrap(),
assert_eq!(finality.push_hash(hashes[6], 6, vec![signers[4]]).unwrap(),
vec![hashes[0], hashes[1], hashes[2], hashes[3]]);
}

#[test]
fn finalize_multiple_signers() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let mut finality = RollingFinality::blank(signers.clone());
let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value());
let hash = H256::random();

// after pushing a block signed by four validators, it becomes verified right away.
assert_eq!(finality.push_hash(hash, signers[0..4].to_vec()).unwrap(), vec![hash]);
assert_eq!(finality.push_hash(hash, 0, signers[0..4].to_vec()).unwrap(), vec![hash]);
}

#[test]
fn from_ancestry() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let hashes: Vec<_> = (0..12).map(|i| (H256::random(), vec![signers[i % 6]])).collect();
let hashes: Vec<_> = (0..12).map(|i| (H256::random(), i as u64, vec![signers[i % 6]])).collect();

let mut finality = RollingFinality::blank(signers.clone());
let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value());
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();

assert_eq!(finality.unfinalized_hashes().count(), 3);
Expand All @@ -211,15 +235,81 @@ mod tests {
fn from_ancestry_multiple_signers() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let hashes: Vec<_> = (0..12).map(|i| {
(H256::random(), vec![signers[i % 6], signers[(i + 1) % 6], signers[(i + 2) % 6]])
(H256::random(), i as u64, vec![signers[i % 6], signers[(i + 1) % 6], signers[(i + 2) % 6]])
}).collect();

let mut finality = RollingFinality::blank(signers.clone());
let mut finality = RollingFinality::blank(signers.clone(), BlockNumber::max_value());
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();

// only the last hash has < 51% of authorities' signatures
assert_eq!(finality.unfinalized_hashes().count(), 1);
assert_eq!(finality.unfinalized_hashes().next(), Some(&hashes[11].0));
assert_eq!(finality.subchain_head(), Some(hashes[11].0));
}

#[test]
fn rejects_unknown_signers_2_3() {
let signers = (0..3).map(|_| Address::random()).collect::<Vec<_>>();
let mut finality = RollingFinality::blank(signers.clone(), 0);
assert!(finality.push_hash(H256::random(), 0, vec![signers[0], Address::random()]).is_err());
}

#[test]
fn finalize_multiple_2_3() {
let signers: Vec<_> = (0..7).map(|_| Address::random()).collect();

let mut finality = RollingFinality::blank(signers.clone(), 0);
let hashes: Vec<_> = (0..9).map(|_| H256::random()).collect();

// 4 / 7 signers is < 67% so no finality.
for (i, hash) in hashes.iter().take(8).cloned().enumerate() {
let i = i % 4;
assert!(finality.push_hash(hash, i as u64, vec![signers[i]]).unwrap().len() == 0);
}

// after pushing a block signed by a fifth validator, the first five
// blocks of the unverified chain become verified.
assert_eq!(finality.push_hash(hashes[8], 8, vec![signers[4]]).unwrap(),
vec![hashes[0], hashes[1], hashes[2], hashes[3], hashes[4]]);
}

#[test]
fn finalize_multiple_signers_2_3() {
let signers: Vec<_> = (0..5).map(|_| Address::random()).collect();
let mut finality = RollingFinality::blank(signers.clone(), 0);
let hash = H256::random();

// after pushing a block signed by four validators, it becomes verified right away.
assert_eq!(finality.push_hash(hash, 0, signers[0..4].to_vec()).unwrap(), vec![hash]);
}

#[test]
fn from_ancestry_2_3() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let hashes: Vec<_> = (0..12).map(|i| (H256::random(), i as u64, vec![signers[i % 6]])).collect();

let mut finality = RollingFinality::blank(signers, 0);
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();

// The last four hashes, with index 11, 10, 9, and 8, have been pushed. 7 would have finalized a block.
assert_eq!(finality.unfinalized_hashes().count(), 4);
assert_eq!(finality.subchain_head(), Some(hashes[11].0));
}

#[test]
fn from_ancestry_multiple_signers_2_3() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let hashes: Vec<_> = (0..12).map(|i| {
let hash_signers = signers.iter().cycle().skip(i).take(4).cloned().collect();
(H256::random(), i as u64, hash_signers)
}).collect();

let mut finality = RollingFinality::blank(signers.clone(), 0);
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();

// only the last hash has < 67% of authorities' signatures
assert_eq!(finality.unfinalized_hashes().count(), 1);
assert_eq!(finality.unfinalized_hashes().next(), Some(&hashes[11].0));
assert_eq!(finality.subchain_head(), Some(hashes[11].0));
}
}
Loading

0 comments on commit 8a8e90a

Please sign in to comment.