linera-io/linera-protocol · error · GrpcProtoConversionError
InconsistentChainId
InconsistentChainId
Error message
GrpcProtoConversionError::InconsistentChainId
What it means
Converting a gRPC api::BlockProposal into the internal BlockProposal deserializes the signed ProposalContent and cross-checks that its inner block.chain_id matches the chain_id field on the protobuf envelope. GrpcProtoConversionError::InconsistentChainId means those two identifiers disagree, so the message is malformed or tampered with.
Source
Thrown at linera-rpc/src/grpc/conversions.rs:272
Ok(Self {
chain_id: Some(block_proposal.content.block.chain_id.into()),
content: bincode::serialize(&block_proposal.content)?,
owner: Some(block_proposal.owner().try_into()?),
signature: Some(block_proposal.signature.into()),
original_proposal: block_proposal
.original_proposal
.map(|cert| bincode::serialize(&cert))
.transpose()?,
})
}
}
impl TryFrom<api::BlockProposal> for BlockProposal {
type Error = GrpcProtoConversionError;
fn try_from(block_proposal: api::BlockProposal) -> Result<Self, Self::Error> {
let content: ProposalContent = bincode::deserialize(&block_proposal.content)?;
ensure!(
Some(content.block.chain_id.into()) == block_proposal.chain_id,
GrpcProtoConversionError::InconsistentChainId
);
Ok(Self {
content,
signature: try_proto_convert(block_proposal.signature)?,
original_proposal: block_proposal
.original_proposal
.map(|bytes| bincode::deserialize(&bytes))
.transpose()?,
})
}
}
impl TryFrom<api::CrossChainRequest> for CrossChainRequest {
type Error = GrpcProtoConversionError;
fn try_from(cross_chain_request: api::CrossChainRequest) -> Result<Self, Self::Error> {View on GitHub (pinned to 6c226ddcb3)
Solutions
- Drop the message and ignore or penalize the sender — the proposal cannot be validated as-is
- If you control the sender, derive the envelope chain_id from the same serialized content it ships (content.block.chain_id)
- Pin all Linera components (client, validator, proxy) to one release
Example fix
// before
let proposal: BlockProposal = api_block_proposal.try_into()?; // aborts handler on InconsistentChainId
// after
let proposal = match api_block_proposal.try_into() {
Ok(p) => p,
Err(GrpcProtoConversionError::InconsistentChainId) => {
tracing::warn!("dropping block proposal with mismatched chain_id");
return Ok(api::BlockProposalResponse::default()); // reject, keep serving
}
Err(e) => return Err(e.into()),
}; Defensive patterns
Strategy: try-catch
Validate before calling
let content: ProposalContent = bincode::deserialize(&raw.content)?;
if Some(content.block.chain_id.into()) != raw.chain_id {
// drop the message before attempting full conversion
} Type guard
fn proposal_chain_ids_match(raw: &api::BlockProposal) -> bool {
bincode::deserialize::<ProposalContent>(&raw.content)
.map(|c| Some(c.block.chain_id.into()) == raw.chain_id)
.unwrap_or(false)
} Try / catch
Match GrpcProtoConversionError::InconsistentChainId specifically and treat the message as invalid input: log the mismatched identifiers, do not retry the conversion, and keep serving other peers.
Prevention
- Always derive the envelope chain_id from the serialized content; never set it separately
- Pin all Linera components to one release when proxies forward proposals
- Treat conversion failures as adversarial input in alerting, not transient errors
When it happens
Trigger: A validator, proxy, or client receives a BlockProposal whose top-level chain_id was set independently of the bincode-encoded content — a forwarding bug that rewrites chain_id, version-skewed serializers, or a crafted proposal.
Common situations: Client, validator, and proxy built from different linera-rpc versions; a relay mislabeling forwarded proposals; adversarial nodes sending a proposal for one chain under another chain's id.
Related errors
- MissingCertificates
- serialize event_ids
- serialize heights
- Proxy URI should be valid
- Exporter URI should be valid
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/3ff79aa78f87d8e0.
Report an issue: GitHub.