linera-io/linera-protocol · error · ChainError
CannotRejectMessage
CannotRejectMessage
Error message
Block proposed to {chain_id} is attempting to reject protected message {posted_message:?} What it means
For MessageAction::Reject, execute_message_in_block enforces at linera-chain/src/block_tracker.rs:290 that a posted message may only be rejected if it is not protected, or if the chain is already closed. Protected messages (system-critical messages identified by PostedMessage::is_protected, such as committee changes) must be accepted; rejecting them would break protocol invariants. Violation raises ChainError::CannotRejectMessage and scraps the entire block proposal, as the comment above the check states.
Source
Thrown at linera-chain/src/block_tracker.rs:290
ensure!(!chain.system.closed.get(), ChainError::ClosedChain);
let mut actor =
ExecutionStateActor::new(chain, txn_tracker, self.resource_controller);
Box::pin(actor.execute_message(
context,
posted_message.message.clone(),
(grant > Amount::ZERO).then_some(&mut grant),
))
.await
.with_execution_context(chain_execution_context)?;
actor
.send_refund(context, grant)
.with_execution_context(chain_execution_context)?;
}
MessageAction::Reject => {
// If rejecting a message fails, the entire block proposal should be
// scrapped.
ensure!(
!posted_message.is_protected() || *chain.system.closed.get(),
ChainError::CannotRejectMessage {
chain_id: self.chain_id,
origin: incoming_bundle.origin,
posted_message: Box::new(posted_message.clone()),
}
);
let mut actor =
ExecutionStateActor::new(chain, txn_tracker, self.resource_controller);
if posted_message.is_tracked() {
// Bounce the message.
actor
.bounce_message(context, grant, posted_message.message.clone())
.with_execution_context(ChainExecutionContext::Block)?;
} else {
// Nothing to do except maybe refund the grant.
actor
.send_refund(context, grant)View on GitHub (pinned to 6c226ddcb3)
Solutions
- Change the bundle's action to MessageAction::Accept for protected messages — there is no supported path that rejects them on an open chain.
- Fix reject policies to consult posted_message.is_protected() before choosing Reject, not just the origin.
- If the chain is being decommissioned, close the chain first: on a closed chain rejecting protected messages is permitted.
- Propagate CannotRejectMessage as a signal to regenerate the block without the offending reject action rather than retrying it.
Example fix
// before
let action = if user_wants_to_skip(&origin) { MessageAction::Reject } else { MessageAction::Accept };
// after
let action = if user_wants_to_skip(&origin) && !posted_message.is_protected() {
MessageAction::Reject
} else {
MessageAction::Accept
}; Defensive patterns
Strategy: validation
Validate before calling
// Choose the bundle action per message before building the block:
let action = if reject_policy.allows(origin, &posted_message) && !posted_message.is_protected() {
MessageAction::Reject
} else {
MessageAction::Accept
}; Try / catch
match result {
Ok(outcome) => outcome,
Err(ChainError::CannotRejectMessage { chain_id, origin, posted_message }) => {
// Regenerate the block accepting this message; retrying the same block will fail again.
tracing::warn!(
chain_id = %chain_id,
origin = %origin,
message_id = ?posted_message.message_hash(),
"protected message cannot be rejected; rebuilding block with Accept"
);
self.rebuild_with_accept(origin).await?
}
Err(e) => return Err(e),
} Prevention
- Never blanket-reject unknown-origin bundles; gate on posted_message.is_protected().
- Keep client and protocol versions in sync so the protected-message set matches the chain's.
- Treat CannotRejectMessage as a block-regeneration signal, not a retryable error.
- In tests, cover both reject paths: protected message on open chain (must fail) and on closed chain (must succeed).
When it happens
Trigger: A block proposal sets MessageAction::Reject on an incoming bundle whose posted message is protected (e.g., a new-committee or governance message) while the chain is still open; client code blanket-rejects all incoming messages to shed load; a wallet default configured to reject unknown messages receives a system message.
Common situations: Client-side logic that rejects messages from unknown origins as a spam defense, catching protected system messages too; test scenarios rejecting arbitrary bundles; version skew where a message type newly became protected but the client's reject list was not updated.
Related errors
- ClosedChain
- InvalidBlockChaining
- FalseFirstRoundAttestation
- InvalidOwner
- Fast blocks cannot query oracles
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/d7bc46a83e35301a.
Report an issue: GitHub.