linera-io/linera-protocol · error · ExecutionError

ServiceOracleQueryOperations

ServiceOracleQueryOperations

Error message

ExecutionError::ServiceOracleQueryOperations(operations)

What it means

When a contract queries a service as an oracle (QueryServiceOracle), the query must be side-effect free with respect to consensus: the QueryOutcome may carry a response but must contain zero operations. If the service handler schedules operations during the query, the actor fails with ServiceOracleQueryOperations listing the offending operations (execution_state_actor.rs:750), because oracle responses must replay deterministically.

Source

Thrown at linera-execution/src/execution_state_actor.rs:750

                    .txn_tracker
                    .oracle(|| async {
                        let context = QueryContext {
                            chain_id: state.context().extra().chain_id(),
                            next_block_height,
                            local_time,
                        };
                        let QueryOutcome {
                            response,
                            operations,
                        } = Box::pin(state.query_user_application_with_deadline(
                            application_id,
                            context,
                            query,
                            deadline,
                            created_blobs,
                        ))
                        .await?;
                        ensure!(
                            operations.is_empty(),
                            ExecutionError::ServiceOracleQueryOperations(operations)
                        );
                        Ok(OracleResponse::Service(response))
                    })
                    .await?
                    .to_service_response()?;
                callback.respond(bytes);
            }

            AddOutgoingMessage { message, callback } => {
                self.txn_tracker.add_outgoing_message(message);
                callback.respond(());
            }

            SetLocalTime {
                local_time,
                callback,

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Remove operation-scheduling from the service query handler; queries may only read state and return bytes
  2. If an action is required, have the calling contract emit the operation itself instead of delegating to the service
  3. Add a unit test asserting QueryOutcome.operations is empty for every query path of the application

Example fix

// before
fn handle_query(q: Query) -> Result<Response> {
    if q.should_act { self.schedule_operation(op)?; }
    self.read_state(q)
}

// after
fn handle_query(q: Query) -> Result<Response> {
    // read-only: never schedule operations here
    self.read_state(q)
}
Defensive patterns

Strategy: try-catch

Validate before calling

// Test-level validation: every query path must produce zero operations
#[test]
fn queries_have_no_side_effects() {
    for query in test_queries() {
        let QueryOutcome { response: _, operations } = service.handle_query(query);
        assert!(operations.is_empty(), "query scheduled operations");
    }
}

Type guard

fn is_service_oracle_query_operations(err: &ExecutionError) -> bool {
    matches!(err, ExecutionError::ServiceOracleQueryOperations(_))
}

Try / catch

match contract.call_service_oracle(app_id, query).await {
    Ok(bytes) => bytes,
    Err(ref e) if is_service_oracle_query_operations(e) => {
        // service bug: query handler scheduled operations; fix the service, do not retry
        return Err(anyhow!("service query must not schedule operations; fix its query handler"));
    }
    Err(e) => return Err(e.into()),
}

Prevention

When it happens

Trigger: A service's query handler calls a runtime API that records operations (schedule or add operation) while the service is being queried as an oracle by a contract.

Common situations: One code path shared between query and act in an application; services ported from contract-style code that always enqueue follow-up operations; framework hooks that auto-schedule during any handler; forgetting that oracle queries are read-only.

Related errors


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