Trait Routines

Source
pub trait Routines<TimeStamp>: Logging<TimeStamp>
where TimeStamp: Time,
{ // Required methods fn can_run(&self) -> Result<(), Self::Logger>; fn run_service(&self) -> Result<(), Self::Logger>; // Provided method fn on_ran_service(&self) { ... } }
Expand description

Structured execution interface for offchain routines.

Routines provides a disciplined execution model for offchain workers, replacing ad-hoc logic with explicit phases and well-defined failure semantics.

§Why structured routines are needed

Offchain workers are fundamentally different from runtime calls:

Runtime callsOffchain workers
TransactionalBest-effort
Automatic rollback on errorNo rollback
State changes are atomicPartial execution is possible
Errors bubble naturallyErrors must be handled manually

As a result, offchain logic must be structured explicitly to ensure:

  • invariants are checked before execution,
  • routines run to intentional completion,
  • partial state does not silently corrupt future runs,
  • failures are observable and diagnosable.

The Routines trait enforces this structure.

§Execution model

|-------------|
| can_run()   |  <- check invariants, prerequisites
|-----|-------|
      |
      V
|-------------|
| run_service |  <- perform the operation
|-----|-------|
      |
      V
|-------------|
| on_ran_*    |  <- bookkeeping, metrics, logging
|-------------|

Each phase has a distinct responsibility, making offchain logic easier to reason about, test, and evolve.

§Failure semantics

  • Any failure is logged via Logging and returned as Logger.
  • Callers must treat failures as hard stops for the current routine.
  • Subsequent routines may or may not execute, depending on orchestration.

This explicit handling avoids implicit control flow and makes routine dependencies visible.

§Arranging multiple routines

Structured routines compose naturally into pipelines:

Example
-------
Init -> Declare -> Rotate -> Elect

Each routine:

  • validates its own prerequisites,
  • executes independently,
  • leaves the system in a well-defined state.

This allows offchain workers to act as deterministic coordinators rather than monolithic scripts.

§Logging and observability

Because routines run outside the runtime’s transactional model, logging is the primary observability mechanism.

By integrating with Logging:

  • all errors are logged exactly once,
  • routine boundaries are visible in logs,
  • execution can be traced across blocks.

This makes post-mortem debugging and operational monitoring feasible.

§Example usage

let routine = MyRoutine { at: block };

if routine.can_run().is_ok() {
    routine.run_service()?;
    routine.on_ran_service();
}

Required Methods§

Source

fn can_run(&self) -> Result<(), Self::Logger>

Checks whether the routine is allowed to run.

This method must:

  • validate prerequisites,
  • check invariants,
  • avoid mutating state.

It exists to prevent partial execution in environments without rollback guarantees.

Source

fn run_service(&self) -> Result<(), Self::Logger>

Executes the routine’s core logic.

Implementations should assume that can_run has already succeeded and focus solely on performing the intended operation.

Provided Methods§

Source

fn on_ran_service(&self)

Hook invoked after successful execution.

This method is intended for:

  • logging,
  • metrics,
  • bookkeeping,
  • or emitting side effects that must only occur on success.

The default implementation is a no-op.

Dyn Compatibility§

This trait is not dyn compatible.

In older versions of Rust, dyn compatibility was called "object safety", so this trait is not object safe.

Implementors§