Skip to content
This repository was archived by the owner on Dec 26, 2024. It is now read-only.

Commit c11022c

Browse files
feat(consensus): single height consensus returns Decision with Precommits (#2206)
1 parent bcef46c commit c11022c

File tree

5 files changed

+107
-40
lines changed

5 files changed

+107
-40
lines changed

crates/sequencing/papyrus_consensus/src/lib.rs

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,14 @@ use papyrus_protobuf::consensus::{ConsensusMessage, Proposal};
1212
use single_height_consensus::SingleHeightConsensus;
1313
use starknet_api::block::{BlockHash, BlockNumber};
1414
use tracing::{debug, info, instrument};
15-
use types::{ConsensusBlock, ConsensusContext, ConsensusError, ProposalInit, ValidatorId};
15+
use types::{
16+
ConsensusBlock,
17+
ConsensusContext,
18+
ConsensusError,
19+
Decision,
20+
ProposalInit,
21+
ValidatorId,
22+
};
1623

1724
pub mod config;
1825
#[allow(missing_docs)]
@@ -36,7 +43,7 @@ async fn run_height<BlockT: ConsensusBlock>(
3643
validator_id: ValidatorId,
3744
network_receiver: &mut SubscriberReceiver<ConsensusMessage>,
3845
cached_messages: &mut Vec<ConsensusMessage>,
39-
) -> Result<BlockT, ConsensusError>
46+
) -> Result<Decision<BlockT>, ConsensusError>
4047
where
4148
ProposalWrapper:
4249
Into<(ProposalInit, mpsc::Receiver<BlockT::ProposalChunk>, oneshot::Receiver<BlockHash>)>,
@@ -75,7 +82,7 @@ where
7582
continue;
7683
}
7784

78-
let maybe_block = match message {
85+
let maybe_decision = match message {
7986
ConsensusMessage::Proposal(proposal) => {
8087
// Special case due to fake streaming.
8188
let (proposal_init, content_receiver, fin_receiver) =
@@ -85,8 +92,8 @@ where
8592
_ => shc.handle_message(message).await?,
8693
};
8794

88-
if let Some(block) = maybe_block {
89-
return Ok(block);
95+
if let Some(decision) = maybe_decision {
96+
return Ok(decision);
9097
}
9198
}
9299
}
@@ -107,7 +114,7 @@ where
107114
let mut current_height = start_height;
108115
let mut future_messages = Vec::new();
109116
loop {
110-
let block = run_height(
117+
let decision = run_height(
111118
Arc::clone(&context),
112119
current_height,
113120
validator_id,
@@ -118,8 +125,9 @@ where
118125

119126
info!(
120127
"Finished consensus for height: {current_height}. Agreed on block with id: {:x}",
121-
block.id().0
128+
decision.block.id().0
122129
);
130+
debug!("Decision: {:?}", decision);
123131
metrics::gauge!(papyrus_metrics::PAPYRUS_CONSENSUS_HEIGHT, current_height.0 as f64);
124132
current_height = current_height.unchecked_next();
125133
}

crates/sequencing/papyrus_consensus/src/single_height_consensus.rs

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ use crate::types::{
1515
ConsensusBlock,
1616
ConsensusContext,
1717
ConsensusError,
18+
Decision,
1819
ProposalInit,
1920
Round,
2021
ValidatorId,
@@ -26,10 +27,7 @@ const ROUND_ZERO: Round = 0;
2627
/// call to `start`, which is relevant if we are the proposer for this height's first round.
2728
/// SingleHeightConsensus receives messages directly as parameters to function calls. It can send
2829
/// out messages "directly" to the network, and returning a decision to the caller.
29-
pub(crate) struct SingleHeightConsensus<BlockT>
30-
where
31-
BlockT: ConsensusBlock,
32-
{
30+
pub(crate) struct SingleHeightConsensus<BlockT: ConsensusBlock> {
3331
height: BlockNumber,
3432
context: Arc<dyn ConsensusContext<Block = BlockT>>,
3533
validators: Vec<ValidatorId>,
@@ -40,10 +38,7 @@ where
4038
precommits: HashMap<(Round, ValidatorId), Vote>,
4139
}
4240

43-
impl<BlockT> SingleHeightConsensus<BlockT>
44-
where
45-
BlockT: ConsensusBlock,
46-
{
41+
impl<BlockT: ConsensusBlock> SingleHeightConsensus<BlockT> {
4742
pub(crate) async fn new(
4843
height: BlockNumber,
4944
context: Arc<dyn ConsensusContext<Block = BlockT>>,
@@ -65,7 +60,7 @@ where
6560
}
6661

6762
#[instrument(skip(self), fields(height=self.height.0), level = "debug")]
68-
pub(crate) async fn start(&mut self) -> Result<Option<BlockT>, ConsensusError> {
63+
pub(crate) async fn start(&mut self) -> Result<Option<Decision<BlockT>>, ConsensusError> {
6964
info!("Starting consensus with validators {:?}", self.validators);
7065
let events = self.state_machine.start();
7166
self.handle_state_machine_events(events).await
@@ -83,7 +78,7 @@ where
8378
init: ProposalInit,
8479
p2p_messages_receiver: mpsc::Receiver<<BlockT as ConsensusBlock>::ProposalChunk>,
8580
fin_receiver: oneshot::Receiver<BlockHash>,
86-
) -> Result<Option<BlockT>, ConsensusError> {
81+
) -> Result<Option<Decision<BlockT>>, ConsensusError> {
8782
debug!(
8883
"Received proposal: proposal_height={}, proposer={:?}",
8984
init.height.0, init.proposer
@@ -137,7 +132,7 @@ where
137132
pub(crate) async fn handle_message(
138133
&mut self,
139134
message: ConsensusMessage,
140-
) -> Result<Option<BlockT>, ConsensusError> {
135+
) -> Result<Option<Decision<BlockT>>, ConsensusError> {
141136
debug!("Received message: {:?}", message);
142137
match message {
143138
ConsensusMessage::Proposal(_) => {
@@ -148,7 +143,10 @@ where
148143
}
149144

150145
#[instrument(skip_all)]
151-
async fn handle_vote(&mut self, vote: Vote) -> Result<Option<BlockT>, ConsensusError> {
146+
async fn handle_vote(
147+
&mut self,
148+
vote: Vote,
149+
) -> Result<Option<Decision<BlockT>>, ConsensusError> {
152150
let (votes, sm_vote) = match vote.vote_type {
153151
VoteType::Prevote => {
154152
(&mut self.prevotes, StateMachineEvent::Prevote(vote.block_hash, ROUND_ZERO))
@@ -180,7 +178,7 @@ where
180178
async fn handle_state_machine_events(
181179
&mut self,
182180
mut events: VecDeque<StateMachineEvent>,
183-
) -> Result<Option<BlockT>, ConsensusError> {
181+
) -> Result<Option<Decision<BlockT>>, ConsensusError> {
184182
while let Some(event) = events.pop_front() {
185183
trace!("Handling event: {:?}", event);
186184
match event {
@@ -194,16 +192,7 @@ where
194192
// sent this out when responding to a StartRound.
195193
}
196194
StateMachineEvent::Decision(block_hash, round) => {
197-
let block = self
198-
.proposals
199-
.remove(&round)
200-
.expect("StateMachine arrived at an unknown decision");
201-
assert_eq!(
202-
block.id(),
203-
block_hash,
204-
"StateMachine block hash should match the stored block"
205-
);
206-
return Ok(Some(block));
195+
return self.handle_state_machine_decision(block_hash, round).await;
207196
}
208197
StateMachineEvent::Prevote(block_hash, round) => {
209198
self.handle_state_machine_vote(block_hash, round, VoteType::Prevote).await?;
@@ -255,12 +244,13 @@ where
255244
self.state_machine.handle_event(StateMachineEvent::StartRound(Some(id), round))
256245
}
257246

247+
#[instrument(skip_all)]
258248
async fn handle_state_machine_vote(
259249
&mut self,
260250
block_hash: BlockHash,
261251
round: Round,
262252
vote_type: VoteType,
263-
) -> Result<Option<BlockT>, ConsensusError> {
253+
) -> Result<Option<Decision<BlockT>>, ConsensusError> {
264254
let votes = match vote_type {
265255
VoteType::Prevote => &mut self.prevotes,
266256
VoteType::Precommit => &mut self.precommits,
@@ -273,4 +263,29 @@ where
273263
self.context.broadcast(ConsensusMessage::Vote(vote)).await?;
274264
Ok(None)
275265
}
266+
267+
#[instrument(skip_all)]
268+
async fn handle_state_machine_decision(
269+
&mut self,
270+
block_hash: BlockHash,
271+
round: Round,
272+
) -> Result<Option<Decision<BlockT>>, ConsensusError> {
273+
let block =
274+
self.proposals.remove(&round).expect("StateMachine arrived at an unknown decision");
275+
assert_eq!(block.id(), block_hash, "StateMachine block hash should match the stored block");
276+
let supporting_precommits: Vec<Vote> = self
277+
.validators
278+
.iter()
279+
.filter_map(|v| {
280+
let vote = self.precommits.get(&(round, *v))?;
281+
if vote.block_hash != block_hash {
282+
return None;
283+
}
284+
Some(vote.clone())
285+
})
286+
.collect();
287+
// TODO(matan): Check actual weights.
288+
assert!(supporting_precommits.len() >= self.state_machine.quorum_size() as usize);
289+
Ok(Some(Decision { precommits: supporting_precommits, block }))
290+
}
276291
}

crates/sequencing/papyrus_consensus/src/single_height_consensus_test.rs

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,23 @@ async fn proposer() {
6363

6464
assert_eq!(shc.handle_message(prevote(block.id(), 0, 2_u32.into())).await, Ok(None));
6565
assert_eq!(shc.handle_message(prevote(block.id(), 0, 3_u32.into())).await, Ok(None));
66-
assert_eq!(shc.handle_message(precommit(block.id(), 0, 2_u32.into())).await, Ok(None));
67-
let decision =
68-
shc.handle_message(precommit(block.id(), 0, 3_u32.into())).await.unwrap().unwrap();
69-
assert_eq!(decision, block);
66+
67+
let precommits = vec![
68+
precommit(block.id(), 0, 1_u32.into()),
69+
precommit(BlockHash(Felt::TWO), 0, 4_u32.into()), // Ignores since disagrees.
70+
precommit(block.id(), 0, 2_u32.into()),
71+
precommit(block.id(), 0, 3_u32.into()),
72+
];
73+
assert_eq!(shc.handle_message(precommits[1].clone()).await, Ok(None));
74+
assert_eq!(shc.handle_message(precommits[2].clone()).await, Ok(None));
75+
let decision = shc.handle_message(precommits[3].clone()).await.unwrap().unwrap();
76+
assert_eq!(decision.block, block);
77+
assert!(
78+
decision
79+
.precommits
80+
.into_iter()
81+
.all(|item| precommits.contains(&ConsensusMessage::Vote(item)))
82+
);
7083

7184
// Check the fin sent to the network.
7285
let fin = Arc::into_inner(fin_receiver).unwrap().take().unwrap().await.unwrap();
@@ -120,9 +133,19 @@ async fn validator() {
120133

121134
assert_eq!(shc.handle_message(prevote(block.id(), 0, 2_u32.into())).await, Ok(None));
122135
assert_eq!(shc.handle_message(prevote(block.id(), 0, 3_u32.into())).await, Ok(None));
123-
assert_eq!(shc.handle_message(precommit(block.id(), 0, 2_u32.into())).await, Ok(None));
124136

125-
let decision =
126-
shc.handle_message(precommit(block.id(), 0, 3_u32.into())).await.unwrap().unwrap();
127-
assert_eq!(decision, block);
137+
let precommits = vec![
138+
precommit(block.id(), 0, 2_u32.into()),
139+
precommit(block.id(), 0, 3_u32.into()),
140+
precommit(block.id(), 0, node_id),
141+
];
142+
assert_eq!(shc.handle_message(precommits[0].clone()).await, Ok(None));
143+
let decision = shc.handle_message(precommits[1].clone()).await.unwrap().unwrap();
144+
assert_eq!(decision.block, block);
145+
assert!(
146+
decision
147+
.precommits
148+
.into_iter()
149+
.all(|item| precommits.contains(&ConsensusMessage::Vote(item)))
150+
);
128151
}

crates/sequencing/papyrus_consensus/src/state_machine.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,10 @@ impl StateMachine {
8484
}
8585
}
8686

87+
pub fn quorum_size(&self) -> u32 {
88+
self.quorum
89+
}
90+
8791
/// Starts the state machine, effectively calling `StartRound(0)` from the paper. This is needed
8892
/// to trigger the first leader to propose. See [`StartRound`](StateMachineEvent::StartRound)
8993
pub fn start(&mut self) -> VecDeque<StateMachineEvent> {

crates/sequencing/papyrus_consensus/src/types.rs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@
22
#[path = "types_test.rs"]
33
mod types_test;
44

5+
use std::fmt::Debug;
6+
57
use async_trait::async_trait;
68
use futures::channel::{mpsc, oneshot};
7-
use papyrus_protobuf::consensus::ConsensusMessage;
9+
use papyrus_protobuf::consensus::{ConsensusMessage, Vote};
810
use starknet_api::block::{BlockHash, BlockNumber};
911
use starknet_api::core::ContractAddress;
1012

@@ -144,6 +146,21 @@ pub trait ConsensusContext: Send + Sync {
144146
) -> Result<(), ConsensusError>;
145147
}
146148

149+
#[derive(PartialEq)]
150+
pub struct Decision<BlockT: ConsensusBlock> {
151+
pub precommits: Vec<Vote>,
152+
pub block: BlockT,
153+
}
154+
155+
impl<BlockT: ConsensusBlock> Debug for Decision<BlockT> {
156+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
157+
f.debug_struct("Decision")
158+
.field("block_id", &self.block.id())
159+
.field("precommits", &self.precommits)
160+
.finish()
161+
}
162+
}
163+
147164
#[derive(PartialEq, Debug, Clone)]
148165
pub struct ProposalInit {
149166
pub height: BlockNumber,

0 commit comments

Comments
 (0)