linera-io/linera-protocol · error · WorkerError
Fast blocks cannot query oracles
Error message
Fast blocks cannot query oracles
What it means
Fast rounds let super owners finalize blocks without a full quorum, so validators must be able to verify the block purely from its content. After executing the proposal, try_handle_block_proposal checks that a fast-round block produced no oracle responses (block.has_oracle_responses()); any oracle query in the outcome makes fast-path validation impossible and the proposal is rejected with FastBlockUsingOracles.
Source
Thrown at linera-core/src/chain_worker/state.rs:2648
self.chain
.remove_bundles_from_inboxes(block.timestamp, true, block.incoming_bundles())
.await?;
let block = if let Some(outcome) = outcome {
outcome.clone().with(proposal.content.block.clone())
} else {
let (executed_block, _resource_tracker, _) = Box::pin(self.execute_block(
block.clone(),
local_time,
round.multi_leader(),
&published_blobs,
BlockExecution::HandleProposal,
))
.await?;
executed_block
};
ensure!(
!round.is_fast() || !block.has_oracle_responses(),
WorkerError::FastBlockUsingOracles
);
let chain = &mut self.chain;
// Don't save the changes since the block is not confirmed yet.
chain.rollback();
// Create the vote and store it in the chain state.
let blobs = self
.get_required_blobs(proposal.expected_blob_ids(), block.created_blobs())
.await?;
let key_pair = self.config.key_pair();
let manager = &mut self.chain.manager;
match manager.create_vote(&proposal, block, key_pair, local_time, blobs)? {
// Cache the value we voted on, so the client doesn't have to send it again.
Some(Either::Left(vote)) => {
self.block_values
.insert_hashed(Cow::Borrowed(vote.value.inner()));View on GitHub (pinned to 6c226ddcb3)
Solutions
- Propose oracle-using blocks in a multi-leader or single-leader round instead of Round::Fast.
- Split the block: oracle-free operations may go fast, oracle queries go through a regular round.
- After trial execution, check has_oracle_responses() on the outcome and downgrade the round before submitting.
Example fix
// before: fast round for a block that queries oracles
let round = Round::Fast;
// after: choose the round based on the trial outcome
let executed = trial_execute(&block).await?;
let round = if executed.has_oracle_responses() {
Round::MultiLeader(0)
} else {
Round::Fast
}; Defensive patterns
Strategy: validation
Validate before calling
// After trial execution, before settling on the fast round.
if round.is_fast() && executed_block.has_oracle_responses() {
// Downgrade to a regular round; validators will reject a fast submission.
round = Round::MultiLeader(0);
} Type guard
// Fast-round eligibility check on an executed block.
fn is_fast_round_safe(block: &Block) -> bool {
!block.has_oracle_responses()
} Try / catch
match node.handle_block_proposal(proposal).await {
Err(NodeError::WorkerError(err)) if matches!(*err, WorkerError::FastBlockUsingOracles) => {
// Re-propose the same block in a non-fast round (e.g. MultiLeader(0)).
}
result => result,
} Prevention
- Default to regular rounds; use fast only for provably oracle-free blocks.
- Run a trial execution and check has_oracle_responses() before choosing Round::Fast.
- Keep oracle-querying operations (timers, price feeds, application oracles) out of fast blocks.
- Treat this rejection as an automatic round downgrade, not a permanent failure.
When it happens
Trigger: Proposing in Round::Fast a block whose execution queries oracles (time or benchmark queries, application service oracles); oracle-dependent operations left inside a block that a super owner submits as fast.
Common situations: Applications that routinely query oracles (timers, price feeds, application oracles) and default to fast proposals on super-owner chains; tests reusing fast blocks that gained oracle-reading operations after an app upgrade.
Related errors
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/edb20c268caaf977.
Report an issue: GitHub.