diff --git a/ethcore/res/ethereum/tests b/ethcore/res/ethereum/tests
index d505c07e011..725dbc73a54 160000
--- a/ethcore/res/ethereum/tests
+++ b/ethcore/res/ethereum/tests
@@ -1 +1 @@
-Subproject commit d505c07e011ab8eb1b8d71ca4c550543a04aa0fc
+Subproject commit 725dbc73a54649e22a00330bd0f4d6699a5060e5
diff --git a/ethcore/res/wasm-tests b/ethcore/res/wasm-tests
index d17bfb69620..0edbf860ff7 160000
--- a/ethcore/res/wasm-tests
+++ b/ethcore/res/wasm-tests
@@ -1 +1 @@
-Subproject commit d17bfb6962041c4ac7f82eb79f72eef8d42f9447
+Subproject commit 0edbf860ff7ed4b6b6336097ba44836e8c6482dd
diff --git a/ethcore/src/engines/authority_round/finality.rs b/ethcore/src/engines/authority_round/finality.rs
index af57278c9f3..dc25bfeea14 100644
--- a/ethcore/src/engines/authority_round/finality.rs
+++ b/ethcore/src/engines/authority_round/finality.rs
@@ -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;
@@ -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
)>,
+ headers: VecDeque<(H256, BlockNumber, Vec)>,
signers: SimpleList,
sign_count: HashMap,
last_pushed: Option,
+ /// 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) -> Self {
+ pub fn blank(signers: Vec, 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,
}
}
@@ -52,38 +56,28 @@ impl RollingFinality {
///
/// Fails if any provided signature isn't part of the signers set.
pub fn build_ancestry_subchain(&mut self, iterable: I) -> Result<(), UnknownValidator>
- where I: IntoIterator- )>
+ where I: IntoIterator
- )>,
{
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();
@@ -98,7 +92,7 @@ impl RollingFinality {
/// Get an iterator over stored hashes in order.
#[cfg(test)]
pub fn unfinalized_hashes(&self) -> impl Iterator
- {
- self.headers.iter().map(|(h, _)| h)
+ self.headers.iter().map(|(h, _, _)| h)
}
/// Get the validator set.
@@ -109,36 +103,21 @@ 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) -> Result, UnknownValidator> {
+ pub fn push_hash(&mut self, head: H256, number: BlockNumber, signers: Vec)
+ -> Result, 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);
@@ -146,6 +125,50 @@ impl RollingFinality {
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)]
@@ -156,48 +179,49 @@ mod tests {
#[test]
fn rejects_unknown_signers() {
let signers = (0..3).map(|_| Address::random()).collect::>();
- 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() {
+ 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));
}
@@ -205,13 +229,14 @@ 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]])
+ 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));
diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs
index 4409608a723..a261fab494e 100644
--- a/ethcore/src/engines/authority_round/mod.rs
+++ b/ethcore/src/engines/authority_round/mod.rs
@@ -89,6 +89,8 @@ pub struct AuthorityRoundParams {
pub maximum_uncle_count: usize,
/// Empty step messages transition block.
pub empty_steps_transition: u64,
+ /// First block for which a 2/3 quorum (instead of 1/2) is required.
+ pub quorum_2_3_transition: BlockNumber,
/// Number of accepted empty steps.
pub maximum_empty_steps: usize,
/// Transition block to strict empty steps validation.
@@ -124,6 +126,7 @@ impl From for AuthorityRoundParams {
maximum_uncle_count: p.maximum_uncle_count.map_or(0, Into::into),
empty_steps_transition: p.empty_steps_transition.map_or(u64::max_value(), |n| ::std::cmp::max(n.into(), 1)),
maximum_empty_steps: p.maximum_empty_steps.map_or(0, Into::into),
+ quorum_2_3_transition: p.quorum_2_3_transition.map_or_else(BlockNumber::max_value, Into::into),
strict_empty_steps_transition: p.strict_empty_steps_transition.map_or(0, Into::into),
randomness_contract_address: p.randomness_contract_address.map(Into::into),
}
@@ -220,11 +223,11 @@ struct EpochManager {
}
impl EpochManager {
- fn blank() -> Self {
+ fn blank(quorum_2_3_transition: BlockNumber) -> Self {
EpochManager {
epoch_transition_hash: H256::default(),
epoch_transition_number: 0,
- finality_checker: RollingFinality::blank(Vec::new()),
+ finality_checker: RollingFinality::blank(Vec::new(), quorum_2_3_transition),
force: true,
}
}
@@ -275,7 +278,8 @@ impl EpochManager {
.map(|(list, _)| list.into_inner())
.expect("proof produced by this engine; therefore it is valid; qed");
- self.finality_checker = RollingFinality::blank(epoch_set);
+ let quorum_2_3_transition = self.finality_checker.quorum_2_3_transition();
+ self.finality_checker = RollingFinality::blank(epoch_set, quorum_2_3_transition);
}
self.epoch_transition_hash = last_transition.block_hash;
@@ -438,6 +442,7 @@ pub struct AuthorityRound {
maximum_uncle_count: usize,
empty_steps_transition: u64,
strict_empty_steps_transition: u64,
+ quorum_2_3_transition: BlockNumber,
maximum_empty_steps: usize,
machine: EthereumMachine,
/// If set, enables random number contract integration.
@@ -449,6 +454,8 @@ struct EpochVerifier {
step: Arc,
subchain_validators: SimpleList,
empty_steps_transition: u64,
+ /// First block for which a 2/3 quorum (instead of 1/2) is required.
+ quorum_2_3_transition: BlockNumber,
}
impl super::EpochVerifier for EpochVerifier {
@@ -461,7 +468,8 @@ impl super::EpochVerifier for EpochVerifier {
}
fn check_finality_proof(&self, proof: &[u8]) -> Option> {
- let mut finality_checker = RollingFinality::blank(self.subchain_validators.clone().into_inner());
+ let signers = self.subchain_validators.clone().into_inner();
+ let mut finality_checker = RollingFinality::blank(signers, self.quorum_2_3_transition);
let mut finalized = Vec::new();
let headers: Vec = Rlp::new(proof).as_list().ok()?;
@@ -486,7 +494,8 @@ impl super::EpochVerifier for EpochVerifier {
};
signers.push(*parent_header.author());
- let newly_finalized = finality_checker.push_hash(parent_header.hash(), signers).ok()?;
+ let newly_finalized =
+ finality_checker.push_hash(parent_header.hash(), parent_header.number(), signers).ok()?;
finalized.extend(newly_finalized);
Some(())
@@ -692,7 +701,7 @@ impl AuthorityRound {
validate_score_transition: our_params.validate_score_transition,
validate_step_transition: our_params.validate_step_transition,
empty_steps: Default::default(),
- epoch_manager: Mutex::new(EpochManager::blank()),
+ epoch_manager: Mutex::new(EpochManager::blank(our_params.quorum_2_3_transition)),
immediate_transitions: our_params.immediate_transitions,
block_reward: our_params.block_reward,
block_reward_contract_transition: our_params.block_reward_contract_transition,
@@ -701,6 +710,7 @@ impl AuthorityRound {
maximum_uncle_count: our_params.maximum_uncle_count,
empty_steps_transition: our_params.empty_steps_transition,
maximum_empty_steps: our_params.maximum_empty_steps,
+ quorum_2_3_transition: our_params.quorum_2_3_transition,
strict_empty_steps_transition: our_params.strict_empty_steps_transition,
machine: machine,
randomness_contract_address: our_params.randomness_contract_address,
@@ -865,7 +875,7 @@ impl AuthorityRound {
signers.extend(parent_empty_steps_signers.drain(..));
if let Ok(empty_step_signers) = header_empty_steps_signers(&header, self.empty_steps_transition) {
- let res = (header.hash(), signers);
+ let res = (header.hash(), header.number(), signers);
trace!(target: "finality", "Ancestry iteration: yielding {:?}", res);
parent_empty_steps_signers = empty_step_signers;
@@ -878,7 +888,7 @@ impl AuthorityRound {
}
})
.while_some()
- .take_while(|&(h, _)| h != epoch_transition_hash);
+ .take_while(|&(h, _, _)| h != epoch_transition_hash);
if let Err(e) = epoch_manager.finality_checker.build_ancestry_subchain(ancestry_iter) {
debug!(target: "engine", "inconsistent validator set within epoch: {:?}", e);
@@ -886,7 +896,8 @@ impl AuthorityRound {
}
}
- let finalized = epoch_manager.finality_checker.push_hash(chain_head.hash(), vec![*chain_head.author()]);
+ let finalized = epoch_manager.finality_checker.push_hash(
+ chain_head.hash(), chain_head.number(), vec![*chain_head.author()]);
finalized.unwrap_or_default()
}
}
@@ -1182,6 +1193,11 @@ impl Engine for AuthorityRound {
/// Apply the block reward on finalisation of the block.
fn on_close_block(&self, block: &mut ExecutedBlock) -> Result<(), Error> {
let mut beneficiaries = Vec::new();
+
+ if block.header().number() == self.quorum_2_3_transition {
+ info!(target: "engine", "Block {}: Transitioning to 2/3 quorum.", self.quorum_2_3_transition);
+ }
+
if block.header().number() >= self.empty_steps_transition {
let empty_steps = if block.header().seal().is_empty() {
// this is a new block, calculate rewards based on the empty steps messages we have accumulated
@@ -1556,6 +1572,7 @@ impl Engine for AuthorityRound {
step: self.step.clone(),
subchain_validators: list,
empty_steps_transition: self.empty_steps_transition,
+ quorum_2_3_transition: self.quorum_2_3_transition,
});
match finalize {
@@ -1655,6 +1672,7 @@ mod tests {
block_reward_contract_transition: 0,
block_reward_contract: Default::default(),
strict_empty_steps_transition: 0,
+ quorum_2_3_transition: 0,
randomness_contract_address: None,
};
diff --git a/ethcore/src/engines/validator_set/mod.rs b/ethcore/src/engines/validator_set/mod.rs
index 45a7d9ded67..cf1adfbbdb9 100644
--- a/ethcore/src/engines/validator_set/mod.rs
+++ b/ethcore/src/engines/validator_set/mod.rs
@@ -92,7 +92,6 @@ pub trait ValidatorSet: Send + Sync + 'static {
Ok(())
}
- #[cfg(all())]
/// Called for each new block this node is creating. If this block is
/// the first block of an epoch, this is called *after* on_epoch_begin(),
/// but with the same parameters.
diff --git a/ethcore/src/snapshot/tests/proof_of_authority.rs b/ethcore/src/snapshot/tests/proof_of_authority.rs
index f1610e6ccda..0d7ae26e936 100644
--- a/ethcore/src/snapshot/tests/proof_of_authority.rs
+++ b/ethcore/src/snapshot/tests/proof_of_authority.rs
@@ -183,7 +183,7 @@ fn make_chain(accounts: Arc, blocks_beyond: usize, transitions:
// push blocks to finalize transition
for finalization_count in 1.. {
- if finalization_count * 2 > cur_signers.len() { break }
+ if finalization_count * 3 > cur_signers.len() * 2 { break }
push_block(&cur_signers, (num + finalization_count) as u64, make_useless_transactions());
}
diff --git a/json/src/spec/authority_round.rs b/json/src/spec/authority_round.rs
index b061e30bae9..c3eb0be7eb5 100644
--- a/json/src/spec/authority_round.rs
+++ b/json/src/spec/authority_round.rs
@@ -65,6 +65,8 @@ pub struct AuthorityRoundParams {
pub maximum_empty_steps: Option,
/// Strict validation of empty steps transition block.
pub strict_empty_steps_transition: Option,
+ /// First block for which a 2/3 quorum (instead of 1/2) is required.
+ pub quorum_2_3_transition: Option,
/// If set, enables random number contract integration, and use Proof of
/// Stake (PoS) consensus. Otherwise, use Proof of Authority (PoA)
/// consensus.