linera-io/linera-protocol · error · ExecutionError
OutdatedUpdateStream
OutdatedUpdateStream
Error message
ExecutionError::OutdatedUpdateStream
What it means
UpdateStream is an internal system operation that chain clients create to advance a subscriber's position in a publisher's event stream (linera-core/src/client/chain_client/mod.rs:706). Execution (system.rs:631) requires the stored per-application next index for that subscription to be strictly lower than the incoming next_index; an operation that carries no progress is rejected as OutdatedUpdateStream to keep subscription state monotonic.
Source
Thrown at linera-execution/src/system.rs:631
self.committee_hash.set(Some(event_data.blob_hash));
self.epoch.set(epoch);
}
UpdateStream {
application_id,
chain_id,
stream_id,
first_index,
next_index,
} => {
let subscriptions = self
.event_subscriptions
.get_mut_or_default(&(chain_id, stream_id.clone()))
.await?;
let app_next_index = *subscriptions
.applications
.get(&application_id)
.ok_or(ExecutionError::UnsubscribedUpdateStream)?;
ensure!(
app_next_index < next_index,
ExecutionError::OutdatedUpdateStream
);
txn_tracker.add_stream_to_process(
application_id,
chain_id,
stream_id.clone(),
app_next_index,
first_index,
next_index,
);
subscriptions
.applications
.insert(application_id, next_index);
subscriptions.recalculate_min();
let index = next_index
.checked_sub(1)
.ok_or(ArithmeticError::Underflow)?;View on GitHub (pinned to 6c226ddcb3)
Solutions
- Rebuild the operations from the chain's current state immediately before signing: linera-core itself reads get_stream_indices and filters with app_index < next_index, so avoid caching these operations.
- Deduplicate UpdateStream operations in a batch and drop any already covered by the chain's stored indices.
- Run only one writer per chain for stream processing (or serialize submissions) so updates are not raced.
- In tests, derive first_index/next_index from the publisher's actual stream counts instead of constants.
Example fix
// before: op built from stale/cached state
let op = SystemOperation::UpdateStream { application_id, chain_id, stream_id, first_index: 0, next_index: 5 };
// after: recompute from the node right before submitting
let counts = local_node.get_stream_indices(publisher_chain_id, stream_id.clone()).await?;
let op = SystemOperation::UpdateStream {
application_id, chain_id: publisher_chain_id, stream_id,
first_index: counts.first_index, next_index: counts.next_index,
}; // and skip when counts.next_index <= stored subscription index Defensive patterns
Strategy: validation
Validate before calling
// recompute from the node immediately before signing; skip stale updates
let counts = local_node.get_stream_indices(publisher_chain_id, stream_id.clone()).await?;
let stored = subscriptions.applications.get(&app_id).copied().unwrap_or(0);
let ops = if counts.next_index > stored {
vec![SystemOperation::UpdateStream {
application_id: app_id, chain_id: publisher_chain_id, stream_id,
first_index: counts.first_index, next_index: counts.next_index,
}.into()]
} else { vec![] }; // nothing to do: avoids OutdatedUpdateStream Try / catch
match result {
Err(ExecutionError::OutdatedUpdateStream) => {
// rebuild the operation batch from fresh chain state and resubmit;
// the stale operation itself must be discarded, not retried
}
other => other,
} Prevention
- Never cache UpdateStream operations across block submissions.
- Run a single writer per chain for stream processing to avoid duplicate advances.
- In tests, compute indices from actual stream state rather than hardcoded numbers.
When it happens
Trigger: Submitting an UpdateStream whose next_index is less than or equal to the subscription's stored index: operations built from stale chain state, the same update included twice in one batch, or two processes racing to process the same subscription so the second submission is already covered.
Common situations: Re-running or replaying stream-processing blocks; tests constructing UpdateStream operations by hand with hardcoded indices; running multiple clients/workers against the same chain concurrently; a client that fetched stream counts, then delayed long enough for another writer to advance the subscription.
Related errors
- EventsNotFound
- Invalid owner address
- no signer found for owner ${owner}
- Invalid owner address
- MetaMask is not available
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/f8ed9a2b6348aab5.
Report an issue: GitHub.