use super::common::MockValidatorState; use crate::simulate::{processed::ProcessedHeight, property::Property, tracker::ProgressTracker}; use commonware_consensus::marshal::core::Variant; use commonware_cryptography::{ed25519, sha256, Digestible}; use std::{future::Future, pin::Pin}; /// Post-run property: all validators agree on the finalized block at `height`. #[derive(Clone, Copy)] pub(crate) struct BlockAgreementAtHeight { height: u64, } impl BlockAgreementAtHeight { pub fn new(height: u64) -> Self { Self { height } } } impl Property> for BlockAgreementAtHeight where V: Variant, V::ApplicationBlock: Digestible, MockValidatorState: Send + Sync, { fn name(&self) -> &str { "block_agreement_at_height" } fn check<'a>( &'a self, _tracker: &'a ProgressTracker, states: &'a [&'a MockValidatorState], ) -> Pin> + Send + 'a>> { Box::pin(async move { let mut expected = None; for state in states { let Some(digest) = state.digest_at_height(self.height).await else { return Err(format!( "missing finalized digest at height {} on at least one validator", self.height )); }; if let Some(previous) = expected { if digest != previous { return Err(format!( "digest disagreement at finalized height {}", self.height )); } } else { expected = Some(digest); } } Ok(()) }) } } /// Post-run property: at least one node used state sync and then advanced further. #[derive(Clone, Copy)] pub(crate) struct LateJoinerStateSyncHandoff; impl Property> for LateJoinerStateSyncHandoff where V: Variant, V::ApplicationBlock: Digestible, MockValidatorState: Send + Sync, { fn name(&self) -> &str { "late_joiner_state_sync_handoff" } fn check<'a>( &'a self, _tracker: &'a ProgressTracker, states: &'a [&'a MockValidatorState], ) -> Pin> + Send + 'a>> { Box::pin(async move { for state in states { let Some(sync_height) = state.state_sync_height() else { continue; }; let processed_height = state.processed_height().await; if processed_height > sync_height { return Ok(()); } } Err( "no validator both used state sync and advanced beyond the synced height" .to_string(), ) }) } } /// Post-run property: a validator started state sync, crashed before it /// completed, restarted, re-entered state sync, then advanced beyond the /// synced height. #[derive(Clone, Copy)] pub(crate) struct CrashDuringStateSyncRecovery; impl Property> for CrashDuringStateSyncRecovery where V: Variant, V::ApplicationBlock: Digestible, MockValidatorState: Send + Sync, { fn name(&self) -> &str { "crash_during_state_sync_recovery" } fn check<'a>( &'a self, _tracker: &'a ProgressTracker, states: &'a [&'a MockValidatorState], ) -> Pin> + Send + 'a>> { Box::pin(async move { let mut observed = Vec::new(); for state in states { let processed_height = state.processed_height().await; observed.push(format!( "entries={} sync_height={:?} processed_height={processed_height}", state.state_sync_entries(), state.state_sync_height(), )); let Some(sync_height) = state.state_sync_height() else { continue; }; if state.state_sync_entries() < 2 { continue; } if processed_height > sync_height { return Ok(()); } } Err( format!( "no validator re-entered state sync after a crash and then advanced beyond the synced height; observed [{}]", observed.join(", "), ), ) }) } }