{"record":{"id":"d7bc46a83e35301a","repo":"linera-io/linera-protocol","slug":"cannotrejectmessage","errorCode":"CannotRejectMessage","errorMessage":"Block proposed to {chain_id} is attempting to reject protected message {posted_message:?}","messagePattern":"Block proposed to (.+?) is attempting to reject protected message (.+?)","errorType":"error_code","errorClass":"ChainError","httpStatus":null,"severity":"error","filePath":"linera-chain/src/block_tracker.rs","lineNumber":290,"sourceCode":"                ensure!(!chain.system.closed.get(), ChainError::ClosedChain);\n\n                let mut actor =\n                    ExecutionStateActor::new(chain, txn_tracker, self.resource_controller);\n                Box::pin(actor.execute_message(\n                    context,\n                    posted_message.message.clone(),\n                    (grant > Amount::ZERO).then_some(&mut grant),\n                ))\n                .await\n                .with_execution_context(chain_execution_context)?;\n                actor\n                    .send_refund(context, grant)\n                    .with_execution_context(chain_execution_context)?;\n            }\n            MessageAction::Reject => {\n                // If rejecting a message fails, the entire block proposal should be\n                // scrapped.\n                ensure!(\n                    !posted_message.is_protected() || *chain.system.closed.get(),\n                    ChainError::CannotRejectMessage {\n                        chain_id: self.chain_id,\n                        origin: incoming_bundle.origin,\n                        posted_message: Box::new(posted_message.clone()),\n                    }\n                );\n                let mut actor =\n                    ExecutionStateActor::new(chain, txn_tracker, self.resource_controller);\n                if posted_message.is_tracked() {\n                    // Bounce the message.\n                    actor\n                        .bounce_message(context, grant, posted_message.message.clone())\n                        .with_execution_context(ChainExecutionContext::Block)?;\n                } else {\n                    // Nothing to do except maybe refund the grant.\n                    actor\n                        .send_refund(context, grant)","sourceCodeStart":272,"sourceCodeEnd":308,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/block_tracker.rs#L272-L308","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nlet action = if user_wants_to_skip(&origin) { MessageAction::Reject } else { MessageAction::Accept };\n\n// after\nlet action = if user_wants_to_skip(&origin) && !posted_message.is_protected() {\n    MessageAction::Reject\n} else {\n    MessageAction::Accept\n};","handlingStrategy":"validation","validationCode":"// Choose the bundle action per message before building the block:\nlet action = if reject_policy.allows(origin, &posted_message) && !posted_message.is_protected() {\n    MessageAction::Reject\n} else {\n    MessageAction::Accept\n};","typeGuard":null,"tryCatchPattern":"match result {\n    Ok(outcome) => outcome,\n    Err(ChainError::CannotRejectMessage { chain_id, origin, posted_message }) => {\n        // Regenerate the block accepting this message; retrying the same block will fail again.\n        tracing::warn!(\n            chain_id = %chain_id,\n            origin = %origin,\n            message_id = ?posted_message.message_hash(),\n            \"protected message cannot be rejected; rebuilding block with Accept\"\n        );\n        self.rebuild_with_accept(origin).await?\n    }\n    Err(e) => return Err(e),\n}","preventionTips":["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)."],"tags":["linera","blockchain","message-execution","consensus","rust"],"backgroundTag":"protected-message-rejection","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}