Skip to content

Commit 241bb85

Browse files
Hyunsik Jeongsgkim126
authored andcommitted
Wait in propose step when generated proposal is empty
Wait for (proposal timeout)/2. Empty proposals are generated immediately, so it would not cause a problem.
1 parent 836a2a0 commit 241bb85

File tree

3 files changed

+100
-7
lines changed

3 files changed

+100
-7
lines changed

core/src/block.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -340,6 +340,7 @@ impl LockedBlock {
340340
/// A block that has a valid seal.
341341
///
342342
/// The block's header has valid seal arguments. The block cannot be reversed into a `ClosedBlock` or `OpenBlock`.
343+
#[derive(Clone)]
343344
pub struct SealedBlock {
344345
block: ExecutedBlock,
345346
}

core/src/consensus/tendermint/mod.rs

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ use crate::BlockId;
6363
use ChainNotify;
6464

6565
/// Timer token representing the consensus step timeouts.
66-
pub const ENGINE_TIMEOUT_TOKEN_NONCE_BASE: TimerToken = 23;
66+
const ENGINE_TIMEOUT_TOKEN_NONCE_BASE: TimerToken = 23;
67+
/// Timer token for empty proposal blocks.
68+
const ENGINE_TIMEOUT_EMPTY_PROPOSAL: TimerToken = 22;
6769

6870
pub type BlockHash = H256;
6971

@@ -198,7 +200,7 @@ impl TendermintInner {
198200
/// Check Tendermint can move from the commit step to the propose step
199201
fn can_move_from_commit_to_propose(&self) -> bool {
200202
let vote_step = VoteStep::new(self.height - 1, self.last_confirmed_view, Step::Precommit);
201-
self.step == TendermintState::Commit && self.has_all_votes(&vote_step) && self.check_prev_block_exists()
203+
self.step.is_commit() && self.has_all_votes(&vote_step) && self.check_prev_block_exists()
202204
}
203205

204206
/// Find the designated for the given view.
@@ -373,7 +375,7 @@ impl TendermintInner {
373375
// move_to_step can be called with the same step
374376
// Also, when moving to the commit step,
375377
// keep `votes_received` for gossiping.
376-
if prev_step != step.into() && step != Step::Commit {
378+
if prev_step.to_step() != step && step != Step::Commit {
377379
self.votes_received = BitSet::new();
378380
}
379381

@@ -555,7 +557,7 @@ impl TendermintInner {
555557
let view = consensus_view(proposal).expect("Imported block is already verified");
556558
if current_height == height && self.view == view {
557559
self.proposal = Some(proposal.hash());
558-
if self.step == TendermintState::Propose {
560+
if self.step.is_propose() {
559561
self.move_to_step(Step::Prevote);
560562
}
561563
} else if current_height < height {
@@ -703,6 +705,22 @@ impl TendermintInner {
703705
panic!("Block is generated at unexpected step {:?}", self.step);
704706
}
705707

708+
if !sealed_block.transactions().is_empty() {
709+
cdebug!(ENGINE, "Submit proposal {} immediatly", hash);
710+
self.submit_proposal_block(sealed_block);
711+
} else {
712+
cdebug!(ENGINE, "Submit proposal {} after the half of the proposal timeout, because it's empty", hash);
713+
self.step = TendermintState::ProposeWaitEmpty {
714+
block: Box::new(sealed_block.clone()),
715+
};
716+
self.extension().set_timer_empty_proposal(self.view);
717+
}
718+
}
719+
720+
fn submit_proposal_block(&mut self, sealed_block: &SealedBlock) {
721+
let header = sealed_block.header();
722+
let hash = header.hash();
723+
706724
let vote_step =
707725
VoteStep::new(header.number() as Height, consensus_view(&header).expect("I am proposer"), Step::Propose);
708726
let vote_info = message_info_rlp(vote_step, Some(hash));
@@ -864,6 +882,23 @@ impl TendermintInner {
864882
}
865883

866884
fn on_timeout(&mut self, token: usize) {
885+
// Timeout from empty block generation
886+
if token == ENGINE_TIMEOUT_EMPTY_PROPOSAL {
887+
let prev_step = mem::replace(&mut self.step, TendermintState::Propose);
888+
match prev_step {
889+
TendermintState::ProposeWaitEmpty {
890+
block,
891+
} => {
892+
self.submit_proposal_block(&block);
893+
}
894+
_ => {
895+
cwarn!(ENGINE, "Empty proposal timer was not cleared.");
896+
}
897+
}
898+
return
899+
}
900+
901+
// Timeout from Tendermint step
867902
if self.extension().is_expired_timeout_token(token) {
868903
return
869904
}
@@ -885,6 +920,12 @@ impl TendermintInner {
885920
cwarn!(ENGINE, "Propose timed out but block is not generated yet");
886921
None
887922
}
923+
TendermintState::ProposeWaitEmpty {
924+
..
925+
} => {
926+
cwarn!(ENGINE, "Propose timed out but still waiting for the empty block");
927+
Some(Step::Prevote)
928+
}
888929
TendermintState::Prevote if self.has_enough_any_votes() => {
889930
ctrace!(ENGINE, "Prevote timeout.");
890931
Some(Step::Precommit)
@@ -1536,12 +1577,20 @@ impl TendermintExtension {
15361577
fn set_timer_step(&self, step: Step, view: View) {
15371578
let expired_token_nonce = self.timeout_token_nonce.fetch_add(1, AtomicOrdering::SeqCst);
15381579

1580+
self.api.clear_timer(ENGINE_TIMEOUT_EMPTY_PROPOSAL).expect("Timer clear succeeds");
15391581
self.api.clear_timer(expired_token_nonce).expect("Timer clear succeeds");
15401582
self.api
15411583
.set_timer_once(expired_token_nonce + 1, self.timeouts.timeout(step, view))
15421584
.expect("Timer set succeeds");
15431585
}
15441586

1587+
fn set_timer_empty_proposal(&self, view: View) {
1588+
self.api.clear_timer(ENGINE_TIMEOUT_EMPTY_PROPOSAL).expect("Timer clear succeeds");
1589+
self.api
1590+
.set_timer_once(ENGINE_TIMEOUT_EMPTY_PROPOSAL, self.timeouts.timeout(Step::Propose, view) / 2)
1591+
.expect("Timer set succeeds");
1592+
}
1593+
15451594
fn on_proposal_message(&self, tendermint: MutexGuard<TendermintInner>, signature: SchnorrSignature, bytes: Bytes) {
15461595
let c = match self.client.upgrade() {
15471596
Some(c) => c,
@@ -1640,7 +1689,7 @@ impl TendermintExtension {
16401689
);
16411690
self.update_peer_state(token, peer_vote_step, peer_proposal, peer_known_votes);
16421691

1643-
let current_vote_step = if tendermint.step == TendermintState::Commit {
1692+
let current_vote_step = if tendermint.step.is_commit() {
16441693
// Even in the commit step, it must be possible to get pre-commits from
16451694
// the previous step. So, act as the last precommit step.
16461695
VoteStep {
@@ -1794,7 +1843,7 @@ impl NetworkExtension for TendermintExtension {
17941843

17951844
impl TimeoutHandler for TendermintExtension {
17961845
fn on_timeout(&self, token: TimerToken) {
1797-
debug_assert!(token >= ENGINE_TIMEOUT_TOKEN_NONCE_BASE);
1846+
debug_assert!(token >= ENGINE_TIMEOUT_TOKEN_NONCE_BASE || token == ENGINE_TIMEOUT_EMPTY_PROPOSAL);
17981847
if let Some(c) = self.tendermint.upgrade() {
17991848
c.on_timeout(token);
18001849
}

core/src/consensus/tendermint/types.rs

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,16 +22,19 @@ use primitives::H256;
2222
use rlp::{Decodable, DecoderError, Encodable, RlpStream, UntrustedRlp};
2323

2424
use super::message::VoteStep;
25+
use crate::block::{IsBlock, SealedBlock};
2526

2627
pub type Height = usize;
2728
pub type View = usize;
2829

29-
#[derive(Debug, PartialEq, Eq, Hash)]
3030
pub enum TendermintState {
3131
Propose,
3232
ProposeWaitBlockGeneration {
3333
parent_hash: H256,
3434
},
35+
ProposeWaitEmpty {
36+
block: Box<SealedBlock>,
37+
},
3538
Prevote,
3639
Precommit,
3740
Commit,
@@ -44,11 +47,51 @@ impl TendermintState {
4447
TendermintState::ProposeWaitBlockGeneration {
4548
..
4649
} => Step::Propose,
50+
TendermintState::ProposeWaitEmpty {
51+
..
52+
} => Step::Propose,
4753
TendermintState::Prevote => Step::Prevote,
4854
TendermintState::Precommit => Step::Precommit,
4955
TendermintState::Commit => Step::Commit,
5056
}
5157
}
58+
59+
pub fn is_propose(&self) -> bool {
60+
match self {
61+
TendermintState::Propose
62+
| TendermintState::ProposeWaitBlockGeneration {
63+
..
64+
}
65+
| TendermintState::ProposeWaitEmpty {
66+
..
67+
} => true,
68+
_ => false,
69+
}
70+
}
71+
72+
pub fn is_commit(&self) -> bool {
73+
match self {
74+
TendermintState::Commit => true,
75+
_ => false,
76+
}
77+
}
78+
}
79+
80+
impl fmt::Debug for TendermintState {
81+
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
82+
match self {
83+
TendermintState::Propose => write!(f, "TendermintState::Propose"),
84+
TendermintState::ProposeWaitBlockGeneration {
85+
parent_hash,
86+
} => write!(f, "TendermintState::ProposeWaitBlockGeneration({})", parent_hash),
87+
TendermintState::ProposeWaitEmpty {
88+
block,
89+
} => write!(f, "TendermintState::ProposeWaitEmpty({})", block.header().hash()),
90+
TendermintState::Prevote => write!(f, "TendermintState::Prevote"),
91+
TendermintState::Precommit => write!(f, "TendermintState::Precommit"),
92+
TendermintState::Commit => write!(f, "TendermintState::Commit"),
93+
}
94+
}
5295
}
5396

5497
impl From<Step> for TendermintState {

0 commit comments

Comments
 (0)