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.