Architecture
How a Dusk node is put together, and the seams that make one platform span very different hardware.
Crate layout
A node is assembled from the core crates (dusk_core, dusk_capnp,
dusk_program, …), a set of programs, and one impl. The
SDK Reference › Crates page lists each crate and its
role; this page is about how they interact at runtime.
Client / server split
The single most load-bearing rule in the codebase: the server is no_std, the
client is std. A program crate contains both sides, separated by a client
Cargo feature:
- Server side - the unconditional code, compiled into the node. It must be
no_std-clean:allocinstead ofstd, no std-only dependencies. - Client side - everything behind
#[cfg(feature = "client")], compiled into the CLI.stdis fine here.
The same rule extends outward: dusk_core and dusk_program are no_std; impls
(dusk_nix) and the client crates (dusk_cli, dusk_py) are std, as is the
client half of every program crate - the prompt under base/sh/src/client/
included.
A client session
A node has exactly one namespace,
created at startup. The node's init process starts a detached sh to run the
init script it is handed - for the node artifact, by default, nightfall -l 9090 - and
nightfall binds the TCP listener and, for each
incoming connection, spawns a dusk_core session task that shares that
one namespace - so all connected clients see the same processes. The session
wraps a DuskServer as a Cap'n Proto bootstrap capability and runs an RPC system
over the stream. The client now holds a Dusk capability and can call into the
node.
Dusk.process → Dusk.run
Starting work is two steps. Dusk.process(programArgs) gets the node's
LauncherSet from the
registry in dusk_core::launchers, keyed by namespace id, and launches a
process - the
launcher is chosen by a local read of the args' program id (no network call).
Then either Dusk.run(process) spawns it as its own task (a daemon that outlives
the session) or process.run() runs it inside the calling session.
Link-time dispatch
Two seams are resolved by the linker rather than by data:
- The driver shim.
dusk_corecallsunsafe extern "Rust"symbols (_dusk_hostname,_dusk_tid,_dusk_exit) that the impl defines viadusk_driver_impl!.dusk_coredepends on no impl; the impl satisfies the symbols. See Drivers & Impls. - The shell entry table. Shell-invocable programs register into a
#[distributed_slice]SH_ENTRIEStable at link time - in the client binary, where shell entry resolution happens - so the shell can resolve a shell entry name to a program without any registry being built at runtime.
Both seams are what let the pieces be mixed and matched - programs and impls into a node, shell-invocable programs into a client - without any of them depending on each other directly.