Skip to content

Commit e2dbe6b

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 115a749 commit e2dbe6b

File tree

2 files changed

+31
-24
lines changed

2 files changed

+31
-24
lines changed

core/src/consensus/tendermint/worker.rs

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

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

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

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

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

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

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

973972
let current_height = self.height;
974-
let vote_step = VoteStep::new(self.height, self.view, self.step.to_step());
975-
let proposal_at_current_view = self.votes.get_block_hashes(&vote_step).first().cloned();
976-
if proposal_at_current_view == Some(proposal.hash()) {
973+
let current_vote_step = VoteStep::new(self.height, self.view, self.step.to_step());
974+
let proposal_is_for_current = self.votes.has_votes_for(&current_vote_step, proposal.hash());
975+
if proposal_is_for_current {
977976
self.proposal = Proposal::new_imported(proposal.hash());
978977
let current_step = self.step.clone();
979978
match current_step {
@@ -1006,16 +1005,15 @@ impl Worker {
10061005
self.move_to_height(height);
10071006
let prev_block_view = TendermintSealView::new(proposal.seal()).previous_block_view().unwrap();
10081007
self.save_last_confirmed_view(prev_block_view);
1009-
let proposal_at_view_0 = self
1010-
.votes
1011-
.get_block_hashes(&VoteStep {
1008+
let proposal_is_for_view0 = self.votes.has_votes_for(
1009+
&VoteStep {
10121010
height,
10131011
view: 0,
10141012
step: Step::Propose,
1015-
})
1016-
.first()
1017-
.cloned();
1018-
if proposal_at_view_0 == Some(proposal.hash()) {
1013+
},
1014+
proposal.hash(),
1015+
);
1016+
if proposal_is_for_view0 {
10191017
self.proposal = Proposal::new_imported(proposal.hash())
10201018
}
10211019
self.move_to_step(TendermintState::Prevote, false);
@@ -1897,7 +1895,7 @@ impl Worker {
18971895
return
18981896
}
18991897

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

core/src/consensus/vote_collector.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,15 @@ impl<M: Message + Default + Encodable + Debug> VoteCollector<M> {
230230
guard.get(round).map(|c| c.block_votes.keys().cloned().filter_map(|x| x).collect()).unwrap_or_else(Vec::new)
231231
}
232232

233+
pub fn has_votes_for(&self, round: &M::Round, block_hash: H256) -> bool {
234+
let guard = self.votes.read();
235+
let votes = guard
236+
.get(round)
237+
.map(|c| c.block_votes.keys().cloned().filter_map(|x| x).collect())
238+
.unwrap_or_else(Vec::new);
239+
votes.into_iter().any(|vote_block_hash| vote_block_hash == block_hash)
240+
}
241+
233242
pub fn get_all(&self) -> Vec<M> {
234243
self.votes.read().iter().flat_map(|(_round, collector)| collector.messages.iter()).cloned().collect()
235244
}

0 commit comments

Comments
 (0)