diff --git a/ethcore/light/src/client/mod.rs b/ethcore/light/src/client/mod.rs index 0ea8983b9..e1c823ee2 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/client/client.rs b/ethcore/src/client/client.rs index 127f0f9eb..82a612a90 100644 --- a/ethcore/src/client/client.rs +++ b/ethcore/src/client/client.rs @@ -474,7 +474,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(); @@ -536,10 +536,18 @@ 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; + + 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(); let route = chain.insert_block(&mut batch, block_data, receipts.clone(), ExtrasInsert { fork_choice: fork_choice, @@ -560,7 +568,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); @@ -667,9 +675,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/finality.rs b/ethcore/src/engines/authority_round/finality.rs index 3745cde96..3a8be0415 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/authority_round/mod.rs b/ethcore/src/engines/authority_round/mod.rs index 1e6432f10..c6195d0b2 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; @@ -214,7 +215,7 @@ impl EpochManager { // zoom to epoch for given header. returns true if succeeded, false otherwise. fn zoom_to(&mut self, client: &EngineClient, machine: &EthereumMachine, validators: &ValidatorSet, header: &Header) -> bool { - let last_was_parent = self.finality_checker.subchain_head() == Some(header.parent_hash().clone()); + let last_was_parent = self.finality_checker.subchain_head() == Some(*header.parent_hash()); // early exit for current target == chain head, but only if the epochs are // the same. @@ -451,7 +452,7 @@ impl super::EpochVerifier for EpochVerifier { Some(header) => header_empty_steps_signers(header, self.empty_steps_transition).ok()?, _ => Vec::new(), }; - signers.push(parent_header.author().clone()); + signers.push(*parent_header.author()); let newly_finalized = finality_checker.push_hash(parent_header.hash(), signers).ok()?; finalized.extend(newly_finalized); @@ -577,7 +578,7 @@ fn verify_external(header: &Header, validators: &ValidatorSet, empty_steps_trans if is_invalid_proposer { trace!(target: "engine", "verify_block_external: bad proposer for step: {}", header_step); - Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: header.author().clone() }))? + Err(EngineError::NotProposer(Mismatch { expected: correct_proposer, found: *header.author() }))? } else { Ok(()) } @@ -764,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()]; + 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()]); + finalized.unwrap_or_default() + } } fn unix_now() -> Duration { @@ -1144,7 +1206,7 @@ impl Engine for AuthorityRound { trace!(target: "engine", "Multiple blocks proposed for step {}.", parent_step); self.validators.report_malicious(header.author(), set_number, header.number(), Default::default()); - Err(EngineError::DoubleVote(header.author().clone()))?; + Err(EngineError::DoubleVote(*header.author()))?; } // If empty step messages are enabled we will validate the messages in the seal, missing messages are not @@ -1230,9 +1292,53 @@ 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(); + + let mut ancestry = itertools::repeat_call(move || { + chain(hash).and_then(|header| { + if header.number() == 0 { return None } + hash = *header.parent_hash(); + 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, + finalized: &[H256], chain: &super::Headers
, transition_store: &super::PendingTransitionStore, ) -> Option> { @@ -1247,108 +1353,46 @@ 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; - } - }; - - // find most recently finalized blocks, then check transition store for pending transitions. - 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; - } - }; - - let epoch_transition_hash = epoch_manager.epoch_transition_hash; - - // 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 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 = (hash, signers); - trace!(target: "finality", "Ancestry iteration: yielding {:?}", res); - - hash = header.parent_hash().clone(); - parent_empty_steps_signers = empty_step_signers; - - Some(res) - - } else { - warn!(target: "finality", "Failed to get empty step signatures from block {}", header.hash()); - None - } + // check transition store for pending transitions against recently finalized blocks + 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.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 } + else { return Some(header) } + }) }) - }) - .while_some() - .take_while(|&(h, _)| h != epoch_transition_hash); + .while_some() + .take_while(|h| h.hash() != *finalized_hash) + .collect(); - if let Err(_) = epoch_manager.finality_checker.build_ancestry_subchain(ancestry_iter) { - debug!(target: "engine", "inconsistent validator set within epoch"); - return None; - } - } + let finalized_header = chain(*finalized_hash) + .expect("header is finalized; finalized headers must exist in the chain; qed"); - { - 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::>(); + let signal_number = finalized_header.number(); + info!(target: "engine", "Applying validator set change signalled at block {}", signal_number); - // 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(); + finality_proof.push(finalized_header); + finality_proof.reverse(); - info!(target: "engine", "Applying validator set change signalled at block {}", signal_number); + let finality_proof = ::rlp::encode_list(&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)); - } - } + 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 + // 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)); } } @@ -1403,6 +1447,19 @@ 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 { + let finalized = self.build_finality( + block.header(), + &mut ancestry.take_while(|e| !e.is_finalized).map(|e| e.header), + ); + + if !finalized.is_empty() { + debug!(target: "finality", "Finalizing blocks: {:?}", finalized); + } + + finalized.into_iter().map(AncestryAction::MarkFinalized).collect() + } } #[cfg(test)] diff --git a/ethcore/src/engines/basic_authority.rs b/ethcore/src/engines/basic_authority.rs index 58eb5ea22..034d79107 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> { @@ -159,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 0f5a9f4cc..42ea0e81e 100644 --- a/ethcore/src/engines/mod.rs +++ b/ethcore/src/engines/mod.rs @@ -335,10 +335,30 @@ 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( + &self, + _chain_head: &M::Header, + _finalized: &[H256], + _chain: &Headers, + _transition_store: &PendingTransitionStore, + ) -> Option> { + 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, _chain: &Headers, diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index d4a76e430..a3710222a 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> { @@ -652,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,