Skip to content

[Critical] DecisionHandler.on_decided() never called in HostMsg::Decided handler #90

@qj0r9j0vc2

Description

@qj0r9j0vc2

Problem

In crates/consensus/src/host.rs, the HostMsg::Decided handler does NOT call self.decision_handler.on_decided(). This means decided Cuts are never persisted, forwarded, or processed.

Location

crates/consensus/src/host.rs:268-316

Code Analysis

HostMsg::Decided {
    certificate,
    extensions: _,
    reply_to,
} => {
    let height = certificate.height;
    let round = certificate.round;
    
    info!(parent: &self.span, "Consensus decided on value");
    
    // Check for epoch transition
    let _epoch_transition = match self.on_block_committed(height) {
        // ...
    };
    
    // Reply with next height to start
    let next_height = height.next();
    // ... sends Next::Start ...
    
    // BUG: decision_handler.on_decided() is NEVER CALLED!
}

The DecisionHandler trait expects:

async fn on_decided(
    &self,
    height: ConsensusHeight,
    round: ConsensusRound,
    value: ConsensusValue,  // <-- This is needed
    certificate: CommitCertificate<CipherBftContext>,
) -> Result<(), ConsensusError>

Root Cause

Two issues compound this bug:

  1. Handler never called: The HostMsg::Decided handler simply logs and replies with Next::Start without invoking decision_handler.on_decided()

  2. Missing value lookup: Even if the handler tried to call on_decided(), it only has certificate.value_id - not the actual ConsensusValue. The value needs to be looked up from ChannelValueBuilder.cuts_by_value_id.

Impact

  • Decided Cuts are lost: The backward-compatible spawn_host() sends Cuts to decided_tx, but that channel never receives anything
  • Node loop stalls: In node.rs:run(), the decided_rx.recv() branch never triggers
  • No Cut execution: The execution layer never receives decided Cuts
  • State never advances: Block state root is never updated

Suggested Fix

HostMsg::Decided {
    certificate,
    extensions: _,
    reply_to,
} => {
    let height = certificate.height;
    let round = certificate.round;
    let value_id = certificate.value_id.clone();
    
    info!(parent: &self.span, height = height.0, "Consensus decided on value");
    
    // Look up the actual value from value_id
    // This requires the value_builder to expose a method to get value by id
    // or we need to store proposed values by value_id in the Host
    
    // Call decision handler
    if let Err(e) = self.decision_handler.on_decided(
        height,
        round,
        value,  // Need to obtain this from value_id lookup
        certificate.clone(),
    ).await {
        error!(parent: &self.span, error = %e, "Failed to process decided value");
    }
    
    // Check for epoch transition
    // ...
}

The ChannelValueBuilder already stores cuts by value_id in cuts_by_value_id. A getter method should be added to retrieve the value for the Decided handler.

Severity

Critical - This breaks the entire consensus finalization flow.

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions