diff --git a/ethcore/src/engines/tendermint/mod.rs b/ethcore/src/engines/tendermint/mod.rs index 8bbed8c84..02cd114b5 100644 --- a/ethcore/src/engines/tendermint/mod.rs +++ b/ethcore/src/engines/tendermint/mod.rs @@ -78,14 +78,14 @@ pub struct Tendermint { proposer_nonce: AtomicUsize, /// Vote accumulator. votes: VoteCollector, - /// Proposed block held until seal is gathered. - proposed_block: Mutex>, /// Channel for updating the sealing. message_channel: Mutex>>, /// Used to sign messages and proposals. account_provider: Mutex>>, /// Message for the last PoLC. - last_lock: RwLock>, + lock_change: RwLock>, + /// Last lock round. + last_lock: AtomicUsize, /// Bare hash of the proposed block, used for seal submission. proposal: RwLock> } @@ -105,10 +105,10 @@ impl Tendermint { step: RwLock::new(Step::Propose), proposer_nonce: AtomicUsize::new(0), votes: VoteCollector::new(), - proposed_block: Mutex::new(None), message_channel: Mutex::new(None), account_provider: Mutex::new(None), - last_lock: RwLock::new(None), + lock_change: RwLock::new(None), + last_lock: AtomicUsize::new(0), proposal: RwLock::new(None) }); let handler = TransitionHandler { engine: Arc::downgrade(&engine) }; @@ -169,29 +169,41 @@ impl Tendermint { self.update_sealing() }, Step::Prevote => { - self.broadcast_message(None) + let should_unlock = |lock_change_round| { + self.last_lock.load(AtomicOrdering::SeqCst) < lock_change_round + && lock_change_round < self.round.load(AtomicOrdering::SeqCst) + }; + let block_hash = match *self.lock_change.read() { + Some(ref m) if should_unlock(m.round) => self.proposal.read().clone(), + Some(ref m) => m.block_hash, + None => None, + }; + self.broadcast_message(block_hash) }, Step::Precommit => { - let block_hash = match *self.last_lock.read() { - Some(ref m) => None, - None => None, + let block_hash = match *self.lock_change.read() { + Some(ref m) if self.is_round(m) => { + self.last_lock.store(m.round, AtomicOrdering::SeqCst); + m.block_hash + }, + _ => None, }; self.broadcast_message(block_hash); }, Step::Commit => { // Commit the block using a complete signature set. let round = self.round.load(AtomicOrdering::SeqCst); - if let Some((proposer, votes)) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { + if let Some(seal) = self.votes.seal_signatures(self.height.load(AtomicOrdering::SeqCst), round, *self.proposal.read()) { let seal = vec![ ::rlp::encode(&round).to_vec(), - ::rlp::encode(proposer).to_vec(), - ::rlp::encode(&votes).to_vec() + ::rlp::encode(&seal.proposal).to_vec(), + ::rlp::encode(&seal.votes).to_vec() ]; if let Some(block_hash) = *self.proposal.read() { self.submit_seal(block_hash, seal); } } - *self.last_lock.write() = None; + *self.lock_change.write() = None; }, } } @@ -218,10 +230,14 @@ impl Tendermint { self.is_nonce_proposer(self.proposer_nonce.load(AtomicOrdering::SeqCst), address) } - fn is_current(&self, message: &ConsensusMessage) -> bool { + fn is__height(&self, message: &ConsensusMessage) -> bool { message.is_height(self.height.load(AtomicOrdering::SeqCst)) } + fn is_round(&self, message: &ConsensusMessage) -> bool { + message.is_round(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst)) + } + fn has_enough_any_votes(&self) -> bool { self.votes.count_step_votes(self.height.load(AtomicOrdering::SeqCst), self.round.load(AtomicOrdering::SeqCst), *self.step.read()) > self.threshold() } @@ -332,17 +348,17 @@ impl Engine for Tendermint { // Check if the message is known. if self.votes.vote(message.clone(), sender).is_none() { - let is_newer_than_lock = match *self.last_lock.read() { + let is_newer_than_lock = match *self.lock_change.read() { Some(ref lock) => &message > lock, None => true, }; if is_newer_than_lock && message.step == Step::Prevote && self.has_enough_aligned_votes(&message) { - *self.last_lock.write() = Some(message.clone()); + *self.lock_change.write() = Some(message.clone()); } // Check if it can affect step transition. - if self.is_current(&message) { + if self.is__height(&message) { let next_step = match *self.step.read() { Step::Precommit if self.has_enough_aligned_votes(&message) => { if message.block_hash.is_none() { diff --git a/ethcore/src/engines/tendermint/timeout.rs b/ethcore/src/engines/tendermint/timeout.rs index 329145984..7cd94350b 100644 --- a/ethcore/src/engines/tendermint/timeout.rs +++ b/ethcore/src/engines/tendermint/timeout.rs @@ -91,6 +91,7 @@ impl IoHandler for TransitionHandler { }, Step::Commit => { set_timeout(io, engine.our_params.timeouts.propose); + engine.last_lock.store(0, AtomicOrdering::SeqCst); engine.round.store(0, AtomicOrdering::SeqCst); engine.height.fetch_add(1, AtomicOrdering::SeqCst); Some(Step::Propose) diff --git a/ethcore/src/engines/tendermint/vote_collector.rs b/ethcore/src/engines/tendermint/vote_collector.rs index e7ea0a5d5..424759678 100644 --- a/ethcore/src/engines/tendermint/vote_collector.rs +++ b/ethcore/src/engines/tendermint/vote_collector.rs @@ -27,6 +27,11 @@ pub struct VoteCollector { votes: RwLock> } +pub struct SealSignatures { + pub proposal: H520, + pub votes: Vec +} + impl VoteCollector { pub fn new() -> VoteCollector { VoteCollector { votes: RwLock::new(BTreeMap::new()) } @@ -36,29 +41,29 @@ impl VoteCollector { self.votes.write().insert(message, voter) } - pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option<(&H520, &[H520])> { - self.votes - .read() - .keys() - .cloned() + pub fn seal_signatures(&self, height: Height, round: Round, block_hash: Option) -> Option { + let guard = self.votes.read(); // Get only Propose and Precommits. + let mut correct_signatures = guard.keys() .filter(|m| m.is_aligned(height, round, block_hash) && m.step != Step::Prevote) - .map(|m| m.signature) - .collect::>() - .split_first() + .map(|m| m.signature.clone()); + correct_signatures.next().map(|proposal| SealSignatures { + proposal: proposal, + votes: correct_signatures.collect() + }) } - pub fn aligned_votes(&self, message: &ConsensusMessage) -> Vec<&ConsensusMessage> { - self.votes - .read() - .keys() + pub fn aligned_votes(&self, message: &ConsensusMessage) -> Vec { + let guard = self.votes.read(); + guard.keys() // Get only Propose and Precommits. .filter(|m| m.is_aligned(message.height, message.round, message.block_hash) && m.step == message.step) + .cloned() .collect() } - pub fn aligned_signatures(&self, message: &ConsensusMessage) -> &[H520] { - self.seal_signatures(message.height, message.round, message.block_hash).map_or(&[], |s| s.1) + pub fn aligned_signatures(&self, message: &ConsensusMessage) -> Vec { + self.seal_signatures(message.height, message.round, message.block_hash).map_or(Vec::new(), |s| s.votes) } pub fn count_step_votes(&self, height: Height, round: Round, step: Step) -> usize {