linera-io/linera-protocol · error · ChainError
Missing operations or messages from mandatory applications:
Error message
Missing operations or messages from mandatory applications: {0:?} What it means
check_app_permissions also enforces mandatory applications: if the chain declares mandatory_applications, every block must contain an operation from — or an accepted message originating from — each mandatory app; covered apps are removed from a running set as transactions are scanned, and any leftover at chain.rs:1624-1627 raises MissingMandatoryApplications. An exempt system operation clears the whole requirement (chain.rs:1596-1600).
Source
Thrown at linera-chain/src/chain.rs:1624
)
);
if let Operation::User { application_id, .. } = operation {
mandatory.remove(application_id);
}
}
Transaction::ReceiveMessages(incoming_bundle)
if incoming_bundle.action == MessageAction::Accept =>
{
for pending in incoming_bundle.messages() {
if let Message::User { application_id, .. } = &pending.message {
mandatory.remove(application_id);
}
}
}
Transaction::ReceiveMessages(_) => {}
}
}
ensure!(
mandatory.is_empty(),
ChainError::MissingMandatoryApplications(mandatory.into_iter().collect())
);
Ok(())
}
/// Validates the chain-state-level preconditions for a `SystemOperation::Checkpoint`:
/// no *system* event stream tracker is set.
///
/// The structural invariant that `Checkpoint` must be the *first* transaction in its
/// block is enforced unconditionally in `execute_block`, independently of these
/// preconditions. Sender-side event conditions are validated inside
/// `ExecutionStateView::prepare_checkpoint`.
async fn check_checkpoint_preconditions(&self) -> Result<(), ChainError> {
let mut had_system_event_tracker = false;
self.next_expected_events
.for_each_index_while(|stream_id| {
if matches!(stream_id.application_id, GenericApplicationId::System) {View on GitHub (pinned to 6c226ddcb3)
Solutions
- Include at least one operation from each mandatory application in the block (or an accepted incoming message from it)
- Include an exempt system operation, which satisfies/clears the mandatory requirement for that block
- If the mandate is no longer wanted, change ApplicationPermissions to drop the mandatory app
Example fix
// before: block missing the mandatory app's transaction
let txs = vec![Transaction::ExecuteOperation(other_op)]; // chain mandates app M
// after: ensure every mandatory app is represented
let mut txs = vec![Transaction::ExecuteOperation(operation_of_mandatory_app)];
txs.push(Transaction::ExecuteOperation(other_op));
let block = ProposedBlock { transactions: txs, .. }; Defensive patterns
Strategy: validation
Validate before calling
// Mirror the mandatory-app coverage check before submitting:
let perms = client.chain_info(chain_id).await?.info.application_permissions;
let mut mandatory: HashSet<_> = perms.mandatory_applications.iter().copied().collect();
for t in &block.transactions {
match t {
Transaction::ExecuteOperation(op) if op.is_exempt_from_permissions() => { mandatory.clear(); }
Transaction::ExecuteOperation(Operation::User { application_id, .. }) => { mandatory.remove(application_id); }
Transaction::ReceiveMessages(b) if b.action == MessageAction::Accept => {
for m in b.messages() {
if let Message::User { application_id, .. } = &m.message { mandatory.remove(application_id); }
}
}
_ => {}
}
}
anyhow::ensure!(mandatory.is_empty(), "block missing mandatory apps: {mandatory:?}"); Type guard
fn covers_mandatory_apps(block: &ProposedBlock, perms: &ApplicationPermissions) -> bool {
let mut mandatory: HashSet<_> = perms.mandatory_applications.iter().copied().collect();
for t in &block.transactions {
match t {
Transaction::ExecuteOperation(op) if op.is_exempt_from_permissions() => mandatory.clear(),
Transaction::ExecuteOperation(Operation::User { application_id, .. }) => { mandatory.remove(application_id); }
Transaction::ReceiveMessages(b) if b.action == MessageAction::Accept => {
for m in b.messages() {
if let Message::User { application_id, .. } = &m.message { mandatory.remove(application_id); }
}
}
_ => {}
}
}
mandatory.is_empty()
} Try / catch
match result {
Err(ChainError::MissingMandatoryApplications(missing)) => {
// add an operation or accepted message for each app in `missing`, or include
// an exempt system operation which satisfies the mandate
}
other => other?,
} Prevention
- Read mandatory_applications from chain state when building blocks for restricted chains
- Remember an exempt system operation satisfies the mandate for the block
- Keep permission config with the app deployment docs so block builders know the rules
When it happens
Trigger: Submitting a block that omits a mandatory app's operation/message to a chain configured with mandatory_applications; a block containing only rejected messages when a mandatory app requires accepted activity; unrelated third-party blocks to a chain that mandates specific apps.
Common situations: See trigger scenarios.
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
- Empty blocks are not allowed
- Block timestamp {new} must not be earlier than the parent bl
- All operations on this chain must be from one of the followi
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/eacb32f4e59b1924.
Report an issue: GitHub.