Skip to content

Drivers & Impls

A Dusk node is a portable core (dusk_core) plus a thin, platform-specific impl. The impl supplies a driver: the handful of primitives the hardware forces Dusk to ask for. This split is why one fleet can span microcontrollers and supercomputers - the programs and the policy are shared; only the driver changes.

The Driver trait

Driver is a small Send + Sync trait of OS/hardware hooks:

  • hostname() - what this node calls itself,
  • tid() - which thread is calling: the same number for the whole life of a thread, and a different one for every thread running a node at the same time. The logs program uses it to send each record to the node running on that thread. An impl with one node and no threads returns the same number every time,
  • exit(exit_code) - halt the node.

An impl implements Driver and registers it once with dusk_driver_impl!.

The extern-shim pattern

dusk_core is no_std and depends on no impl, yet it has to call into one. It does this through a link-time shim. dusk_driver_impl! defines a lazy_static singleton for the driver plus #[no_mangle] extern functions - _dusk_hostname, _dusk_tid and _dusk_exit. dusk_core::driver declares those same symbols as unsafe extern "Rust" and calls through them. The linker resolves the symbols to whichever impl is linked into the final binary. Callers in dusk_core, programs, and other no_std crates only ever name dusk_core::driver::* and get whatever impl is present - which is exactly what lets a program like sleep be written once and run under the nix impl today, an MCU impl tomorrow.

Drivers do not call themselves

There is one trap worth stating plainly: inside a Driver impl, do not reach for dusk_core::driver::hostname() (or any other driver::*). That call round-trips through the extern shim straight back into your own crate. The shim is the route into the impl for no_std callers; the impl is the destination. From inside the impl, call the underlying OS/hardware primitive directly.

Impls stay lean

dusk_core owns every piece of policy that can be platform-agnostic - schedulers, queues, state machines all belong there, behind a thinner primitive exposed through Driver. An impl should own only what the platform forces: the hostname, how to halt the node, the program set to launch, the facts about itself, its platform and its device it writes into the key-value store as the node starts, plus the platform's embassy-time driver and critical-section implementation. If you find yourself adding a non-trivial state machine to an impl, that is a sign the logic belongs in dusk_core instead.

Time driver

embassy_time::Timer needs an embassy-time-driver providing _embassy_time_now and _embassy_time_schedule_wake. dusk_core does not provide one - each impl pulls in the driver appropriate for its platform. The nix impl enables embassy-time/std (a ready POSIX driver); an MCU impl would enable its HAL's time-driver feature. A program that just wants to sleep uses embassy_time::Timer::after(...) directly; reading or setting a node's wall-clock goes through the Dusk.time / Dusk.settime RPCs, not the Driver.