{"record":{"id":"41aadd16b5e110dd","repo":"linera-io/linera-protocol","slug":"closed-chains-cannot-have-operations-accepted-mes","errorCode":null,"errorMessage":"Closed chains cannot have operations, accepted messages or empty blocks","messagePattern":"Closed chains cannot have operations, accepted messages or empty blocks","errorType":"validation","errorClass":"ChainError","httpStatus":null,"severity":"error","filePath":"linera-chain/src/chain.rs","lineNumber":1331,"sourceCode":"            chain_timestamp <= block.timestamp,\n            ChainError::InvalidBlockTimestamp {\n                parent: chain_timestamp,\n                new: block.timestamp\n            }\n        );\n        ensure!(!block.transactions.is_empty(), ChainError::EmptyBlock);\n\n        ensure!(\n            block.published_blob_ids()\n                == published_blobs\n                    .iter()\n                    .map(|blob| blob.id())\n                    .collect::<BTreeSet<_>>(),\n            ChainError::InternalError(\"published_blobs mismatch\".to_string())\n        );\n\n        if *self.execution_state.system.closed.get() {\n            ensure!(block.has_only_rejected_messages(), ChainError::ClosedChain);\n        }\n\n        Self::check_app_permissions(\n            self.execution_state\n                .system\n                .application_permissions\n                .get()\n                .await?,\n            &block,\n        )?;\n\n        ensure!(\n            !block\n                .transactions\n                .iter()\n                .skip(1)\n                .any(Transaction::is_checkpoint),\n            ChainError::CheckpointPreconditionFailed(","sourceCodeStart":1313,"sourceCodeEnd":1349,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-chain/src/chain.rs#L1313-L1349","documentation":"If the chain's system.closed flag is set (via the admin CloseChain operation), execute_block only admits blocks whose transactions are all rejected incoming messages (has_only_rejected_messages, linera-chain/src/chain.rs:112-122 and 1330-1332). ClosedChain fires for any operation, any accepted message, or an empty transaction list on a closed chain — closure is terminal for writes.","triggerScenarios":"Submitting operations or accepted-message blocks to a chain that was closed earlier; a client or faucet still holding the chain's key trying to transfer after closure; automated jobs that keep proposing to a retired chain.","commonSituations":"Retiring a chain but leaving background clients (bots, cron jobs, faucet) running; user apps sending to a closed recipient; test chains closed between phases with fixtures reused afterwards.","solutions":["Query the chain's state and check the closed flag before submitting anything","If you only need to bounce queued incoming messages, build blocks whose bundles all use MessageAction::Reject","Closure is irreversible — move activity to a new chain and repoint clients to it"],"exampleFix":"// before: submitting an operation blindly\nclient.submit_block(vec![Transaction::ExecuteOperation(op)]).await?; // ClosedChain\n\n// after: gate on the closed flag; only rejects are admissible\nlet info = client.chain_info(chain_id).await?;\nif info.info.system_closed {\n    return Err(anyhow::anyhow!(\"chain {chain_id} is closed\"));\n}\nclient.submit_block(vec![Transaction::ExecuteOperation(op)]).await?;","handlingStrategy":"validation","validationCode":"// Before submitting anything to a chain, check its lifecycle state:\nlet info = client.chain_info(chain_id).await?;\nif info.info.system_closed {\n    anyhow::bail!(\"chain {chain_id} is closed; only rejected-message blocks are allowed\");\n}\n// and for closed chains, ensure every bundle uses MessageAction::Reject:\nassert!(block.has_only_rejected_messages());","typeGuard":"fn admissible_on_closed_chain(block: &ProposedBlock) -> bool {\n    // mirrors ProposedBlock::has_only_rejected_messages (data_types/mod.rs:112)\n    block.transactions.iter().all(|t| matches!(\n        t,\n        Transaction::ReceiveMessages(IncomingBundle { action: MessageAction::Reject, .. })\n    ))\n}","tryCatchPattern":"match result {\n    Err(ChainError::ClosedChain) => {\n        // permanent for this chain: stop submitting, retire clients, or move to a new chain\n    }\n    other => other?,\n}","preventionTips":["Check the closed flag in chain info before any submission","Shut down bots/cron jobs/faucets when retiring a chain","If only message bouncing is needed, build Reject-only blocks"],"tags":["linera","chain-lifecycle","closed-chain","admin","rust"],"backgroundTag":"closed-chain-write","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}