linera-io/linera-protocol · error · ChainError
Empty blocks are not allowed
Error message
Empty blocks are not allowed
What it means
execute_block_inner (linera-chain/src/chain.rs:1130) fires EmptyBlock after the execution loop when every transaction was an incoming bundle that got discarded — not rejected — due to resource limit errors (the discard path at chain.rs:1060-1123 removes them for retry in a later block). If all transactions are removed this way, the block would confirm nothing, which Linera forbids. The code comment calls it 'unlikely in practice but theoretically possible'.
Source
Thrown at linera-chain/src/chain.rs:1130
Self::discard_remaining_bundles(block, i, None);
Self::discard_remaining_stream_updates(block, i);
} else {
info!(
%error,
index = i,
"UpdateStream exceeded block limits, discarding for retry"
);
block.transactions.remove(i);
}
// Do not increment i - the next transaction is now at i.
}
(Err(e), _, _) => return Err(e),
};
}
// This can only happen if all transactions were incoming bundles that all got discarded
// due to resource limit errors. This is unlikely in practice but theoretically possible.
ensure!(!block.transactions.is_empty(), ChainError::EmptyBlock);
// A block may advance the epoch at most once, so that consecutive blocks never skip
// an epoch: the child of a block in epoch `e` is at most in epoch `e + 1`.
let end_epoch = *chain.system.epoch.get();
ensure!(
end_epoch.0 <= start_epoch.0.saturating_add(1),
ChainError::MultipleEpochAdvances {
start_epoch,
end_epoch,
}
);
let recipients = block_execution_tracker.recipients();
let non_ack_tx_indices = block_execution_tracker.non_checkpoint_ack_tx_indices();
let mut recipient_heights = Vec::new();
for (recipient, height) in chain
.previous_message_blocks
.multi_get_pairs(recipients)View on GitHub (pinned to 6c226ddcb3)
Solutions
- Retry later: the discarded bundles are re-queued on the sender side, so a subsequent proposal after conditions change can succeed
- Include at least one always-succeeding operation (e.g., a minimal transfer) in the block so it can never end up empty
- Ask the sender to split oversized bundles, or (if you govern the chain) adjust resource limits in the policy
Example fix
// before: proposing a block of only risky incoming bundles
let txs = bundles.into_iter().map(Transaction::ReceiveMessages).collect();
let block = ProposedBlock { transactions: txs, .. };
// after: anchor the block with one guaranteed operation
let mut txs = vec![Transaction::ExecuteOperation(Operation::system(
SystemOperation::Transfer { .. } // minimal, always admissible
))];
txs.extend(bundles.into_iter().map(Transaction::ReceiveMessages));
let block = ProposedBlock { transactions: txs, .. }; Defensive patterns
Strategy: fallback
Validate before calling
// Before proposing, detect the risky shape: a block made only of incoming bundles
// that resource limits might discard:
let only_bundles = !block.transactions.is_empty()
&& block.transactions.iter().all(|t| matches!(t, Transaction::ReceiveMessages(_)));
if only_bundles {
// add a fallback transaction so the block can never end up empty
} Try / catch
match result {
Err(ChainError::EmptyBlock) => {
// every bundle was discarded for limits: retry later (bundles stay queued
// on the sender) or add a minimal always-valid operation and re-propose
}
other => other?,
} Prevention
- Anchor bundle-only blocks with one trivial operation
- Watch resource-limit metrics on senders whose bundles target your chains
- Remember discarded (not rejected) bundles are retried in later blocks — this error can clear on its own
When it happens
Trigger: A proposal consisting solely of incoming bundles that all exceed block resource limits (fuel, message size, event counts) and whose senders are protected/never-reject, forcing discard instead of reject; a lowered resource policy making previously-fine bundles oversized.
Common situations: Adversarial or buggy senders publishing oversized bundles to a recipient chain; committee policy tightening limits so queued bundles no longer fit; chains whose every pending message comes from one heavy application.
Related errors
- Chain is expecting a next block at height {expected_block_he
- The previous block hash of a new block should match the last
- Incoming message bundle in block proposed to {chain_id} has
- Cannot vote for block proposal of chain {chain_id} because {
- Block timestamp {new} must not be earlier than the parent bl
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/1b6961de6890963b.
Report an issue: GitHub.