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).

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.

Appendix: 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)