linera-io/linera-protocol · error · ChainError
Closed chains cannot have operations, accepted messages or e
Error message
Closed chains cannot have operations, accepted messages or empty blocks
What it means
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.
Source
Thrown at linera-chain/src/chain.rs:1331
chain_timestamp <= block.timestamp,
ChainError::InvalidBlockTimestamp {
parent: chain_timestamp,
new: block.timestamp
}
);
ensure!(!block.transactions.is_empty(), ChainError::EmptyBlock);
ensure!(
block.published_blob_ids()
== published_blobs
.iter()
.map(|blob| blob.id())
.collect::<BTreeSet<_>>(),
ChainError::InternalError("published_blobs mismatch".to_string())
);
if *self.execution_state.system.closed.get() {
ensure!(block.has_only_rejected_messages(), ChainError::ClosedChain);
}
Self::check_app_permissions(
self.execution_state
.system
.application_permissions
.get()
.await?,
&block,
)?;
ensure!(
!block
.transactions
.iter()
.skip(1)
.any(Transaction::is_checkpoint),
ChainError::CheckpointPreconditionFailed(View on GitHub (pinned to 6c226ddcb3)
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
Example fix
// before: submitting an operation blindly
client.submit_block(vec![Transaction::ExecuteOperation(op)]).await?; // ClosedChain
// after: gate on the closed flag; only rejects are admissible
let info = client.chain_info(chain_id).await?;
if info.info.system_closed {
return Err(anyhow::anyhow!("chain {chain_id} is closed"));
}
client.submit_block(vec![Transaction::ExecuteOperation(op)]).await?; Defensive patterns
Strategy: validation
Validate before calling
// Before submitting anything to a chain, check its lifecycle state:
let info = client.chain_info(chain_id).await?;
if info.info.system_closed {
anyhow::bail!("chain {chain_id} is closed; only rejected-message blocks are allowed");
}
// and for closed chains, ensure every bundle uses MessageAction::Reject:
assert!(block.has_only_rejected_messages()); Type guard
fn admissible_on_closed_chain(block: &ProposedBlock) -> bool {
// mirrors ProposedBlock::has_only_rejected_messages (data_types/mod.rs:112)
block.transactions.iter().all(|t| matches!(
t,
Transaction::ReceiveMessages(IncomingBundle { action: MessageAction::Reject, .. })
))
} Try / catch
match result {
Err(ChainError::ClosedChain) => {
// permanent for this chain: stop submitting, retire clients, or move to a new chain
}
other => other?,
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Admin HTTP server exited unexpectedly: {result:?}
- Cannot run admin operations on the memory store
- Default wallet directory is not supported in this platform:
- nonexistent chain `{chain_id}`
- keypair not found for chain `{chain_id}`
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/41aadd16b5e110dd.
Report an issue: GitHub.