Skip to content
Open
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1af3fa5
feat(ethexe-consensus): mini-announces for instant injected TX promises
ukint-vs Apr 8, 2026
b45f07a
Merge remote-tracking branch 'origin/master' into vs/mini-announces
ukint-vs Apr 8, 2026
f3c2e76
fix(ethexe-consensus): drop VR for non-validators in ReadyForMoreAnno…
ukint-vs Apr 8, 2026
cd4d8e3
chore(ethexe-consensus): add TODO for batch commitment during mini-an…
ukint-vs Apr 8, 2026
6c02fd7
fix(ethexe-compute): skip canonical events for same-block mini-announces
ukint-vs Apr 8, 2026
72c90a8
fix(ethexe-consensus): allow dead_code for wait_for_state test helper
ukint-vs Apr 8, 2026
cd54848
fix(ethexe-service): default continuous_block_generation to true in t…
ukint-vs Apr 8, 2026
2dee90e
revert: default continuous_block_generation back to false in tests
ukint-vs Apr 9, 2026
71a9eb3
fix(ethexe-service): guard AggregateBatchCommitment from new heads
ukint-vs Apr 9, 2026
db75857
fix(ethexe-consensus): replay pending events oldest-first in subordinate
ukint-vs Apr 9, 2026
9b2125c
fix(ethexe-service): default continuous_block_generation to true in t…
ukint-vs Apr 9, 2026
af65fee
fix(ethexe-consensus): add batch timer to ReadyForMiniAnnounce
ukint-vs Apr 9, 2026
102613a
fix(ethexe-consensus): buffer next_block in Coordinator for threshold>1
ukint-vs Apr 9, 2026
d0a58bc
chore: fix test by removing validator restarting, detect where conse…
ecol-master Apr 13, 2026
94d1051
Merge branch 'master' into vs/mini-announces
ecol-master Apr 13, 2026
39c75fb
refactor(ethexe-consensus): timer-based polling for mini-announces
ukint-vs Apr 14, 2026
57d0257
fix(ethexe-consensus): timeout Coordinator on second new head
ukint-vs Apr 14, 2026
3cd275d
fix(ethexe-consensus): block-aware CDL for mini-announces correctness
ukint-vs Apr 14, 2026
fc0ea47
fix(ethexe-consensus): validate mini-announce parent and defer VR on …
ukint-vs Apr 15, 2026
63c9791
refactor(ethexe-consensus): simplify subordinate for mini-announces
ukint-vs Apr 16, 2026
bfad58c
fix(ethexe-consensus): CDL off-by-one in block-aware counting
ukint-vs Apr 16, 2026
db0c977
fix(ethexe-consensus): cap pending_events during steady-state deferral
ukint-vs Apr 16, 2026
e67cedc
merge: resolve conflicts with origin/master
ukint-vs Apr 16, 2026
00be1c5
docs(ethexe-consensus): update edge cases with verified constraints
ukint-vs Apr 16, 2026
c20b446
Merge branch 'master' into vs/mini-announces
ukint-vs Apr 16, 2026
d805093
refactor(ethexe-consensus): reduce mini-announces complexity
ukint-vs Apr 16, 2026
d13f0ba
chore: remove .claude/worktrees from tracking
ukint-vs Apr 16, 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
22 changes: 15 additions & 7 deletions ethexe/compute/src/compute.rs
Original file line number Diff line number Diff line change
Expand Up @@ -310,13 +310,21 @@ pub(crate) mod utils {
) -> Result<ExecutableData> {
let block_hash = announce.block_hash;

let matured_events =
find_canonical_events_post_quarantine(db, block_hash, canonical_quarantine)?;

let events = matured_events
.into_iter()
.filter_map(|event| event.to_request())
.collect();
// Check if parent announce is for the same block (mini-announce case).
// If so, canonical events were already processed by the parent — skip them.
let is_same_block = db
.announce(announce.parent)
.is_some_and(|parent| parent.block_hash == block_hash);
let events = if is_same_block {
vec![]
} else {
let matured_events =
find_canonical_events_post_quarantine(db, block_hash, canonical_quarantine)?;
matured_events
.into_iter()
.filter_map(|event| event.to_request())
.collect()
};

Ok(ExecutableData {
block: SimpleBlockData {
Expand Down
25 changes: 20 additions & 5 deletions ethexe/consensus/src/validator/coordinator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ pub struct Coordinator {
ctx: ValidatorContext,
validators: BTreeSet<Address>,
multisigned_batch: MultisignedBatchCommitment,
/// Buffered next block to process after submission completes.
next_block: Option<SimpleBlockData>,
}

impl StateHandler for Coordinator {
Expand All @@ -55,6 +57,12 @@ impl StateHandler for Coordinator {
self.ctx
}

fn process_new_head(mut self, block: SimpleBlockData) -> Result<ValidatorState> {
// Buffer the new head instead of dying. Process it after submission.
self.next_block = Some(block);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When a second process_new_head arrives while the Coordinator is still collecting validation replies (threshold > 1), the previously buffered block is silently overwritten without any warning:

fn process_new_head(mut self, block: SimpleBlockData) -> Result<ValidatorState> {
    // Buffer the new head instead of dying. Process it after submission.
    self.next_block = Some(block);
    Ok(self.into())
}

If signature collection is slow relative to block production, intermediate blocks will be silently skipped — analogous to the WaitingAnnounceComputed TODO in Producer, but without even a warning. Consider adding a warning when self.next_block.replace(block).is_some() to make silent block drops observable.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Intentional. The latest block is always the correct one to process — older buffered blocks are stale. Same pattern used in AggregateBatchCommitment. No warning needed since this is normal operation under fast block production.

Ok(self.into())
}

fn process_validation_reply(
mut self,
reply: BatchCommitmentValidationReply,
Expand All @@ -72,7 +80,7 @@ impl StateHandler for Coordinator {
}

if self.multisigned_batch.signatures().len() as u64 >= self.ctx.core.signatures_threshold {
Self::submission(self.ctx, self.multisigned_batch)
Self::submission(self.ctx, self.multisigned_batch, self.next_block)
} else {
Ok(self.into())
}
Expand Down Expand Up @@ -110,7 +118,7 @@ impl Coordinator {
.set(block.header.height);

if multisigned_batch.signatures().len() as u64 >= ctx.core.signatures_threshold {
return Self::submission(ctx, multisigned_batch);
return Self::submission(ctx, multisigned_batch, None);
}

let era_index = ctx
Expand All @@ -131,13 +139,15 @@ impl Coordinator {
ctx,
validators: validators.into_iter().collect(),
multisigned_batch,
next_block: None,
}
.into())
}

pub fn submission(
ctx: ValidatorContext,
multisigned_batch: MultisignedBatchCommitment,
next_block: Option<SimpleBlockData>,
) -> Result<ValidatorState> {
let (batch, signatures) = multisigned_batch.into_parts();
let cloned_committer = ctx.core.committer.clone_boxed();
Expand All @@ -150,16 +160,21 @@ impl Coordinator {
block_hash,
batch_digest,
tx,
}.into(),
}
.into(),
Err(err) => ConsensusEvent::Warning(format!(
"Failed to submit commitment for block {block_hash}, digest {batch_digest}: {err}"
))
)),
};
Ok(event)
}
.boxed(),
);
Initial::create(ctx)
let state = Initial::create(ctx)?;
match next_block {
Some(block) => state.process_new_head(block),
None => Ok(state),
}
}
}

Expand Down
1 change: 1 addition & 0 deletions ethexe/consensus/src/validator/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ impl ElectionProvider for MockEthereum {
#[async_trait]
pub trait WaitFor {
async fn wait_for_event(self) -> Result<(ValidatorState, ConsensusEvent)>;
#[allow(dead_code)]
async fn wait_for_state<F>(self, f: F) -> Result<ValidatorState>
where
F: Fn(&ValidatorState) -> bool + Unpin + Send;
Expand Down
Loading
Loading