linera-io/linera-protocol · error · ChainError
ClosedChain
ClosedChain
Error message
Closed chains cannot have operations, accepted messages or empty blocks
What it means
During block execution, execute_message_in_block handles incoming message bundles. For MessageAction::Accept it enforces at linera-chain/src/block_tracker.rs:272 that the target chain is not closed (ChainError::ClosedChain). A closed chain is administratively shut down and must not accept operations, messages, or produce blocks — so a block proposal that accepts an incoming bundle on a closed chain is rejected outright, failing the whole block.
Source
Thrown at linera-chain/src/block_tracker.rs:272
let _message_latency = message_latency.measure_latency_us();
let context = MessageContext {
chain_id: self.chain_id,
origin: incoming_bundle.origin,
origin_timestamp: incoming_bundle.bundle.timestamp,
is_bouncing: posted_message.is_bouncing(),
height: self.block_height,
round,
authenticated_owner: posted_message.authenticated_owner,
refund_grant_to: posted_message.refund_grant_to,
timestamp: self.timestamp,
};
let mut grant = posted_message.grant;
match incoming_bundle.action {
MessageAction::Accept => {
let chain_execution_context =
ChainExecutionContext::IncomingBundle(txn_tracker.transaction_index());
// Once a chain is closed, accepting incoming messages is not allowed.
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!(View on GitHub (pinned to 6c226ddcb3)
Solutions
- Check the chain's closed status (system.closed) before constructing or submitting a block that accepts messages.
- Stop and decommission clients/bots targeting the closed chain; point them at the successor chain.
- If the close was unintentional, the chain owner must address chain administration — a closed chain cannot be reopened by proposing blocks.
- Handle ChainError::ClosedChain in the proposer loop by abandoning retries for that chain instead of resubmitting the same block.
Example fix
// before
let actions = vec![IncomingBundle { action: MessageAction::Accept, ..bundle }];
block_proposal.submit().await?;
// after
ensure!(!client.chain_is_closed(chain_id).await?, "chain {chain_id} is closed; do not propose message-accepting blocks");
let actions = vec![IncomingBundle { action: MessageAction::Accept, ..bundle }];
block_proposal.submit().await?; Defensive patterns
Strategy: validation
Validate before calling
// Before proposing a block that accepts incoming bundles:
let chain_info = client.describe_chain(chain_id).await?;
if chain_info.system_context.closed {
anyhow::bail!("chain {chain_id} is closed; cannot accept incoming messages");
}
// ... build and submit the block with MessageAction::Accept bundles Try / catch
match result {
Ok(outcome) => outcome,
Err(ChainError::ClosedChain) => {
// Stop retrying: the chain is administratively closed.
tracing::info!(chain_id = %chain_id, "chain closed; dropping pending proposals");
self.retire_chain(chain_id);
return Ok(());
}
Err(e) => return Err(e),
} Prevention
- Query closed status immediately before block submission to shrink the race window.
- Monitor for admin close-chain operations on chains your clients serve and drain gracefully.
- Configure proposers to abandon (not retry) on ClosedChain.
- In multi-chain services, map chain lifecycle state and gate message-accepting proposals on it.
When it happens
Trigger: A client proposes a block containing an incoming bundle with MessageAction::Accept after the chain's system.closed flag was set by an admin CloseChain operation; a validator processes a delayed/retried proposal that raced with the close transaction; a stale wallet/client keeps submitting blocks against a chain that was closed between queries.
Common situations: Chain owner closed the chain (e.g., migrating to a new chain) but a service or bot still proposes blocks with pending incoming messages; testing against a chain previously closed in an earlier test case; race between querying chain state and proposing the block.
Related errors
- CannotRejectMessage
- InvalidBlockChaining
- FalseFirstRoundAttestation
- TooManyPublishedBlobs
- UnexpectedBlob
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/f0fb422d90a857fd.
Report an issue: GitHub.