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

  1. Change the bundle's action to MessageAction::Accept for protected messages — there is no supported path that rejects them on an open chain.
  2. Fix reject policies to consult posted_message.is_protected() before choosing Reject, not just the origin.
  3. If the chain is being decommissioned, close the chain first: on a closed chain rejecting protected messages is permitted.
  4. 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

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


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/d7bc46a83e35301a. Report an issue: GitHub.