-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(consensus): create SingleHeightConsensus struct
This involved some updating to the ConsensusBlock trait, mostly to better explain the design. We implemented just the milestone 1 proposal flow for SHC.
- Loading branch information
1 parent
dbbe9e5
commit 9ad368c
Showing
7 changed files
with
271 additions
and
43 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
// TODO: Remove dead code allowance at the end of milestone 1. | ||
#[allow(dead_code)] | ||
pub mod single_height_consensus; | ||
#[allow(dead_code)] | ||
pub mod types; |
69 changes: 69 additions & 0 deletions
69
crates/sequencing/papyrus_consensus/src/single_height_consensus.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
#[cfg(test)] | ||
#[path = "single_height_consensus_test.rs"] | ||
mod single_height_consensus_test; | ||
|
||
use std::sync::Arc; | ||
|
||
use futures::channel::{mpsc, oneshot}; | ||
use futures::SinkExt; | ||
use starknet_api::block::BlockNumber; | ||
|
||
use crate::types::{ | ||
ConsensusBlock, | ||
ConsensusContext, | ||
NodeId, | ||
PeeringConsensusMessage, | ||
ProposalInit, | ||
}; | ||
|
||
pub(crate) struct SingleHeightConsensus<BlockT> | ||
where | ||
BlockT: ConsensusBlock, | ||
{ | ||
height: BlockNumber, | ||
context: Arc<dyn ConsensusContext<Block = BlockT>>, | ||
validators: Vec<NodeId>, | ||
id: NodeId, | ||
to_peering_sender: mpsc::Sender<PeeringConsensusMessage<BlockT::ProposalChunk>>, | ||
from_peering_receiver: mpsc::Receiver<PeeringConsensusMessage<BlockT::ProposalChunk>>, | ||
} | ||
|
||
impl<BlockT> SingleHeightConsensus<BlockT> | ||
where | ||
BlockT: ConsensusBlock, | ||
{ | ||
pub(crate) async fn new( | ||
height: BlockNumber, | ||
context: Arc<dyn ConsensusContext<Block = BlockT>>, | ||
id: NodeId, | ||
to_peering_sender: mpsc::Sender<PeeringConsensusMessage<BlockT::ProposalChunk>>, | ||
from_peering_receiver: mpsc::Receiver<PeeringConsensusMessage<BlockT::ProposalChunk>>, | ||
) -> Self { | ||
let validators = context.validators(height).await; | ||
Self { height, context, validators, id, to_peering_sender, from_peering_receiver } | ||
} | ||
|
||
pub(crate) async fn run(mut self) -> BlockT { | ||
// TODO: In the future this logic will be encapsulated in the state machine, and SHC will | ||
// await a signal from SHC to propose. | ||
let proposer_id = self.context.proposer(&self.validators, self.height); | ||
if proposer_id == self.id { | ||
self.propose().await | ||
} else { | ||
todo!("run as validator"); | ||
} | ||
} | ||
|
||
async fn propose(&mut self) -> BlockT { | ||
let (content_receiver, block_receiver) = self.context.build_proposal(self.height).await; | ||
let (block_hash_sender, block_hash_receiver) = oneshot::channel(); | ||
let init = ProposalInit { height: self.height, proposer: self.id }; | ||
self.to_peering_sender | ||
.send(PeeringConsensusMessage::Proposal((init, content_receiver, block_hash_receiver))) | ||
.await | ||
.expect("failed to send proposal to peering"); | ||
let block = block_receiver.await.expect("failed to build block"); | ||
block_hash_sender.send(block.id()).expect("failed to send block hash"); | ||
block | ||
} | ||
} |
102 changes: 102 additions & 0 deletions
102
crates/sequencing/papyrus_consensus/src/single_height_consensus_test.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,102 @@ | ||
use std::sync::Arc; | ||
|
||
use futures::channel::{mpsc, oneshot}; | ||
use futures::StreamExt; | ||
use starknet_api::block::{BlockHash, BlockNumber}; | ||
use starknet_api::hash::StarkFelt; | ||
use tokio; | ||
|
||
use super::SingleHeightConsensus; | ||
use crate::types::{ | ||
ConsensusBlock, | ||
MockConsensusBlock, | ||
MockTestContext, | ||
NodeId, | ||
PeeringConsensusMessage, | ||
ProposalInit, | ||
}; | ||
|
||
type Shc = SingleHeightConsensus<MockConsensusBlock>; | ||
type ProposalChunk = <MockConsensusBlock as ConsensusBlock>::ProposalChunk; | ||
type PeeringMessage = PeeringConsensusMessage<ProposalChunk>; | ||
|
||
struct TestFields { | ||
pub context: MockTestContext, | ||
pub shc_to_peering_sender: mpsc::Sender<PeeringConsensusMessage<u32>>, | ||
pub shc_to_peering_receiver: mpsc::Receiver<PeeringConsensusMessage<u32>>, | ||
pub peering_to_shc_sender: mpsc::Sender<PeeringConsensusMessage<u32>>, | ||
pub peering_to_shc_receiver: mpsc::Receiver<PeeringConsensusMessage<u32>>, | ||
} | ||
|
||
impl TestFields { | ||
async fn new_shc( | ||
self, | ||
height: BlockNumber, | ||
id: NodeId, | ||
) -> ( | ||
Shc, | ||
mpsc::Receiver<PeeringConsensusMessage<u32>>, | ||
mpsc::Sender<PeeringConsensusMessage<u32>>, | ||
) { | ||
let shc = Shc::new( | ||
height, | ||
Arc::new(self.context), | ||
id, | ||
self.shc_to_peering_sender, | ||
self.peering_to_shc_receiver, | ||
) | ||
.await; | ||
(shc, self.shc_to_peering_receiver, self.peering_to_shc_sender) | ||
} | ||
} | ||
|
||
fn setup() -> TestFields { | ||
let (shc_to_peering_sender, shc_to_peering_receiver) = mpsc::channel(1); | ||
let (peering_to_shc_sender, peering_to_shc_receiver) = mpsc::channel(1); | ||
let context = MockTestContext::new(); | ||
TestFields { | ||
context, | ||
shc_to_peering_sender, | ||
shc_to_peering_receiver, | ||
peering_to_shc_sender, | ||
peering_to_shc_receiver, | ||
} | ||
} | ||
|
||
#[tokio::test] | ||
async fn propose() { | ||
let mut test_fields = setup(); | ||
let node_id: NodeId = 1; | ||
let block_id = BlockHash(StarkFelt::try_from(0 as u128).unwrap()); | ||
// Set expectations for how the test should run: | ||
test_fields.context.expect_validators().returning(move |_| vec![node_id, 2, 3, 4]); | ||
test_fields.context.expect_proposer().returning(move |_, _| node_id); | ||
let block_id_clone = block_id.clone(); | ||
test_fields.context.expect_build_proposal().returning(move |_| { | ||
// SHC doesn't actually handle the content, so ignore for unit tests. | ||
let (_, content_receiver) = mpsc::channel(1); | ||
let (block_sender, block_receiver) = oneshot::channel(); | ||
|
||
// Create the mock block. | ||
let mut block = MockConsensusBlock::new(); | ||
let block_id = block_id_clone.clone(); | ||
block.expect_id().returning(move || block_id.clone()); | ||
|
||
block_sender.send(block).unwrap(); | ||
(content_receiver, block_receiver) | ||
}); | ||
|
||
// Creation calls to `context.validators`. | ||
let (shc, mut shc_to_peering_receiver, _) = test_fields.new_shc(BlockNumber(0), node_id).await; | ||
|
||
// This calls to `context.proposer` and `context.build_proposal`. | ||
let block = shc.run().await; | ||
assert_eq!(block.id(), block_id); | ||
|
||
// Check what was sent to peering. We don't check the content stream as that is filled by | ||
// ConsensusContext, not SHC. | ||
let PeeringMessage::Proposal((init, _, block_hash_receiver)) = | ||
shc_to_peering_receiver.next().await.unwrap(); | ||
assert_eq!(init, ProposalInit { height: BlockNumber(0), proposer: node_id }); | ||
assert_eq!(block_hash_receiver.await.unwrap(), block_id); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,17 +1,10 @@ | ||
use crate::types::{ConsensusBlock, ConsensusContext}; | ||
use crate::types::ConsensusContext; | ||
|
||
// This should cause compilation to fail if the traits are not object safe. | ||
// This should cause compilation to fail if `ConsensusContext` is not object safe. Note that | ||
// `ConsensusBlock` need not be object safe for this to work. | ||
#[test] | ||
fn check_object_safety() { | ||
// Arbitrarily chosen types for testing. | ||
type _ProposalIter = std::slice::Iter<'static, u32>; | ||
type _Blk = Box<dyn ConsensusBlock<ProposalChunk = u32, ProposalIter = _ProposalIter>>; | ||
|
||
fn _check_consensus_block() -> _Blk { | ||
todo!() | ||
} | ||
|
||
fn _check_context() -> Box<dyn ConsensusContext<Block = _Blk>> { | ||
fn _check_context() -> Box<dyn ConsensusContext<Block = ()>> { | ||
todo!() | ||
} | ||
} |