This commit is contained in:
Giacomo Pasini 2024-03-12 12:59:22 +01:00
parent a237b05d25
commit c4a0af34ad
No known key found for this signature in database
GPG Key ID: FC08489D2D895D4B
13 changed files with 94 additions and 27 deletions

View File

@ -1,3 +1,10 @@
mod ref_state;
pub mod sut;
mod transition;
type Block = carnot_engine::Block<[u8; 32]>;
type AggregateQc = carnot_engine::AggregateQc<[u8; 32]>;
type Qc = carnot_engine::Qc<[u8; 32]>;
type StandardQc = carnot_engine::StandardQc<[u8; 32]>;
type TimeoutQc = carnot_engine::TimeoutQc<[u8; 32]>;
type NewView = carnot_engine::NewView<[u8; 32]>;

View File

@ -1,13 +1,12 @@
use std::collections::{BTreeMap, HashSet};
use carnot_engine::{
AggregateQc, Block, BlockId, LeaderProof, NodeId, Qc, StandardQc, TimeoutQc, View,
};
use carnot_engine::{LeaderProof, NodeId, View};
use proptest::prelude::*;
use proptest::strategy::BoxedStrategy;
use proptest_state_machine::ReferenceStateMachine;
use crate::fuzz::transition::Transition;
use crate::fuzz::{AggregateQc, Block, Qc, StandardQc, TimeoutQc};
// A reference state machine (RefState) is used to generated state transitions.
// To generate some kinds of transition, we may need to keep historical blocks in RefState.
@ -42,8 +41,8 @@ impl ReferenceStateMachine for RefState {
fn init_state() -> BoxedStrategy<Self::State> {
let genesis_block = Block {
view: View::new(0),
id: BlockId::zeros(),
parent_qc: Qc::Standard(StandardQc::genesis()),
id: [0; 32],
parent_qc: Qc::Standard(StandardQc::genesis([0; 32])),
leader_proof: LEADER_PROOF.clone(),
};
@ -330,10 +329,11 @@ impl RefState {
fn transition_receive_safe_block_with_aggregated_qc(&self) -> BoxedStrategy<Transition> {
//TODO: more randomness
let current_view = self.current_view();
let mut id = [0; 32];
rand::thread_rng().fill_bytes(&mut id);
Just(Transition::ReceiveSafeBlock(Block {
view: current_view.next(),
id: BlockId::random(&mut rand::thread_rng()),
id,
parent_qc: Qc::Aggregated(AggregateQc {
high_qc: self.high_qc(),
view: current_view,
@ -360,9 +360,13 @@ impl RefState {
pub fn high_qc(&self) -> StandardQc {
self.chain
.values()
.map(|entry| entry.high_qc().unwrap_or_else(StandardQc::genesis))
.map(|entry| {
entry
.high_qc()
.unwrap_or_else(|| StandardQc::genesis([0; 32]))
})
.max_by_key(|qc| qc.view)
.unwrap_or_else(StandardQc::genesis)
.unwrap_or_else(|| StandardQc::genesis([0; 32]))
}
pub fn latest_timeout_qcs(&self) -> Vec<TimeoutQc> {
@ -386,17 +390,19 @@ impl RefState {
self.contains_block(block.parent_qc.block())
}
fn contains_block(&self, block_id: BlockId) -> bool {
fn contains_block(&self, block_id: [u8; 32]) -> bool {
self.chain
.iter()
.any(|(_, entry)| entry.blocks.iter().any(|block| block.id == block_id))
}
fn consecutive_block(parent: &Block) -> Block {
let mut id = [0; 32];
rand::thread_rng().fill_bytes(&mut id);
Block {
// use rand because we don't want this to be shrinked by proptest
view: parent.view.next(),
id: BlockId::random(&mut rand::thread_rng()),
id,
parent_qc: Qc::Standard(StandardQc {
view: parent.view,
id: parent.id,

View File

@ -8,13 +8,13 @@ use carnot_engine::{
use proptest_state_machine::{ReferenceStateMachine, StateMachineTest};
use crate::fuzz::ref_state::RefState;
use crate::fuzz::transition::Transition;
use crate::fuzz::{transition::Transition, Block};
// ConsensusEngineTest defines a state that we want to test.
// This is called as SUT (System Under Test).
#[derive(Clone, Debug)]
pub struct ConsensusEngineTest {
pub engine: Carnot<FlatOverlay<RoundRobin, FreezeMembership>>,
pub engine: Carnot<FlatOverlay<RoundRobin, FreezeMembership>, [u8; 32]>,
}
impl ConsensusEngineTest {
@ -23,8 +23,8 @@ impl ConsensusEngineTest {
NodeId::new([0; 32]),
Block {
view: View::new(0),
id: BlockId::zeros(),
parent_qc: Qc::Standard(StandardQc::genesis()),
id: [0; 32],
parent_qc: Qc::Standard(StandardQc::genesis([0; 32])),
leader_proof: LeaderProof::LeaderId {
leader_id: NodeId::new([0; 32]),
},

View File

@ -1,6 +1,6 @@
use std::collections::HashSet;
use carnot_engine::{Block, NewView, TimeoutQc};
use crate::fuzz::{Block, NewView, TimeoutQc};
// State transtitions that will be picked randomly
#[derive(Clone, Debug)]

View File

@ -22,6 +22,7 @@ thiserror = "1.0"
bincode = "1.3"
once_cell = "1.0"
indexmap = { version = "1.9", features = ["serde"] }
const-hex = "1"
[dev-dependencies]
rand = "0.8"

View File

@ -1,14 +1,14 @@
use serde::{Deserialize, Serialize};
use crate::utils::display_hex_bytes_newtype;
use crate::utils::{display_hex_bytes_newtype, serde_bytes_newtype};
pub mod carnot;
pub mod cryptarchia;
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash, Serialize, Deserialize, PartialOrd, Ord)]
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash, PartialOrd, Ord)]
pub struct HeaderId([u8; 32]);
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash, Serialize, Deserialize)]
#[derive(Clone, Debug, Eq, PartialEq, Copy, Hash)]
pub struct ContentId([u8; 32]);
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
@ -73,3 +73,15 @@ impl From<ContentId> for [u8; 32] {
display_hex_bytes_newtype!(HeaderId);
display_hex_bytes_newtype!(ContentId);
serde_bytes_newtype!(HeaderId, 32);
serde_bytes_newtype!(ContentId, 32);
#[test]
fn test_serde() {
assert_eq!(
crate::wire::deserialize::<HeaderId>(&crate::wire::serialize(&HeaderId([0; 32])).unwrap())
.unwrap(),
HeaderId([0; 32])
);
}

View File

@ -14,4 +14,40 @@ macro_rules! display_hex_bytes_newtype {
};
}
macro_rules! serde_bytes_newtype {
($newtype:ty, $len:expr) => {
impl serde::Serialize for $newtype {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
if serializer.is_human_readable() {
const_hex::const_encode::<$len, false>(&self.0)
.as_str()
.serialize(serializer)
} else {
self.0.serialize(serializer)
}
}
}
impl<'de> serde::Deserialize<'de> for $newtype {
fn deserialize<D>(deserializer: D) -> Result<$newtype, D::Error>
where
D: serde::Deserializer<'de>,
{
if deserializer.is_human_readable() {
let s = <&str>::deserialize(deserializer)?;
const_hex::decode_to_array(s)
.map(Self)
.map_err(serde::de::Error::custom)
} else {
<[u8; $len]>::deserialize(deserializer).map(Self)
}
}
}
};
}
pub(crate) use display_hex_bytes_newtype;
pub(crate) use serde_bytes_newtype;

View File

@ -306,6 +306,7 @@ where
);
if carnot.is_next_leader() {
tracing::info!("is next leader, gathering vores");
let network_adapter = adapter.clone();
task_manager.push(genesis_block.view.next(), async move {
let Event::Approve { qc, .. } = Self::gather_votes(
@ -319,6 +320,7 @@ where
tracing::debug!("Failed to gather initial votes");
return Event::None;
};
tracing::info!("got enough votes");
Event::ProposeBlock { qc }
});
}

View File

@ -264,7 +264,7 @@ impl NetworkAdapter for Libp2pAdapter {
}
}
NetworkMessage::Vote(msg) => {
tracing::debug!("received vote");
tracing::debug!("received vote {:?}", msg);
let mut cache = cache.cache.lock().unwrap();
let view = msg.vote.view;
if let Some(messages) = cache.get_mut(&view) {

View File

@ -86,7 +86,7 @@ impl TimeoutQcMsg {
}
}
#[derive(Serialize, Deserialize)]
#[derive(Debug, Serialize, Deserialize)]
pub enum NetworkMessage {
Timeout(TimeoutMsg),
TimeoutQc(TimeoutQcMsg),

View File

@ -84,7 +84,6 @@ impl Tally for CarnotTally {
));
}
}
Err(CarnotTallyError::StreamEnded)
}
}

View File

@ -1,5 +1,5 @@
use nomos_core::{
block::BlockId,
header::HeaderId,
tx::mock::{MockTransaction, MockTxId},
};
use nomos_log::{Logger, LoggerSettings};
@ -23,7 +23,7 @@ struct MockPoolNode {
mockpool: ServiceHandle<
MempoolService<
MockAdapter,
MockPool<BlockId, MockTransaction<MockMessage>, MockTxId>,
MockPool<HeaderId, MockTransaction<MockMessage>, MockTxId>,
Transaction,
>,
>,
@ -80,7 +80,7 @@ fn test_mockmempool() {
let network = app.handle().relay::<NetworkService<Mock>>();
let mempool = app.handle().relay::<MempoolService<
MockAdapter,
MockPool<BlockId, MockTransaction<MockMessage>, MockTxId>,
MockPool<HeaderId, MockTransaction<MockMessage>, MockTxId>,
Transaction,
>>();
@ -102,7 +102,7 @@ fn test_mockmempool() {
let (mtx, mrx) = tokio::sync::oneshot::channel();
mempool_outbound
.send(MempoolMsg::View {
ancestor_hint: BlockId::default(),
ancestor_hint: [0; 32].into(),
reply_channel: mtx,
})
.await

View File

@ -1,13 +1,17 @@
use carnot_consensus::CarnotInfo;
use carnot_engine::{Block, NodeId, TimeoutQc, View};
use carnot_engine::{NodeId, View};
use fraction::Fraction;
use futures::stream::{self, StreamExt};
use nomos_core::header::HeaderId;
use std::{collections::HashSet, time::Duration};
use tests::{adjust_timeout, ConsensusConfig, MixNode, Node, NomosNode, SpawnConfig};
const TARGET_VIEW: View = View::new(20);
const DUMMY_NODE_ID: NodeId = NodeId::new([0u8; 32]);
type Block = carnot_engine::Block<HeaderId>;
type TimeoutQc = carnot_engine::TimeoutQc<HeaderId>;
#[tokio::test]
async fn ten_nodes_one_down() {
let (_mixnodes, mixnet_config) = MixNode::spawn_nodes(3).await;