Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion ethcore/res/ethereum/tests
Submodule tests updated from d505c0 to 725dbc
2 changes: 1 addition & 1 deletion ethcore/res/wasm-tests
159 changes: 92 additions & 67 deletions ethcore/src/engines/authority_round/finality.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use std::collections::{VecDeque};
use std::collections::hash_map::{HashMap, Entry};

use ethereum_types::{H256, Address};
use types::BlockNumber;

use engines::validator_set::SimpleList;

Expand All @@ -30,20 +31,23 @@ 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.
quorum_2_3_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>, quorum_2_3_transition: BlockNumber) -> Self {
RollingFinality {
headers: VecDeque::new(),
signers: SimpleList::new(signers),
sign_count: HashMap::new(),
last_pushed: None,
quorum_2_3_transition,
}
}

Expand All @@ -52,38 +56,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 @@ -98,7 +92,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 @@ -109,43 +103,72 @@ 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>
{
if signers.iter().any(|s| !self.signers.contains(s)) { 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 {:?}: {:?}", 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 quorum_2_3_transition(&self) -> BlockNumber {
self.quorum_2_3_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.quorum_2_3_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.
*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");
}
}
}
}
}

#[cfg(test)]
Expand All @@ -156,62 +179,64 @@ mod tests {
#[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(), 0);
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 signers: Vec<_> = (0..7).map(|_| Address::random()).collect();

let mut finality = RollingFinality::blank(signers.clone());
let hashes: Vec<_> = (0..7).map(|_| H256::random()).collect();
let mut finality = RollingFinality::blank(signers.clone(), 0);
let hashes: Vec<_> = (0..9).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);
// 4 / 7 signers is < 67% so no finality.
for (i, hash) in hashes.iter().take(8).cloned().enumerate() {
Comment thread
vkomenda marked this conversation as resolved.
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 fourth validator, the first four
// 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[6], vec![signers[4]]).unwrap(),
vec![hashes[0], hashes[1], hashes[2], hashes[3]]);
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() {
let signers: Vec<_> = (0..6).map(|_| Address::random()).collect();
let mut finality = RollingFinality::blank(signers.clone());
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, 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, 0);
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();

assert_eq!(finality.unfinalized_hashes().count(), 3);
// 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() {
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]])
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());
let mut finality = RollingFinality::blank(signers.clone(), 0);
finality.build_ancestry_subchain(hashes.iter().rev().cloned()).unwrap();

// only the last hash has < 51% of authorities' signatures
// 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));
Expand Down
Loading