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

  1. Include at least one operation from each mandatory application in the block (or an accepted incoming message from it)
  2. Include an exempt system operation, which satisfies/clears the mandatory requirement for that block
  3. 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

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


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