Skip to content

Commit 650af6d

Browse files
add tests
1 parent 1f7f6f5 commit 650af6d

3 files changed

Lines changed: 137 additions & 0 deletions

File tree

consensus/src/marshal/coding/mod.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,11 @@ mod tests {
259259
harness::finalize_same_height_different_views::<CodingHarness>();
260260
}
261261

262+
#[test_traced("WARN")]
263+
fn test_coding_certify_persists_equivocated_block() {
264+
harness::certify_persists_equivocated_block::<CodingHarness>();
265+
}
266+
262267
/// Regression test for issue #167: finalizing a descendant must not
263268
/// height-prune the shard-engine buffer before `try_repair_gaps` has
264269
/// consumed buffer-only ancestors.

consensus/src/marshal/mocks/harness.rs

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -259,6 +259,13 @@ pub trait TestHarness: 'static + Sized {
259259
all_handles: &mut [ValidatorHandle<Self>],
260260
) -> impl Future<Output = ()> + Send;
261261

262+
/// Mark a block as certified (notarized) via the mailbox.
263+
fn certify(
264+
handle: &mut ValidatorHandle<Self>,
265+
round: Round,
266+
block: &Self::TestBlock,
267+
) -> impl Future<Output = bool> + Send;
268+
262269
/// Create a finalization certificate.
263270
fn make_finalization(
264271
proposal: Proposal<Self::Commitment>,
@@ -927,6 +934,81 @@ pub fn verified_success_implies_recoverable_after_restart<H: TestHarness>(
927934
}
928935
}
929936

937+
/// Regression: when a leader equivocates, a validator may verify one block
938+
/// (A) and then certify a different block (B) at the same round. `verified()`
939+
/// and `certified()` must write to distinct archives so both blocks are
940+
/// retained and retrievable; otherwise the second write collides on the same
941+
/// prunable-archive index (`skip_if_index_exists=true`) and is silently
942+
/// dropped despite the mailbox returning success.
943+
pub fn certify_persists_equivocated_block<H: TestHarness>() {
944+
let runner = deterministic::Runner::timed(Duration::from_secs(60));
945+
runner.start(|mut context| async move {
946+
let Fixture {
947+
participants,
948+
schemes,
949+
..
950+
} = bls12381_threshold_vrf::fixture::<V, _>(&mut context, NAMESPACE, NUM_VALIDATORS);
951+
let mut oracle =
952+
setup_network_with_participants(context.clone(), NZUsize!(1), participants.clone())
953+
.await;
954+
let setup = H::setup_validator(
955+
context.with_label("validator_0"),
956+
&mut oracle,
957+
participants[0].clone(),
958+
ConstantProvider::new(schemes[0].clone()),
959+
)
960+
.await;
961+
let mut handle = ValidatorHandle::<H> {
962+
mailbox: setup.mailbox,
963+
extra: setup.extra,
964+
};
965+
966+
let round = Round::new(Epoch::zero(), View::new(1));
967+
let parent = Sha256::hash(b"");
968+
let parent_commitment = H::genesis_parent_commitment(NUM_VALIDATORS as u16);
969+
970+
// Two distinct blocks at the same height/round (leader equivocation):
971+
// distinct timestamps yield distinct digests.
972+
let block_a = H::make_test_block(
973+
parent,
974+
parent_commitment,
975+
Height::new(1),
976+
1,
977+
NUM_VALIDATORS as u16,
978+
);
979+
let digest_a = H::digest(&block_a);
980+
let block_b = H::make_test_block(
981+
parent,
982+
parent_commitment,
983+
Height::new(1),
984+
2,
985+
NUM_VALIDATORS as u16,
986+
);
987+
let digest_b = H::digest(&block_b);
988+
assert_ne!(digest_a, digest_b, "test requires distinct digests");
989+
990+
let mut peers: [ValidatorHandle<H>; 0] = [];
991+
H::verify(&mut handle, round, &block_a, &mut peers).await;
992+
assert!(
993+
H::certify(&mut handle, round, &block_b).await,
994+
"certified must ack"
995+
);
996+
997+
let got_a = handle.mailbox.get_block(&digest_a).await;
998+
assert!(
999+
got_a.is_some(),
1000+
"verified block A must be persisted in verified_blocks"
1001+
);
1002+
assert_eq!(got_a.unwrap().digest(), digest_a);
1003+
let got_b = handle.mailbox.get_block(&digest_b).await;
1004+
assert!(
1005+
got_b.is_some(),
1006+
"certified block B must be persisted despite a verify at the same round"
1007+
);
1008+
assert_eq!(got_b.unwrap().digest(), digest_b);
1009+
});
1010+
}
1011+
9301012
/// Contract: once marshal has delivered a finalized block to the application,
9311013
/// that finalized block and its certificate must already be durable.
9321014
pub fn delivery_visibility_implies_recoverable_after_restart<H: TestHarness>(
@@ -1275,6 +1357,10 @@ impl TestHarness for StandardHarness {
12751357
assert!(handle.mailbox.verified(round, block.clone()).await);
12761358
}
12771359

1360+
async fn certify(handle: &mut ValidatorHandle<Self>, round: Round, block: &B) -> bool {
1361+
handle.mailbox.certified(round, block.clone()).await
1362+
}
1363+
12781364
fn make_finalization(proposal: Proposal<D>, schemes: &[S], quorum: u32) -> Finalization<S, D> {
12791365
let finalizes: Vec<_> = schemes
12801366
.iter()
@@ -1536,6 +1622,22 @@ impl TestHarness for InlineHarness {
15361622
.await;
15371623
}
15381624

1625+
async fn certify(
1626+
handle: &mut ValidatorHandle<Self>,
1627+
round: Round,
1628+
block: &Self::TestBlock,
1629+
) -> bool {
1630+
StandardHarness::certify(
1631+
&mut ValidatorHandle::<StandardHarness> {
1632+
mailbox: handle.mailbox.clone(),
1633+
extra: handle.extra.clone(),
1634+
},
1635+
round,
1636+
block,
1637+
)
1638+
.await
1639+
}
1640+
15391641
fn make_finalization(
15401642
proposal: Proposal<Self::Commitment>,
15411643
schemes: &[S],
@@ -1724,6 +1826,22 @@ impl TestHarness for DeferredHarness {
17241826
.await;
17251827
}
17261828

1829+
async fn certify(
1830+
handle: &mut ValidatorHandle<Self>,
1831+
round: Round,
1832+
block: &Self::TestBlock,
1833+
) -> bool {
1834+
InlineHarness::certify(
1835+
&mut ValidatorHandle::<InlineHarness> {
1836+
mailbox: handle.mailbox.clone(),
1837+
extra: handle.extra.clone(),
1838+
},
1839+
round,
1840+
block,
1841+
)
1842+
.await
1843+
}
1844+
17271845
fn make_finalization(
17281846
proposal: Proposal<Self::Commitment>,
17291847
schemes: &[S],
@@ -2063,6 +2181,14 @@ impl TestHarness for CodingHarness {
20632181
assert!(handle.mailbox.verified(round, block.clone()).await);
20642182
}
20652183

2184+
async fn certify(
2185+
handle: &mut ValidatorHandle<Self>,
2186+
round: Round,
2187+
block: &CodedBlock<CodingB, ReedSolomon<Sha256>, Sha256>,
2188+
) -> bool {
2189+
handle.mailbox.certified(round, block.clone()).await
2190+
}
2191+
20662192
fn make_finalization(
20672193
proposal: Proposal<Commitment>,
20682194
schemes: &[S],

consensus/src/marshal/standard/mod.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,12 @@ mod tests {
201201
harness::verified_success_implies_recoverable_after_restart::<DeferredHarness>(0..16);
202202
}
203203

204+
#[test_traced("WARN")]
205+
fn test_standard_certify_persists_equivocated_block() {
206+
harness::certify_persists_equivocated_block::<InlineHarness>();
207+
harness::certify_persists_equivocated_block::<DeferredHarness>();
208+
}
209+
204210

205211
#[test_traced("WARN")]
206212
fn test_standard_delivery_visibility_implies_recoverable_after_restart() {

0 commit comments

Comments
 (0)