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

  1. Check the chain's closed status (system.closed) before constructing or submitting a block that accepts messages.
  2. Stop and decommission clients/bots targeting the closed chain; point them at the successor chain.
  3. If the close was unintentional, the chain owner must address chain administration — a closed chain cannot be reopened by proposing blocks.
  4. 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

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


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