From a9f311230013f2c00956c4b78f5746a99d3a8540 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Thu, 12 Jul 2018 22:08:35 +0100 Subject: [PATCH 1/6] aura: emit ancestry actions for finalizing blocks --- ethcore/src/engines/authority_round/mod.rs | 67 ++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 577c7b97f63..38c19d55c97 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -44,6 +44,7 @@ use itertools::{self, Itertools}; use rlp::{encode, Decodable, DecoderError, Encodable, RlpStream, Rlp}; use ethereum_types::{H256, H520, Address, U128, U256}; use parking_lot::{Mutex, RwLock}; +use types::ancestry_action::AncestryAction; use unexpected::{Mismatch, OutOfBounds}; mod finality; @@ -1399,6 +1400,72 @@ impl Engine for AuthorityRound { fn fork_choice(&self, new: &ExtendedHeader, current: &ExtendedHeader) -> super::ForkChoice { super::total_difficulty_fork_choice(new, current) } + + fn ancestry_actions(&self, block: &ExecutedBlock, ancestry: &mut Iterator) -> Vec { + if self.immediate_transitions { return Vec::new() } + + let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) { + Some(client) => client, + None => { + warn!(target: "engine", "Unable to apply ancestry actions: missing client ref."); + return Vec::new(); + } + }; + + let chain_head = block.header(); + + let mut epoch_manager = self.epoch_manager.lock(); + if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, chain_head) { + return Vec::new(); + } + + if epoch_manager.finality_checker.subchain_head() != Some(*chain_head.parent_hash()) { + // build new finality checker from unfinalized ancestry of chain head, not including chain head itself yet. + trace!(target: "finality", "Building finality up to parent of {} ({})", + chain_head.hash(), chain_head.parent_hash()); + + let mut parent_empty_steps_signers = match header_empty_steps_signers(&chain_head, self.empty_steps_transition) { + Ok(empty_step_signers) => empty_step_signers, + Err(_) => { + warn!(target: "finality", "Failed to get empty step signatures from block {}", chain_head.hash()); + return Vec::new(); + } + }; + + let ancestry_iter = ancestry.take_while(|e| !e.is_finalized) + .map(|extended_header| { + let header = extended_header.header; + let mut signers = vec![header.author().clone()]; + 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); + trace!(target: "finality", "Ancestry iteration: yielding {:?}", res); + + parent_empty_steps_signers = empty_step_signers; + + Some(res) + + } else { + warn!(target: "finality", "Failed to get empty step signatures from block {}", header.hash()); + None + } + }) + .while_some(); + + if let Err(_) = epoch_manager.finality_checker.build_ancestry_subchain(ancestry_iter) { + debug!(target: "engine", "inconsistent validator set within epoch"); + return Vec::new(); + } + } + + if let Ok(finalized) = epoch_manager.finality_checker.push_hash(chain_head.hash(), vec![chain_head.author().clone()]) { + debug!(target: "finality", "Finalizing blocks: {:?}", finalized); + return finalized.into_iter().map(AncestryAction::MarkFinalized).collect() + } + + Vec::new() + } } #[cfg(test)] From 7d417fa72f07787baae0f2d921421d4b12dae5cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Fri, 13 Jul 2018 00:14:38 +0100 Subject: [PATCH 2/6] aura: refactor is_epoch_end to get finalized blocks as argument --- ethcore/src/client/client.rs | 14 ++- ethcore/src/engines/authority_round/mod.rs | 121 +++++++-------------- ethcore/src/engines/basic_authority.rs | 1 + ethcore/src/engines/mod.rs | 1 + ethcore/src/engines/tendermint/mod.rs | 1 + 5 files changed, 50 insertions(+), 88 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 5588c9067a8..7abcb8ee8be 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -535,10 +535,11 @@ impl Importer { state.journal_under(&mut batch, number, hash).expect("DB commit failed"); - for ancestry_action in ancestry_actions { - let AncestryAction::MarkFinalized(ancestry) = ancestry_action; - chain.mark_finalized(&mut batch, ancestry).expect("Engine's ancestry action must be known blocks; qed"); - } + let finalized: Vec<_> = ancestry_actions.into_iter().map(|ancestry_action| { + let AncestryAction::MarkFinalized(a) = ancestry_action; + chain.mark_finalized(&mut batch, a).expect("Engine's ancestry action must be known blocks; qed"); + a + }).collect(); let route = chain.insert_block(&mut batch, block_data, receipts.clone(), ExtrasInsert { fork_choice: fork_choice, @@ -559,7 +560,7 @@ impl Importer { client.db.read().key_value().write_buffered(batch); chain.commit(); - self.check_epoch_end(&header, &chain, client); + self.check_epoch_end(&header, &finalized, &chain, client); client.update_last_hashes(&parent, hash); @@ -666,9 +667,10 @@ impl Importer { } // check for ending of epoch and write transition if it occurs. - fn check_epoch_end<'a>(&self, header: &'a Header, chain: &BlockChain, client: &Client) { + fn check_epoch_end<'a>(&self, header: &'a Header, finalized: &'a [H256], chain: &BlockChain, client: &Client) { let is_epoch_end = self.engine.is_epoch_end( header, + finalized, &(|hash| client.block_header_decoded(BlockId::Hash(hash))), &(|hash| chain.get_pending_transition(hash)), // TODO: limit to current epoch. ); diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 38c19d55c97..8a985e0e139 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -1230,6 +1230,7 @@ impl Engine for AuthorityRound { fn is_epoch_end( &self, chain_head: &Header, + finalized: &[H256], chain: &super::Headers
, transition_store: &super::PendingTransitionStore, ) -> Option> { @@ -1252,100 +1253,54 @@ impl Engine for AuthorityRound { } }; - // find most recently finalized blocks, then check transition store for pending transitions. + // check transition store for pending transitions against recently finalized blocks let mut epoch_manager = self.epoch_manager.lock(); if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, chain_head) { return None; } - if epoch_manager.finality_checker.subchain_head() != Some(*chain_head.parent_hash()) { - // build new finality checker from ancestry of chain head, - // not including chain head itself yet. - trace!(target: "finality", "Building finality up to parent of {} ({})", - chain_head.hash(), chain_head.parent_hash()); - - let mut hash = chain_head.parent_hash().clone(); - let mut parent_empty_steps_signers = match header_empty_steps_signers(&chain_head, self.empty_steps_transition) { - Ok(empty_step_signers) => empty_step_signers, - Err(_) => { - warn!(target: "finality", "Failed to get empty step signatures from block {}", chain_head.hash()); - return None; - } - }; + for finalized_hash in finalized { + if let Some(pending) = transition_store(*finalized_hash) { + // walk the chain backwards from current head until finalized_hash + // to construct transition proof. author == ec_recover(sig) known + // since the blocks are in the DB. + let mut hash = *chain_head.parent_hash(); + let finality_proof = itertools::repeat_call(move || { + chain(hash).and_then(|header| { + hash = *header.parent_hash(); + if header.number() == 0 { return None } + else { return Some(header) } + }) + }) + .while_some() + .take_while(|h| h.hash() != *finalized_hash); - let epoch_transition_hash = epoch_manager.epoch_transition_hash; + let finalized_header = chain(*finalized_hash) + .expect("header is finalized; finalized headers must exist in the chain; qed"); - // walk the chain within current epoch backwards. - // author == ec_recover(sig) known since the blocks are in the DB. - // the empty steps messages in a header signal approval of the parent header. - let ancestry_iter = itertools::repeat_call(move || { - chain(hash).and_then(|header| { - if header.number() == 0 { return None } + let signal_number = finalized_header.number(); + info!(target: "engine", "Applying validator set change signalled at block {}", signal_number); - let mut signers = vec![header.author().clone()]; - signers.extend(parent_empty_steps_signers.drain(..)); + let mut finality_proof: Vec<_> = ::std::iter::once(chain_head.clone()) + .chain(finality_proof) + .chain(::std::iter::once(finalized_header)) + .collect(); - if let Ok(empty_step_signers) = header_empty_steps_signers(&header, self.empty_steps_transition) { - let res = (hash, signers); - trace!(target: "finality", "Ancestry iteration: yielding {:?}", res); + finality_proof.reverse(); - hash = header.parent_hash().clone(); - parent_empty_steps_signers = empty_step_signers; + let finality_proof = ::rlp::encode_list(&finality_proof); + epoch_manager.note_new_epoch(); - Some(res) - } else { - warn!(target: "finality", "Failed to get empty step signatures from block {}", header.hash()); - None - } - }) - }) - .while_some() - .take_while(|&(h, _)| h != epoch_transition_hash); - - if let Err(_) = epoch_manager.finality_checker.build_ancestry_subchain(ancestry_iter) { - debug!(target: "engine", "inconsistent validator set within epoch"); - return None; - } - } - - { - if let Ok(finalized) = epoch_manager.finality_checker.push_hash(chain_head.hash(), vec![chain_head.author().clone()]) { - let mut finalized = finalized.into_iter(); - while let Some(finalized_hash) = finalized.next() { - if let Some(pending) = transition_store(finalized_hash) { - let finality_proof = ::std::iter::once(finalized_hash) - .chain(finalized) - .chain(epoch_manager.finality_checker.unfinalized_hashes()) - .map(|h| if h == chain_head.hash() { - // chain closure only stores ancestry, but the chain head is also - // unfinalized. - chain_head.clone() - } else { - chain(h).expect("these headers fetched before when constructing finality checker; qed") - }) - .collect::>(); - - // this gives us the block number for `hash`, assuming it's ancestry. - let signal_number = chain_head.number() - - finality_proof.len() as BlockNumber - + 1; - let finality_proof = ::rlp::encode_list(&finality_proof); - epoch_manager.note_new_epoch(); - - info!(target: "engine", "Applying validator set change signalled at block {}", signal_number); - - // We turn off can_propose here because upon validator set change there can - // be two valid proposers for a single step: one from the old set and - // one from the new. - // - // This way, upon encountering an epoch change, the proposer from the - // new set will be forced to wait until the next step to avoid sealing a - // block that breaks the invariant that the parent's step < the block's step. - self.step.can_propose.store(false, AtomicOrdering::SeqCst); - return Some(combine_proofs(signal_number, &pending.proof, &*finality_proof)); - } - } + // We turn off can_propose here because upon validator set change there can + // be two valid proposers for a single step: one from the old set and + // one from the new. + // + // This way, upon encountering an epoch change, the proposer from the + // new set will be forced to wait until the next step to avoid sealing a + // block that breaks the invariant that the parent's step < the block's step. + self.step.can_propose.store(false, AtomicOrdering::SeqCst); + return Some(combine_proofs(signal_number, &pending.proof, &*finality_proof)); } } @@ -1424,6 +1379,8 @@ impl Engine for AuthorityRound { trace!(target: "finality", "Building finality up to parent of {} ({})", chain_head.hash(), chain_head.parent_hash()); + // the empty steps messages in a header signal approval of the + // parent header. let mut parent_empty_steps_signers = match header_empty_steps_signers(&chain_head, self.empty_steps_transition) { Ok(empty_step_signers) => empty_step_signers, Err(_) => { diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index e73b1046192..548fcd5765f 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -150,6 +150,7 @@ impl Engine for BasicAuthority { fn is_epoch_end( &self, chain_head: &Header, + _finalized: &[H256], _chain: &super::Headers
, _transition_store: &super::PendingTransitionStore, ) -> Option> { diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 424a9e94c91..be7e350c691 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -339,6 +339,7 @@ pub trait Engine: Sync + Send { fn is_epoch_end( &self, _chain_head: &M::Header, + _finalized: &[H256], _chain: &Headers, _transition_store: &PendingTransitionStore, ) -> Option> { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 2d0015d06b2..e7f3c76b90f 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -635,6 +635,7 @@ impl Engine for Tendermint { fn is_epoch_end( &self, chain_head: &Header, + _finalized: &[H256], _chain: &super::Headers
, transition_store: &super::PendingTransitionStore, ) -> Option> { From a758f68845150ac75916a866e802b4860ea8b351 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 4 Sep 2018 00:58:43 +0100 Subject: [PATCH 3/6] ethcore: add is_epoch_end_light method to Engine The full client now tracks finality by querying the engine on each block import, and it also persists the finalization state to the DB. For the light client current it doesn't persist finality information and only keeps track of finality for epoch signals, by calling `is_epoch_end_light`. This method implements the previously existing logic of building finality for all the blocks in the current epoch and then checking the finalized blocks against the transition store. --- ethcore/light/src/client/mod.rs | 2 +- ethcore/src/engines/authority_round/mod.rs | 171 +++++++++++++-------- ethcore/src/engines/basic_authority.rs | 9 ++ ethcore/src/engines/mod.rs | 9 ++ ethcore/src/engines/tendermint/mod.rs | 9 ++ 5 files changed, 138 insertions(+), 62 deletions(-) diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index 3c88e8b80d4..1ac75f0f35f 100644 --- a/ethcore/light/src/client/mod.rs +++ b/ethcore/light/src/client/mod.rs @@ -313,7 +313,7 @@ impl Client { The node may not be able to synchronize further.", e); } - let epoch_proof = self.engine.is_epoch_end( + let epoch_proof = self.engine.is_epoch_end_light( &verified_header, &|h| self.chain.block_header(BlockId::Hash(h)).and_then(|hdr| hdr.decode().ok()), &|h| self.chain.pending_transition(h), diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 8a985e0e139..7da25a92985 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -765,6 +765,67 @@ impl AuthorityRound { } } } + + // Returns the hashes of all ancestor blocks that are finalized by the given `chain_head`. + fn build_finality(&self, chain_head: &Header, ancestry: &mut Iterator) -> Vec { + if self.immediate_transitions { return Vec::new() } + + let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) { + Some(client) => client, + None => { + warn!(target: "engine", "Unable to apply ancestry actions: missing client ref."); + return Vec::new(); + } + }; + + let mut epoch_manager = self.epoch_manager.lock(); + if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, chain_head) { + return Vec::new(); + } + + if epoch_manager.finality_checker.subchain_head() != Some(*chain_head.parent_hash()) { + // build new finality checker from unfinalized ancestry of chain head, not including chain head itself yet. + trace!(target: "finality", "Building finality up to parent of {} ({})", + chain_head.hash(), chain_head.parent_hash()); + + // the empty steps messages in a header signal approval of the + // parent header. + let mut parent_empty_steps_signers = match header_empty_steps_signers(&chain_head, self.empty_steps_transition) { + Ok(empty_step_signers) => empty_step_signers, + Err(_) => { + warn!(target: "finality", "Failed to get empty step signatures from block {}", chain_head.hash()); + return Vec::new(); + } + }; + + let ancestry_iter = ancestry.map(|header| { + let mut signers = vec![header.author().clone()]; + 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); + trace!(target: "finality", "Ancestry iteration: yielding {:?}", res); + + parent_empty_steps_signers = empty_step_signers; + + Some(res) + + } else { + warn!(target: "finality", "Failed to get empty step signatures from block {}", header.hash()); + None + } + }) + .while_some(); + + if let Err(_) = epoch_manager.finality_checker.build_ancestry_subchain(ancestry_iter) { + debug!(target: "engine", "inconsistent validator set within epoch"); + return Vec::new(); + } + } + + let finalized = epoch_manager.finality_checker.push_hash(chain_head.hash(), vec![chain_head.author().clone()]); + finalized.unwrap_or_default() + } } fn unix_now() -> Duration { @@ -1227,6 +1288,49 @@ impl Engine for AuthorityRound { self.validators.signals_epoch_end(first, header, aux) } + fn is_epoch_end_light( + &self, + chain_head: &Header, + chain: &super::Headers
, + transition_store: &super::PendingTransitionStore, + ) -> Option> { + // epochs only matter if we want to support light clients. + if self.immediate_transitions { return None } + + let epoch_transition_hash = { + let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) { + Some(client) => client, + None => { + warn!(target: "engine", "Unable to check for epoch end: missing client ref."); + return None; + } + }; + + let mut epoch_manager = self.epoch_manager.lock(); + if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, chain_head) { + return None; + } + + epoch_manager.epoch_transition_hash + }; + + let mut hash = chain_head.parent_hash().clone(); + + let mut ancestry = itertools::repeat_call(move || { + chain(hash).and_then(|header| { + if header.number() == 0 { return None } + hash = header.parent_hash().clone(); + Some(header) + }) + }) + .while_some() + .take_while(|header| header.hash() != epoch_transition_hash); + + let finalized = self.build_finality(chain_head, &mut ancestry); + + self.is_epoch_end(chain_head, &finalized, chain, transition_store) + } + fn is_epoch_end( &self, chain_head: &Header, @@ -1357,71 +1461,16 @@ impl Engine for AuthorityRound { } fn ancestry_actions(&self, block: &ExecutedBlock, ancestry: &mut Iterator) -> Vec { - if self.immediate_transitions { return Vec::new() } - - let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) { - Some(client) => client, - None => { - warn!(target: "engine", "Unable to apply ancestry actions: missing client ref."); - return Vec::new(); - } - }; - - let chain_head = block.header(); - - let mut epoch_manager = self.epoch_manager.lock(); - if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, chain_head) { - return Vec::new(); - } - - if epoch_manager.finality_checker.subchain_head() != Some(*chain_head.parent_hash()) { - // build new finality checker from unfinalized ancestry of chain head, not including chain head itself yet. - trace!(target: "finality", "Building finality up to parent of {} ({})", - chain_head.hash(), chain_head.parent_hash()); - - // the empty steps messages in a header signal approval of the - // parent header. - let mut parent_empty_steps_signers = match header_empty_steps_signers(&chain_head, self.empty_steps_transition) { - Ok(empty_step_signers) => empty_step_signers, - Err(_) => { - warn!(target: "finality", "Failed to get empty step signatures from block {}", chain_head.hash()); - return Vec::new(); - } - }; - - let ancestry_iter = ancestry.take_while(|e| !e.is_finalized) - .map(|extended_header| { - let header = extended_header.header; - let mut signers = vec![header.author().clone()]; - 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); - trace!(target: "finality", "Ancestry iteration: yielding {:?}", res); - - parent_empty_steps_signers = empty_step_signers; - - Some(res) - - } else { - warn!(target: "finality", "Failed to get empty step signatures from block {}", header.hash()); - None - } - }) - .while_some(); - - if let Err(_) = epoch_manager.finality_checker.build_ancestry_subchain(ancestry_iter) { - debug!(target: "engine", "inconsistent validator set within epoch"); - return Vec::new(); - } - } + let finalized = self.build_finality( + block.header(), + &mut ancestry.take_while(|e| !e.is_finalized).map(|e| e.header), + ); - if let Ok(finalized) = epoch_manager.finality_checker.push_hash(chain_head.hash(), vec![chain_head.author().clone()]) { + if !finalized.is_empty() { debug!(target: "finality", "Finalizing blocks: {:?}", finalized); - return finalized.into_iter().map(AncestryAction::MarkFinalized).collect() } - Vec::new() + finalized.into_iter().map(AncestryAction::MarkFinalized).collect() } } diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 548fcd5765f..5d33026af8b 100644 --- a/ethcore/src/engines/basic_authority.rs +++ b/ethcore/src/engines/basic_authority.rs @@ -160,6 +160,15 @@ impl Engine for BasicAuthority { self.validators.is_epoch_end(first, chain_head) } + fn is_epoch_end_light( + &self, + chain_head: &Header, + chain: &super::Headers
, + transition_store: &super::PendingTransitionStore, + ) -> Option> { + self.is_epoch_end(chain_head, &[], chain, transition_store) + } + fn epoch_verifier<'a>(&self, header: &Header, proof: &'a [u8]) -> ConstructedVerifier<'a, EthereumMachine> { let first = header.number() == 0; diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index be7e350c691..08cdb06162d 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -346,6 +346,15 @@ pub trait Engine: Sync + Send { None } + fn is_epoch_end_light( + &self, + _chain_head: &M::Header, + _chain: &Headers, + _transition_store: &PendingTransitionStore, + ) -> Option> { + None + } + /// Create an epoch verifier from validation proof and a flag indicating /// whether finality is required. fn epoch_verifier<'a>(&self, _header: &M::Header, _proof: &'a [u8]) -> ConstructedVerifier<'a, M> { diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index e7f3c76b90f..90f69443f78 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -653,6 +653,15 @@ impl Engine for Tendermint { None } + fn is_epoch_end_light( + &self, + chain_head: &Header, + chain: &super::Headers
, + transition_store: &super::PendingTransitionStore, + ) -> Option> { + self.is_epoch_end(chain_head, &[], chain, transition_store) + } + fn epoch_verifier<'a>(&self, _header: &Header, proof: &'a [u8]) -> ConstructedVerifier<'a, EthereumMachine> { let (signal_number, set_proof, finality_proof) = match destructure_proofs(proof) { Ok(x) => x, From 10d5df420ae4e880527c99304cbb754a232369dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Tue, 4 Sep 2018 12:48:35 +0100 Subject: [PATCH 4/6] ethcore: allow finalizing current block --- ethcore/src/client/client.rs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/ethcore/src/client/client.rs b/ethcore/src/client/client.rs index 7abcb8ee8be..19bba286361 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -473,7 +473,7 @@ impl Importer { let number = header.number(); let parent = header.parent_hash(); let chain = client.chain.read(); - let is_finalized = false; + let mut is_finalized = false; // Commit results let block = block.drain(); @@ -537,7 +537,14 @@ impl Importer { let finalized: Vec<_> = ancestry_actions.into_iter().map(|ancestry_action| { let AncestryAction::MarkFinalized(a) = ancestry_action; - chain.mark_finalized(&mut batch, a).expect("Engine's ancestry action must be known blocks; qed"); + + if a != header.hash() { + chain.mark_finalized(&mut batch, a).expect("Engine's ancestry action must be known blocks; qed"); + } else { + // we're finalizing the current block + is_finalized = true; + } + a }).collect(); From 48df25b8643dac0aac4ad947f1610c1d5473a174 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Mon, 10 Sep 2018 16:15:00 +0100 Subject: [PATCH 5/6] aura: fix construction of finality proof --- ethcore/src/engines/authority_round/mod.rs | 28 +++++----------------- 1 file changed, 6 insertions(+), 22 deletions(-) diff --git a/ethcore/src/engines/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 7da25a92985..25892a9d00f 100644 --- a/ethcore/src/engines/authority_round/mod.rs +++ b/ethcore/src/engines/authority_round/mod.rs @@ -1349,27 +1349,14 @@ impl Engine for AuthorityRound { return Some(change) } - let client = match self.client.read().as_ref().and_then(|weak| weak.upgrade()) { - Some(client) => client, - None => { - warn!(target: "engine", "Unable to check for epoch end: missing client ref."); - return None; - } - }; - // check transition store for pending transitions against recently finalized blocks - let mut epoch_manager = self.epoch_manager.lock(); - if !epoch_manager.zoom_to(&*client, &self.machine, &*self.validators, chain_head) { - return None; - } - for finalized_hash in finalized { if let Some(pending) = transition_store(*finalized_hash) { // walk the chain backwards from current head until finalized_hash // to construct transition proof. author == ec_recover(sig) known // since the blocks are in the DB. - let mut hash = *chain_head.parent_hash(); - let finality_proof = itertools::repeat_call(move || { + let mut hash = chain_head.hash(); + let mut finality_proof: Vec<_> = itertools::repeat_call(move || { chain(hash).and_then(|header| { hash = *header.parent_hash(); if header.number() == 0 { return None } @@ -1377,7 +1364,8 @@ impl Engine for AuthorityRound { }) }) .while_some() - .take_while(|h| h.hash() != *finalized_hash); + .take_while(|h| h.hash() != *finalized_hash) + .collect(); let finalized_header = chain(*finalized_hash) .expect("header is finalized; finalized headers must exist in the chain; qed"); @@ -1385,16 +1373,12 @@ impl Engine for AuthorityRound { let signal_number = finalized_header.number(); info!(target: "engine", "Applying validator set change signalled at block {}", signal_number); - let mut finality_proof: Vec<_> = ::std::iter::once(chain_head.clone()) - .chain(finality_proof) - .chain(::std::iter::once(finalized_header)) - .collect(); - + finality_proof.push(finalized_header); finality_proof.reverse(); let finality_proof = ::rlp::encode_list(&finality_proof); - epoch_manager.note_new_epoch(); + self.epoch_manager.lock().note_new_epoch(); // We turn off can_propose here because upon validator set change there can // be two valid proposers for a single step: one from the old set and From 8d53074d0f8944148050950d0e24ba2134ae1123 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Silva?= Date: Mon, 10 Sep 2018 16:45:27 +0100 Subject: [PATCH 6/6] aura: fix warnings - missing docs for is_epoch_end_light - unused method unfinalized_hashes in RollingFinality --- ethcore/src/engines/authority_round/finality.rs | 17 +++++------------ ethcore/src/engines/mod.rs | 12 +++++++++++- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/ethcore/src/engines/authority_round/finality.rs b/ethcore/src/engines/authority_round/finality.rs index 3745cde96c2..3a8be04156d 100644 --- a/ethcore/src/engines/authority_round/finality.rs +++ b/ethcore/src/engines/authority_round/finality.rs @@ -96,7 +96,10 @@ impl RollingFinality { } /// Get an iterator over stored hashes in order. - pub fn unfinalized_hashes(&self) -> Iter { Iter(self.headers.iter()) } + #[cfg(test)] + pub fn unfinalized_hashes(&self) -> impl Iterator { + self.headers.iter().map(|(h, _)| h) + } /// Get the validator set. pub fn validators(&self) -> &SimpleList { &self.signers } @@ -145,16 +148,6 @@ impl RollingFinality { } } -pub struct Iter<'a>(::std::collections::vec_deque::Iter<'a, (H256, Vec
)>); - -impl<'a> Iterator for Iter<'a> { - type Item = H256; - - fn next(&mut self) -> Option { - self.0.next().map(|&(h, _)| h) - } -} - #[cfg(test)] mod tests { use ethereum_types::{H256, Address}; @@ -220,7 +213,7 @@ mod tests { // 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.unfinalized_hashes().next(), Some(&hashes[11].0)); assert_eq!(finality.subchain_head(), Some(hashes[11].0)); } } diff --git a/ethcore/src/engines/mod.rs b/ethcore/src/engines/mod.rs index 08cdb06162d..1154ba24c28 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -333,7 +333,8 @@ pub trait Engine: Sync + Send { /// /// This either means that an immediate transition occurs or a block signalling transition /// has reached finality. The `Headers` given are not guaranteed to return any blocks - /// from any epoch other than the current. + /// from any epoch other than the current. The client must keep track of finality and provide + /// the latest finalized headers to check against the transition store. /// /// Return optional transition proof. fn is_epoch_end( @@ -346,6 +347,15 @@ pub trait Engine: Sync + Send { None } + /// Whether a block is the end of an epoch. + /// + /// This either means that an immediate transition occurs or a block signalling transition + /// has reached finality. The `Headers` given are not guaranteed to return any blocks + /// from any epoch other than the current. This is a specialized method to use for light + /// clients since the light client doesn't track finality of all blocks, and therefore finality + /// for blocks in the current epoch is built inside this method by the engine. + /// + /// Return optional transition proof. fn is_epoch_end_light( &self, _chain_head: &M::Header,