Skip to content

[Bug] ReceivedProposalPart hardcodes valid_round to Round::Nil #94

@qj0r9j0vc2

Description

@qj0r9j0vc2

Problem

In the HostMsg::ReceivedProposalPart handler, the valid_round field is hardcoded to Round::Nil instead of being extracted from the proposal.

Location

crates/consensus/src/host.rs:217-221

Code Analysis

HostMsg::ReceivedProposalPart {
    from,
    part,
    reply_to,
} => {
    match part.content {
        StreamContent::Data(proposal_part) => {
            if proposal_part.first && proposal_part.last {
                let height = proposal_part.height;
                let round = proposal_part.round;
                let proposer = proposal_part.proposer;
                let value = ConsensusValue(proposal_part.cut);

                // Build ProposedValue for the received proposal
                let proposed_value = ProposedValue {
                    height,
                    round,
                    valid_round: Round::Nil,  // HARDCODED!
                    proposer,
                    value,
                    validity: Validity::Valid,
                };
                // ...
            }
        }
    }
}

Impact

In Tendermint-style BFT consensus:

  • valid_round indicates the round in which the proposal was first seen as valid
  • Used for the "locked value" mechanism to ensure safety
  • If a validator has locked on a value in round R, they should only vote for that value or a value with valid_round >= R

Hardcoding to Nil means:

  • The locking mechanism may not function correctly
  • In edge cases (network partitions, round skips), safety guarantees could be violated
  • Validators may vote for conflicting values they shouldn't

Malachite's Expectation

Looking at Malachite's types, ProposedValue expects the actual valid_round from the proposal:

pub struct ProposedValue<Ctx: Context> {
    pub height: Ctx::Height,
    pub round: Round,
    pub valid_round: Round,  // Should come from proposal
    pub proposer: Ctx::Address,
    pub value: Ctx::Value,
    pub validity: Validity,
}

Suggested Fix

The CutProposalPart (or the streaming protocol) should carry valid_round:

// In CutProposalPart definition
pub struct CutProposalPart {
    pub height: ConsensusHeight,
    pub round: ConsensusRound,
    pub valid_round: Round,  // Add this field
    pub proposer: ConsensusAddress,
    pub cut: Cut,
    pub first: bool,
    pub last: bool,
}

// In the handler
let proposed_value = ProposedValue {
    height,
    round,
    valid_round: proposal_part.valid_round,  // Use actual value
    proposer,
    value,
    validity: Validity::Valid,
};

Severity

Medium - Could affect consensus safety in edge cases with round skips or network partitions.

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions