Skip to content

Commit

Permalink
Add marketplace integration test (#3569)
Browse files Browse the repository at this point in the history
  • Loading branch information
ss-es authored Aug 16, 2024
1 parent 43a4a1c commit e87fc54
Show file tree
Hide file tree
Showing 14 changed files with 150 additions and 92 deletions.
4 changes: 3 additions & 1 deletion crates/example-types/src/block_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ use time::OffsetDateTime;
use vbs::version::Version;

use crate::{
auction_results_provider_types::TestAuctionResult,
node_types::TestTypes,
state_types::{TestInstanceState, TestValidatedState},
testable_delay::{DelayConfig, SupportedTraitTypesForAsyncDelay, TestableDelay},
Expand Down Expand Up @@ -274,6 +275,7 @@ impl<
BlockHeader = Self,
BlockPayload = TestBlockPayload,
InstanceState = TestInstanceState,
AuctionResult = TestAuctionResult,
>,
> BlockHeader<TYPES> for TestBlockHeader
{
Expand Down Expand Up @@ -349,7 +351,7 @@ impl<
}

fn get_auction_results(&self) -> Option<TYPES::AuctionResult> {
unimplemented!()
Some(TYPES::AuctionResult { urls: vec![] })
}
}

Expand Down
14 changes: 14 additions & 0 deletions crates/example-types/src/node_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -146,3 +146,17 @@ impl Versions for TestVersions {

type Marketplace = StaticVersion<0, 3>;
}

#[derive(Clone, Debug, Copy)]
pub struct MarketplaceUpgradeTestVersions {}

impl Versions for MarketplaceUpgradeTestVersions {
type Base = StaticVersion<0, 2>;
type Upgrade = StaticVersion<0, 3>;
const UPGRADE_HASH: [u8; 32] = [
1, 0, 1, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0,
0, 0,
];

type Marketplace = StaticVersion<0, 3>;
}
3 changes: 0 additions & 3 deletions crates/task-impls/src/consensus/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,9 +550,6 @@ pub async fn handle_quorum_proposal_validated<
.await;
*decided_certificate_lock = Some(cert.clone());
drop(decided_certificate_lock);
let _ = event_stream
.broadcast(Arc::new(HotShotEvent::UpgradeDecided(cert.clone())))
.await;
}

let mut consensus = task_state.consensus.write().await;
Expand Down
5 changes: 0 additions & 5 deletions crates/task-impls/src/events.rs
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,6 @@ pub enum HotShotEvent<TYPES: NodeType> {
UpgradeVoteSend(UpgradeVote<TYPES>),
/// Upgrade certificate has been sent to the network
UpgradeCertificateFormed(UpgradeCertificate<TYPES>),
/// A HotShot upgrade was decided
UpgradeDecided(UpgradeCertificate<TYPES>),

/* Consensus State Update Events */
/// A undecided view has been created and added to the validated state storage.
Expand Down Expand Up @@ -421,9 +419,6 @@ impl<TYPES: NodeType> Display for HotShotEvent<TYPES> {
"UpgradeCertificateFormed(view_number={:?})",
cert.view_number()
),
HotShotEvent::UpgradeDecided(cert) => {
write!(f, "UpgradeDecided(view_number{:?})", cert.view_number())
}
HotShotEvent::QuorumProposalRequest(view_number) => {
write!(f, "QuorumProposalRequest(view_number={view_number:?})")
}
Expand Down
8 changes: 1 addition & 7 deletions crates/task-impls/src/network.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@ pub fn quorum_filter<TYPES: NodeType>(event: &Arc<HotShotEvent<TYPES>>) -> bool
| HotShotEvent::QuorumVoteSend(_)
| HotShotEvent::DacSend(_, _)
| HotShotEvent::TimeoutVoteSend(_)
| HotShotEvent::UpgradeDecided(_)
| HotShotEvent::ViewChange(_)
)
}
Expand All @@ -53,7 +52,6 @@ pub fn upgrade_filter<TYPES: NodeType>(event: &Arc<HotShotEvent<TYPES>>) -> bool
event.as_ref(),
HotShotEvent::UpgradeProposalSend(_, _)
| HotShotEvent::UpgradeVoteSend(_)
| HotShotEvent::UpgradeDecided(_)
| HotShotEvent::ViewChange(_)
)
}
Expand All @@ -64,7 +62,6 @@ pub fn da_filter<TYPES: NodeType>(event: &Arc<HotShotEvent<TYPES>>) -> bool {
event.as_ref(),
HotShotEvent::DaProposalSend(_, _)
| HotShotEvent::DaVoteSend(_)
| HotShotEvent::UpgradeDecided(_)
| HotShotEvent::ViewChange(_)
)
}
Expand All @@ -73,9 +70,7 @@ pub fn da_filter<TYPES: NodeType>(event: &Arc<HotShotEvent<TYPES>>) -> bool {
pub fn vid_filter<TYPES: NodeType>(event: &Arc<HotShotEvent<TYPES>>) -> bool {
!matches!(
event.as_ref(),
HotShotEvent::VidDisperseSend(_, _)
| HotShotEvent::UpgradeDecided(_)
| HotShotEvent::ViewChange(_)
HotShotEvent::VidDisperseSend(_, _) | HotShotEvent::ViewChange(_)
)
}

Expand All @@ -89,7 +84,6 @@ pub fn view_sync_filter<TYPES: NodeType>(event: &Arc<HotShotEvent<TYPES>>) -> bo
| HotShotEvent::ViewSyncPreCommitVoteSend(_)
| HotShotEvent::ViewSyncCommitVoteSend(_)
| HotShotEvent::ViewSyncFinalizeVoteSend(_)
| HotShotEvent::UpgradeDecided(_)
| HotShotEvent::ViewChange(_)
)
}
Expand Down
3 changes: 0 additions & 3 deletions crates/task-impls/src/quorum_vote/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,6 @@ pub(crate) async fn handle_quorum_proposal_validated<
.await;
*decided_certificate_lock = Some(cert.clone());
drop(decided_certificate_lock);
let _ = sender
.broadcast(Arc::new(HotShotEvent::UpgradeDecided(cert.clone())))
.await;
}

let mut consensus_writer = task_state.consensus.write().await;
Expand Down
138 changes: 82 additions & 56 deletions crates/testing/src/consistency_task.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,18 +21,14 @@ use crate::{
test_task::{TestResult, TestTaskState},
};

trait Validatable {
fn valid(&self) -> Result<()>;
}

/// Map from views to leaves for a single node, allowing multiple leaves for each view (because the node may a priori send us multiple leaves for a given view).
pub type NodeMap<TYPES> = BTreeMap<<TYPES as NodeType>::Time, Vec<Leaf<TYPES>>>;

/// A sanitized map from views to leaves for a single node, with only a single leaf per view.
pub type NodeMapSanitized<TYPES> = BTreeMap<<TYPES as NodeType>::Time, Leaf<TYPES>>;

/// Validate that the `NodeMap` only has a single leaf per view.
pub fn sanitize_node_map<TYPES: NodeType>(
fn sanitize_node_map<TYPES: NodeType>(
node_map: &NodeMap<TYPES>,
) -> Result<NodeMapSanitized<TYPES>> {
let mut result = BTreeMap::new();
Expand All @@ -58,27 +54,25 @@ pub fn sanitize_node_map<TYPES: NodeType>(
Ok(result)
}

/// For a NodeLeafMap, we validate that each leaf extends the preceding leaf.
impl<TYPES: NodeType> Validatable for NodeMapSanitized<TYPES> {
fn valid(&self) -> Result<()> {
let leaf_pairs = self.values().zip(self.values().skip(1));
/// For a NodeMapSanitized, we validate that each leaf extends the preceding leaf.
fn validate_node_map<TYPES: NodeType>(node_map: &NodeMapSanitized<TYPES>) -> Result<()> {
let leaf_pairs = node_map.values().zip(node_map.values().skip(1));

// Check that the child leaf follows the parent, possibly with a gap.
for (parent, child) in leaf_pairs {
ensure!(
// Check that the child leaf follows the parent, possibly with a gap.
for (parent, child) in leaf_pairs {
ensure!(
child.justify_qc().view_number >= parent.view_number(),
"The node has provided leaf:\n\n{child:?}\n\nbut its quorum certificate points to a view before the most recent leaf:\n\n{parent:?}"
);

if child.justify_qc().view_number == parent.view_number()
&& child.justify_qc().data.leaf_commit != parent.commit()
{
bail!("The node has provided leaf:\n\n{child:?}\n\nwhich points to:\n\n{parent:?}\n\nbut the commits do not match.");
}
if child.justify_qc().view_number == parent.view_number()
&& child.justify_qc().data.leaf_commit != parent.commit()
{
bail!("The node has provided leaf:\n\n{child:?}\n\nwhich points to:\n\n{parent:?}\n\nbut the commits do not match.");
}

Ok(())
}

Ok(())
}

/// A map from node ids to `NodeMap`s; note that the latter may have multiple leaves per view in principle.
Expand All @@ -88,7 +82,7 @@ pub type NetworkMap<TYPES> = BTreeMap<usize, NodeMap<TYPES>>;
pub type NetworkMapSanitized<TYPES> = BTreeMap<usize, NodeMapSanitized<TYPES>>;

/// Validate that each node has only produced one unique leaf per view, and produce a `NetworkMapSanitized`.
pub fn sanitize_network_map<TYPES: NodeType>(
fn sanitize_network_map<TYPES: NodeType>(
network_map: &NetworkMap<TYPES>,
) -> Result<NetworkMapSanitized<TYPES>> {
let mut result = BTreeMap::new();
Expand All @@ -104,49 +98,58 @@ pub fn sanitize_network_map<TYPES: NodeType>(
Ok(result)
}

impl<TYPES: NodeType> Validatable for NetworkMap<TYPES> {
fn valid(&self) -> Result<()> {
let sanitized = sanitize_network_map(self)?;

sanitized.valid()
pub type ViewMap<TYPES> = BTreeMap<<TYPES as NodeType>::Time, BTreeMap<usize, Leaf<TYPES>>>;

// Invert the network map by interchanging the roles of the node_id and view number.
//
// # Errors
//
// Returns an error if any node map is invalid.
fn invert_network_map<TYPES: NodeType>(
network_map: &NetworkMapSanitized<TYPES>,
) -> Result<ViewMap<TYPES>> {
let mut inverted_map = BTreeMap::new();
for (node_id, node_map) in network_map.iter() {
validate_node_map(node_map)
.context(format!("Node {node_id} has an invalid leaf history"))?;

// validate each node's leaf map
for (view, leaf) in node_map.iter() {
let view_map = inverted_map.entry(*view).or_insert(BTreeMap::new());
view_map.insert(*node_id, leaf.clone());
}
}

Ok(inverted_map)
}

/// For a NetworkLeafMap, we validate that no two nodes have submitted differing leaves for any given view, in addition to the individual NodeLeafMap checks.
impl<TYPES: NodeType> Validatable for NetworkMapSanitized<TYPES> {
fn valid(&self) -> Result<()> {
// Invert the map by interchanging the roles of the node_id and view number.
let mut inverted_map = BTreeMap::new();
for (node_id, node_map) in self.iter() {
node_map
.valid()
.context(format!("Node {node_id} has an invalid leaf history"))?;

// validate each node's leaf map
for (view, leaf) in node_map.iter() {
let view_map = inverted_map.entry(*view).or_insert(BTreeMap::new());
view_map.insert(*node_id, leaf.clone());
}
}
/// A view map, sanitized to have exactly one leaf per view.
pub type ViewMapSanitized<TYPES> = BTreeMap<<TYPES as NodeType>::Time, Leaf<TYPES>>;

for (view, view_map) in inverted_map.iter() {
let mut leaves: Vec<_> = view_map.iter().collect();
fn sanitize_view_map<TYPES: NodeType>(
view_map: &ViewMap<TYPES>,
) -> Result<ViewMapSanitized<TYPES>> {
let mut result = BTreeMap::new();

leaves.dedup_by(|(_node_a, leaf_a), (_node_b, leaf_b)| leaf_a == leaf_b);
for (view, leaf_map) in view_map.iter() {
let mut node_leaves: Vec<_> = leaf_map.iter().collect();

ensure!(
leaves.len() <= 1,
view_map.iter().fold(
format!("The network does not agree on view {view:?}."),
|acc, (node, leaf)| {
format!("{acc}\n\nNode {node} sent us leaf:\n\n{leaf:?}")
}
)
);
}
node_leaves.dedup_by(|(_node_a, leaf_a), (_node_b, leaf_b)| leaf_a == leaf_b);

Ok(())
ensure!(
node_leaves.len() <= 1,
leaf_map.iter().fold(
format!("The network does not agree on view {view:?}."),
|acc, (node, leaf)| { format!("{acc}\n\nNode {node} sent us leaf:\n\n{leaf:?}") }
)
);

if let Some(leaf) = node_leaves.first() {
result.insert(*view, leaf.1.clone());
}
}

Ok(result)
}

/// Data availability task state
Expand All @@ -155,6 +158,29 @@ pub struct ConsistencyTask<TYPES: NodeType> {
pub consensus_leaves: NetworkMap<TYPES>,
/// safety task requirements
pub safety_properties: OverallSafetyPropertiesDescription<TYPES>,
/// whether we should have seen an upgrade certificate or not
pub ensure_upgrade: bool,
}

impl<TYPES: NodeType> ConsistencyTask<TYPES> {
pub fn validate(&self) -> Result<()> {
let sanitized_network_map = sanitize_network_map(&self.consensus_leaves)?;

let inverted_map = invert_network_map(&sanitized_network_map)?;

let sanitized_view_map = sanitize_view_map(&inverted_map)?;

let expected_upgrade = self.ensure_upgrade;
let actual_upgrade = sanitized_view_map.iter().fold(false, |acc, (_view, leaf)| {
acc || leaf.upgrade_certificate().is_some()
});

ensure!(expected_upgrade == actual_upgrade,
"Mismatch between expected and actual upgrade. Expected upgrade: {expected_upgrade}. Actual upgrade: {actual_upgrade}"
);

Ok(())
}
}

#[async_trait]
Expand All @@ -181,7 +207,7 @@ impl<TYPES: NodeType> TestTaskState for ConsistencyTask<TYPES> {
}

fn check(&self) -> TestResult {
if let Err(e) = self.consensus_leaves.valid() {
if let Err(e) = self.validate() {
return TestResult::Fail(Box::new(e));
}

Expand Down
11 changes: 0 additions & 11 deletions crates/testing/src/predicates/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,17 +147,6 @@ where
Box::new(EventPredicate { check, info })
}

pub fn upgrade_decided<TYPES>() -> Box<EventPredicate<TYPES>>
where
TYPES: NodeType,
{
let info = "UpgradeDecided".to_string();
let check: EventCallback<TYPES> =
Arc::new(move |e: Arc<HotShotEvent<TYPES>>| matches!(e.as_ref(), UpgradeDecided(_)));

Box::new(EventPredicate { check, info })
}

pub fn quorum_vote_send<TYPES>() -> Box<EventPredicate<TYPES>>
where
TYPES: NodeType,
Expand Down
5 changes: 4 additions & 1 deletion crates/testing/src/test_builder.rs
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ pub struct TestDescription<TYPES: NodeType, I: NodeImplementation<TYPES>, V: Ver
pub behaviour: Rc<dyn Fn(u64) -> Behaviour<TYPES, I, V>>,
/// Delay config if any to add delays to asynchronous calls
pub async_delay_config: DelayConfig,
/// view in which to propose an upgrade
pub upgrade_view: Option<u64>,
}

#[derive(Debug)]
Expand Down Expand Up @@ -377,6 +379,7 @@ impl<TYPES: NodeType, I: NodeImplementation<TYPES>, V: Versions> Default
},
behaviour: Rc::new(|_| Behaviour::Standard),
async_delay_config: DelayConfig::default(),
upgrade_view: None,
}
}
}
Expand Down Expand Up @@ -509,7 +512,7 @@ where
config,
marketplace_config: Box::new(|_| MarketplaceConfig::<TYPES, I> {
auction_results_provider: TestAuctionResultsProvider::<TYPES>::default().into(),
fallback_builder_url: Url::parse("http://localhost").unwrap(),
fallback_builder_url: Url::parse("http://localhost:9999").unwrap(),
}),
},
metadata: self,
Expand Down
Loading

0 comments on commit e87fc54

Please sign in to comment.