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 calls | Offchain workers |
|---|---|
| Transactional | Best-effort |
| Automatic rollback on error | No rollback |
| State changes are atomic | Partial execution is possible |
| Errors bubble naturally | Errors 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
Loggingand returned asLogger. - 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 -> ElectEach 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§
Sourcefn can_run(&self) -> Result<(), Self::Logger>
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.
Sourcefn run_service(&self) -> Result<(), Self::Logger>
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§
Sourcefn on_ran_service(&self)
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.