Skip to content

TaskSource: add a default-bodied on_resolution hook so skips are observable #194

Description

@bagelface

Consumer context: gas-killer/roadmap#18. Service re-pins its rev in gas-killer/service#375.

Problem

A skip certificate never reaches the application. Submitter::handle_certified (router/src/submitter.rs:161-165) notifies the sequencer with ResolutionKind::Skipped and returns without touching the handler:

if digest == skip_digest(&self.namespace, height) {
    info!(height, "skip certificate observed; nothing to submit");
    self.notify(height, ResolutionKind::Skipped);
    return;
}

That is correct — there is nothing to submit. But it means the application has no callback on the one resolution path that does not run its handler, so it cannot settle the task that was skipped.

Today the Gas Killer router works around this with a single-slot heuristic: "if the in-flight slot is still occupied when the next task is dequeued, the previous height must have been skipped." That heuristic is precisely what breaks with a window (see gas-killer/service#364 — it can mark a healthy in-flight task as failed and then attribute its payload to a different task).

The alternative for the consumer is a timeout-based sweeper, which works but is coarse: it cannot distinguish "skipped" from "slow" without waiting out (W+1) * ROUND_TIMEOUT.

Scope

Add a default-bodied hook to the TaskSource trait:

#[async_trait::async_trait]
pub trait TaskSource<T: TaskData>: Send {
    async fn next_task(&mut self) -> Option<SequencedTask<T>>;

    /// Called once when a height this source's task was assigned to resolves.
    /// Lets the application settle its own bookkeeping — in particular for
    /// `Skipped`, which never reaches the verification handler.
    async fn on_resolution(&self, _sequenced: &SequencedTask<T>, _kind: ResolutionKind) {}
}

Called from the sequencer's resolution handling, passing the SequencedTask the sequencer already holds for that height, so the application gets its own task back without any identifier needing to traverse SequencedTask/Assignment/Submitter.

  • Default empty body → zero breakage for any other implementor
  • Call it for every ResolutionKind, not just Skipped; a consumer that wants only skips can match on the kind. Uniform is easier to reason about than special-cased.
  • Document that it is called at most once per (task, height) pair, and that a task re-assigned after Foreign/Superseded will see the hook once per height it occupied

Acceptance

  • Trait compiles with existing implementors untouched
  • Deterministic-runtime test: a skip certificate for an assigned height invokes the hook with Skipped exactly once
  • Test: a Foreign resolution followed by re-assignment and success invokes the hook twice with the right kinds

~10 lines. Small, but it is what lets the consumer settle skipped tasks promptly and exactly instead of inferring them from a timer.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestpriority:mediumImportant but not urgentthroughputTask throughput / parallel pipeline (roadmap#18, #19)

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions