Skip to content
Merged
Show file tree
Hide file tree
Changes from 109 commits
Commits
Show all changes
111 commits
Select commit Hold shift + click to select a range
6294103
[consensus/simplex] Prevent certification of our own proposals
clabby Apr 6, 2026
131aaed
cover restart recovery case
clabby Apr 6, 2026
b8ceef0
comment
clabby Apr 6, 2026
37fe7e6
don't inherit locally proposed status for conflicting certs
clabby Apr 6, 2026
0190025
regress follower replay local marker
clabby Apr 7, 2026
b66224f
mark proposed_locally by leadership
clabby Apr 7, 2026
9590313
update `test_only_finalization_rescues_validator`
clabby Apr 7, 2026
1f90102
update remaining tests
clabby Apr 7, 2026
aaecc6e
remove `Certifier::Sometimes`
clabby Apr 7, 2026
fc71931
remove addtional state tracking until we have a better soln
clabby Apr 7, 2026
e78e4dd
Merge remote-tracking branch 'origin/main' into pr/3543
patrick-ogrady Apr 13, 2026
31cdab5
revert to sometimes
patrick-ogrady Apr 13, 2026
a18a0b8
remove duplicate
patrick-ogrady Apr 13, 2026
2008ca7
progress
patrick-ogrady Apr 14, 2026
34e1f1a
Merge remote-tracking branch 'origin/main' into pr/3543
patrick-ogrady Apr 14, 2026
0095e38
fix flaky test
patrick-ogrady Apr 14, 2026
98cc1c7
fmt
patrick-ogrady Apr 14, 2026
9705ef0
nits
patrick-ogrady Apr 14, 2026
eca1198
fix running
patrick-ogrady Apr 14, 2026
7c6b409
nit
patrick-ogrady Apr 14, 2026
1ac6d3f
nits
patrick-ogrady Apr 14, 2026
a4cd3b8
nit
patrick-ogrady Apr 14, 2026
c006297
nit
patrick-ogrady Apr 14, 2026
ab3b402
nit
patrick-ogrady Apr 14, 2026
02e0070
fix lint
patrick-ogrady Apr 14, 2026
2bf14e0
nits
patrick-ogrady Apr 14, 2026
33e146c
nit
patrick-ogrady Apr 14, 2026
e537338
increase coverage
patrick-ogrady Apr 15, 2026
8d88a95
add more coverage
patrick-ogrady Apr 15, 2026
7c68588
Merge remote-tracking branch 'origin/main' into pr/3543
patrick-ogrady Apr 15, 2026
cb55e8a
push
patrick-ogrady Apr 15, 2026
19d0db1
cover last_finalized
patrick-ogrady Apr 15, 2026
15ed6fd
fmt
patrick-ogrady Apr 15, 2026
714ece1
spike on comments
patrick-ogrady Apr 15, 2026
6953ebb
spike on reliable storage
patrick-ogrady Apr 16, 2026
abb69a2
more tests
patrick-ogrady Apr 16, 2026
962507b
spike
patrick-ogrady Apr 16, 2026
e2955a1
spike
patrick-ogrady Apr 16, 2026
8522572
cleanup
patrick-ogrady Apr 16, 2026
7c9fa8c
fmt
patrick-ogrady Apr 16, 2026
0db37a1
simplify
patrick-ogrady Apr 16, 2026
09d76b5
cleanup
patrick-ogrady Apr 16, 2026
1f081f9
more persist
patrick-ogrady Apr 16, 2026
5a9b2b0
fix
patrick-ogrady Apr 16, 2026
83f5552
restore alias
patrick-ogrady Apr 16, 2026
2687876
add assertions
patrick-ogrady Apr 16, 2026
2bd4ba1
excessive comments (to trim)
patrick-ogrady Apr 16, 2026
17976d4
more cleanup
patrick-ogrady Apr 16, 2026
af62c78
more docs
patrick-ogrady Apr 16, 2026
ac26165
fmt
patrick-ogrady Apr 16, 2026
5ce301e
nits
patrick-ogrady Apr 16, 2026
84ce908
progress
patrick-ogrady Apr 16, 2026
21cf103
nits
patrick-ogrady Apr 16, 2026
fb35882
minimize
patrick-ogrady Apr 16, 2026
c837ce6
nit
patrick-ogrady Apr 16, 2026
460c09b
field name
patrick-ogrady Apr 16, 2026
42dda42
nits
patrick-ogrady Apr 16, 2026
f8804e8
nit
patrick-ogrady Apr 16, 2026
e578913
nit
patrick-ogrady Apr 16, 2026
d0434f7
cleanup
patrick-ogrady Apr 16, 2026
8991e3b
fix issue
patrick-ogrady Apr 17, 2026
6bbe825
nit
patrick-ogrady Apr 17, 2026
cbc1aed
nits
patrick-ogrady Apr 17, 2026
2a0711c
add restart tests
patrick-ogrady Apr 17, 2026
a373ee5
nits
patrick-ogrady Apr 17, 2026
e6ebde1
persist early
patrick-ogrady Apr 17, 2026
b0c8a49
revert change
patrick-ogrady Apr 17, 2026
e5197ff
Merge remote-tracking branch 'origin/main' into pr/3543
patrick-ogrady Apr 17, 2026
7bd8b4f
nit
patrick-ogrady Apr 17, 2026
ab8e60a
more tests
patrick-ogrady Apr 17, 2026
37854e3
more tests
patrick-ogrady Apr 17, 2026
dff52e2
extend
patrick-ogrady Apr 17, 2026
cf1e2b9
progress
patrick-ogrady Apr 17, 2026
fd60d04
add more regression tests
patrick-ogrady Apr 17, 2026
0c13c21
remove too strict test
patrick-ogrady Apr 17, 2026
cbe722b
cleanup
patrick-ogrady Apr 17, 2026
5fed8c4
make fix-fmt
patrick-ogrady Apr 17, 2026
30d7952
put order back
patrick-ogrady Apr 17, 2026
880811c
move back
patrick-ogrady Apr 17, 2026
f38383d
keep height
patrick-ogrady Apr 17, 2026
389d7dd
revert special treatment
patrick-ogrady Apr 17, 2026
6e3d783
shorten
patrick-ogrady Apr 17, 2026
bb17f6c
nits
patrick-ogrady Apr 17, 2026
363cba8
minimize
patrick-ogrady Apr 17, 2026
3de180a
nit
patrick-ogrady Apr 17, 2026
b514391
nit
patrick-ogrady Apr 17, 2026
6fcf396
add must use
patrick-ogrady Apr 17, 2026
3c9f09b
remove
patrick-ogrady Apr 17, 2026
b846b2c
nits
patrick-ogrady Apr 17, 2026
51af0b8
nit
patrick-ogrady Apr 17, 2026
8d5d8cd
nit
patrick-ogrady Apr 17, 2026
da822e0
nits
patrick-ogrady Apr 17, 2026
cc6840d
fix
patrick-ogrady Apr 17, 2026
ea24374
nit
patrick-ogrady Apr 17, 2026
f5a2888
local
patrick-ogrady Apr 17, 2026
7e5754b
nit
patrick-ogrady Apr 17, 2026
458ba15
cleanup test
patrick-ogrady Apr 17, 2026
81a2930
nit
patrick-ogrady Apr 17, 2026
1d8c1d3
nits
patrick-ogrady Apr 17, 2026
c5b4b56
nits
patrick-ogrady Apr 17, 2026
94542dd
make consistent
patrick-ogrady Apr 17, 2026
0b1f28b
fix
patrick-ogrady Apr 18, 2026
a2e88d4
Address Feedback (#3628)
patrick-ogrady Apr 20, 2026
c17539b
nit
patrick-ogrady Apr 20, 2026
28ee529
move import
patrick-ogrady Apr 20, 2026
eca4cdc
[consensus] Final Certify Nits (#3629)
patrick-ogrady Apr 20, 2026
d2b918e
add verified check on propose
patrick-ogrady Apr 20, 2026
826c503
[consensus] Decouple Propose Durability from Broadcast (#3630)
patrick-ogrady Apr 20, 2026
a5db735
[consensus] Address Last Round of Feedback (#3634)
patrick-ogrady Apr 20, 2026
d34a7e8
nits
patrick-ogrady Apr 20, 2026
8ae64d5
lint
patrick-ogrady Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions consensus/fuzz/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,7 @@ where
propose_latency: (10.0, 5.0),
verify_latency: (10.0, 5.0),
certify_latency: (10.0, 5.0),
should_certify: application::Certifier::Sometimes,
should_certify: application::Certifier::Always,
};
let (actor, application) =
application::Application::new(context.with_label("application"), app_cfg);
Expand Down Expand Up @@ -609,7 +609,7 @@ fn run_with_twin_mutator<P: simplex::Simplex>(input: FuzzInput) {
propose_latency: (10.0, 5.0),
verify_latency: (10.0, 5.0),
certify_latency: (10.0, 5.0),
should_certify: application::Certifier::Sometimes,
should_certify: application::Certifier::Always,
};
let (actor, application) =
application::Application::new(primary_context.with_label("application"), app_cfg);
Expand Down
25 changes: 13 additions & 12 deletions consensus/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,13 @@ stability_scope!(BETA, cfg(not(target_arch = "wasm32")) {
/// If it is possible to generate a payload, the Digest should be returned over the provided
/// channel. If it is not possible to generate a payload, the channel can be dropped. If construction
/// takes too long, the consensus engine may drop the provided proposal.
///
/// Returning a payload from `propose` commits the local proposer to verifying
/// the same `(context, payload)`.
///
/// For [`CertifiableAutomaton`] implementations, returning a payload from
/// `propose` also commits the local proposer to certifying that same
/// `(round, payload)` if it later becomes notarized.
fn propose(
&mut self,
context: Self::Context,
Expand Down Expand Up @@ -134,18 +141,12 @@ stability_scope!(BETA, cfg(not(target_arch = "wasm32")) {
/// Determine whether a verified payload is safe to commit.
///
/// The round parameter identifies which consensus round is being certified, allowing
/// applications to associate certification with the correct verification context.
///
/// Note: In applications where payloads incorporate the round number (recommended),
/// each round will have a unique payload digest. However, the same payload may appear
/// in multiple rounds when re-proposing notarized blocks at epoch boundaries or in
/// integrations where payloads are round-agnostic.
///
/// This is particularly useful for applications that employ erasure coding, which
/// can override this method to delay or prevent finalization until they have
/// reconstructed and validated the full block (e.g., after receiving enough shards).
/// applications to associate certification with the correct verification context. The
/// same payload may appear in multiple rounds, so implementations must key any state
/// on `(round, payload)` rather than `payload` alone.
///
/// Like [`Automaton::verify`], certification is single-shot for the given
/// Like [`Automaton::verify`], payloads produced by [`Automaton::propose`] are certifiable-by-construction.
/// Also like [`Automaton::verify`], certification is single-shot for the given
/// `(round, payload)`. Once the returned channel resolves or closes, consensus treats
/// certification as concluded and will not retry the same request.
///
Expand Down Expand Up @@ -193,7 +194,7 @@ stability_scope!(BETA, cfg(not(target_arch = "wasm32")) {
/// treat every broadcast identically can set this to `()`.
type Plan: Send;

/// Broadcast a payload to the given recipients.
/// Broadcast a payload according to the given plan.
fn broadcast(
&mut self,
payload: Self::Digest,
Expand Down
36 changes: 30 additions & 6 deletions consensus/src/marshal/application/validation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,37 @@
//! This module centralizes pure invariant checks shared across marshal verification
//! and certification flows.

use crate::types::{Epoch, Epocher, Height, Round};
use commonware_utils::sync::Mutex;
use std::sync::Arc;
use crate::{
marshal::core::{Mailbox, Variant},
types::{Epoch, Epocher, Height, Round},
};
use commonware_cryptography::certificate::Scheme;

/// Cache for the last block built during proposal, shared between the
/// proposer task and the broadcast path.
pub(crate) type LastBuilt<B> = Arc<Mutex<Option<(Round, B)>>>;
/// Which stage of verification a block has reached.
///
/// This is used to determine which marshal cache a block should be stored in.
#[derive(Clone, Copy, Debug)]
pub(crate) enum Stage {
/// The block has been verified (store in `verified_blocks`).
Verified,
/// The block has been certified (store in `notarized_blocks`).
Certified,
}

impl Stage {
/// Store `block` in the marshal cache for the provided stage.
pub(crate) async fn store<S: Scheme, V: Variant>(
self,
marshal: &mut Mailbox<S, V>,
round: Round,
block: V::Block,
) -> bool {
match self {
Self::Verified => marshal.verified(round, block).await,
Self::Certified => marshal.certified(round, block).await,
}
}
}

/// Returns true if the block is at an epoch boundary (last block in its epoch).
#[inline]
Expand Down
111 changes: 111 additions & 0 deletions consensus/src/marshal/application/verification_tasks.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,114 @@ where
.retain(|(task_round, _), _| task_round > finalized_round);
}
}

#[cfg(test)]
mod tests {
use super::*;
use crate::types::{Epoch, View};
use commonware_cryptography::{sha256::Digest as Sha256Digest, Hasher, Sha256};

type D = Sha256Digest;

fn round(view: u64) -> Round {
Round::new(Epoch::zero(), View::new(view))
}

fn pending_task() -> oneshot::Receiver<bool> {
let (_tx, rx) = oneshot::channel();
rx
}

#[test]
fn test_insert_and_take_returns_task() {
let tasks = VerificationTasks::<D>::new();
let digest = Sha256::hash(b"block");
tasks.insert(round(1), digest, pending_task());

assert!(tasks.take(round(1), digest).is_some());
assert!(
tasks.take(round(1), digest).is_none(),
"taking twice should yield None"
);
}

#[test]
fn test_take_absent_key_is_none() {
let tasks = VerificationTasks::<D>::new();
assert!(tasks.take(round(1), Sha256::hash(b"missing")).is_none());
}

#[test]
fn test_take_distinguishes_rounds_and_digests() {
let tasks = VerificationTasks::<D>::new();
let digest_a = Sha256::hash(b"a");
let digest_b = Sha256::hash(b"b");
tasks.insert(round(1), digest_a, pending_task());
tasks.insert(round(2), digest_a, pending_task());
tasks.insert(round(1), digest_b, pending_task());

assert!(tasks.take(round(1), digest_a).is_some());
assert!(tasks.take(round(2), digest_a).is_some());
assert!(tasks.take(round(1), digest_b).is_some());
}

#[test]
fn test_retain_after_drops_at_and_below_boundary() {
let tasks = VerificationTasks::<D>::new();
let digest = Sha256::hash(b"block");
tasks.insert(round(1), digest, pending_task());
tasks.insert(round(2), digest, pending_task());
tasks.insert(round(3), digest, pending_task());

tasks.retain_after(&round(2));

assert!(
tasks.take(round(1), digest).is_none(),
"tasks strictly below boundary should be dropped"
);
assert!(
tasks.take(round(2), digest).is_none(),
"tasks at boundary should be dropped"
);
assert!(
tasks.take(round(3), digest).is_some(),
"tasks strictly above boundary should be retained"
);
}

#[test]
fn test_retain_after_spans_epochs() {
let tasks = VerificationTasks::<D>::new();
let digest = Sha256::hash(b"block");
let early = Round::new(Epoch::zero(), View::new(100));
let late = Round::new(Epoch::new(1), View::zero());
tasks.insert(early, digest, pending_task());
tasks.insert(late, digest, pending_task());

tasks.retain_after(&early);

assert!(
tasks.take(early, digest).is_none(),
"task at boundary must be dropped"
);
assert!(
tasks.take(late, digest).is_some(),
"task in later epoch must outlive an earlier boundary"
);
}

#[test]
fn test_retain_after_empty_map_is_noop() {
let tasks = VerificationTasks::<D>::new();
tasks.retain_after(&round(5));
assert!(tasks.take(round(5), Sha256::hash(b"x")).is_none());
}

#[test]
fn test_default_matches_new() {
let default = <VerificationTasks<D> as Default>::default();
let digest = Sha256::hash(b"block");
default.insert(round(1), digest, pending_task());
assert!(default.take(round(1), digest).is_some());
}
}
Loading
Loading