Skip to content

Commit 0c68995

Browse files
committed
Handle multiple proposals in a view correctly in Tendermint
Previous code assumes that there is only one proposal in a view. Tendermint was always using the first proposal even though there are many proposals in a view. This commit makes Tendermint find proper proposal in the situation.
1 parent 34341d1 commit 0c68995

File tree

2 files changed

+31
-24
lines changed

2 files changed

+31
-24
lines changed

core/src/consensus/tendermint/vote_collector.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,15 @@ impl VoteCollector {
207207
.unwrap_or_else(Vec::new)
208208
}
209209

210+
pub fn has_votes_for(&self, round: &VoteStep, block_hash: H256) -> bool {
211+
let votes = self
212+
.votes
213+
.get(round)
214+
.map(|c| c.block_votes.keys().cloned().filter_map(|x| x).collect())
215+
.unwrap_or_else(Vec::new);
216+
votes.into_iter().any(|vote_block_hash| vote_block_hash == block_hash)
217+
}
218+
210219
pub fn get_all(&self) -> Vec<ConsensusMessage> {
211220
self.votes.iter().flat_map(|(_round, collector)| collector.messages.iter()).cloned().collect()
212221
}

core/src/consensus/tendermint/worker.rs

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -453,7 +453,7 @@ impl Worker {
453453
self.validators.next_block_proposer(prev_block_hash, view)
454454
}
455455

456-
pub fn proposal_at(&self, height: Height, view: View) -> Option<(SchnorrSignature, usize, Bytes)> {
456+
pub fn first_proposal_at(&self, height: Height, view: View) -> Option<(SchnorrSignature, usize, Bytes)> {
457457
let vote_step = VoteStep {
458458
height,
459459
view,
@@ -475,11 +475,9 @@ impl Worker {
475475
step: Step::Propose,
476476
});
477477

478-
if let Some(proposal) = all_votes.first() {
479-
proposal.on.block_hash.expect("Proposal message always include block hash") == block_hash
480-
} else {
481-
false
482-
}
478+
all_votes
479+
.into_iter()
480+
.any(|proposal| proposal.on.block_hash.expect("Proposal message always include block hash") == block_hash)
483481
}
484482

485483
pub fn vote_step(&self) -> VoteStep {
@@ -670,6 +668,7 @@ impl Worker {
670668
match state.to_step() {
671669
Step::Propose => {
672670
cinfo!(ENGINE, "move_to_step: Propose.");
671+
// If there are multiple proposals, use the first proposal.
673672
if let Some(hash) = self.votes.get_block_hashes(&vote_step).first() {
674673
if self.client().block(&BlockId::Hash(*hash)).is_some() {
675674
self.proposal = Proposal::new_imported(*hash);
@@ -683,9 +682,9 @@ impl Worker {
683682
} else {
684683
let parent_block_hash = self.prev_block_hash();
685684
if self.is_signer_proposer(&parent_block_hash) {
686-
if let TwoThirdsMajority::Lock(lock_view, _) = self.last_two_thirds_majority {
685+
if let TwoThirdsMajority::Lock(lock_view, locked_block_hash) = self.last_two_thirds_majority {
687686
cinfo!(ENGINE, "I am a proposer, I'll re-propose a locked block");
688-
match self.locked_proposal_block(lock_view) {
687+
match self.locked_proposal_block(lock_view, locked_block_hash) {
689688
Ok(block) => self.repropose_block(block),
690689
Err(error_msg) => cwarn!(ENGINE, "{}", error_msg),
691690
}
@@ -796,14 +795,14 @@ impl Worker {
796795
}
797796
}
798797

799-
fn locked_proposal_block(&self, locked_view: View) -> Result<encoded::Block, String> {
798+
fn locked_proposal_block(&self, locked_view: View, locked_proposal_hash: H256) -> Result<encoded::Block, String> {
800799
let vote_step = VoteStep::new(self.height, locked_view, Step::Propose);
801-
let locked_proposal_hash = self.votes.get_block_hashes(&vote_step).first().cloned();
800+
let received_locked_block = self.votes.has_votes_for(&vote_step, locked_proposal_hash);
802801

803-
let locked_proposal_hash = locked_proposal_hash.ok_or_else(|| {
802+
if !received_locked_block {
804803
self.request_proposal_to_any(self.height, locked_view);
805-
format!("Have a lock on {}-{}, but do not received a locked proposal", self.height, locked_view)
806-
})?;
804+
return Err(format!("Have a lock on {}-{}, but do not received a locked proposal", self.height, locked_view))
805+
}
807806

808807
let locked_proposal_block = self.client().block(&BlockId::Hash(locked_proposal_hash)).ok_or_else(|| {
809808
format!(
@@ -970,9 +969,9 @@ impl Worker {
970969
});
971970

972971
let current_height = self.height;
973-
let vote_step = VoteStep::new(self.height, self.view, self.step.to_step());
974-
let proposal_at_current_view = self.votes.get_block_hashes(&vote_step).first().cloned();
975-
if proposal_at_current_view == Some(proposal.hash()) {
972+
let current_vote_step = VoteStep::new(self.height, self.view, self.step.to_step());
973+
let proposal_is_for_current = self.votes.has_votes_for(&current_vote_step, proposal.hash());
974+
if proposal_is_for_current {
976975
self.proposal = Proposal::new_imported(proposal.hash());
977976
let current_step = self.step.clone();
978977
match current_step {
@@ -1005,16 +1004,15 @@ impl Worker {
10051004
self.move_to_height(height);
10061005
let prev_block_view = TendermintSealView::new(proposal.seal()).previous_block_view().unwrap();
10071006
self.save_last_confirmed_view(prev_block_view);
1008-
let proposal_at_view_0 = self
1009-
.votes
1010-
.get_block_hashes(&VoteStep {
1007+
let proposal_is_for_view0 = self.votes.has_votes_for(
1008+
&VoteStep {
10111009
height,
10121010
view: 0,
10131011
step: Step::Propose,
1014-
})
1015-
.first()
1016-
.cloned();
1017-
if proposal_at_view_0 == Some(proposal.hash()) {
1012+
},
1013+
proposal.hash(),
1014+
);
1015+
if proposal_is_for_view0 {
10181016
self.proposal = Proposal::new_imported(proposal.hash())
10191017
}
10201018
self.move_to_step(TendermintState::Prevote, false);
@@ -1896,7 +1894,7 @@ impl Worker {
18961894
return
18971895
}
18981896

1899-
if let Some((signature, _signer_index, block)) = self.proposal_at(request_height, request_view) {
1897+
if let Some((signature, _signer_index, block)) = self.first_proposal_at(request_height, request_view) {
19001898
ctrace!(ENGINE, "Send proposal {}-{} to {:?}", request_height, request_view, token);
19011899
self.send_proposal_block(signature, request_view, block, result);
19021900
return

0 commit comments

Comments
 (0)