Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Tracing and Debugging

Hooks have no debugger and no stdout. The Hook API’s trace* family of host calls is the only window into what a hook is doing while it runs, and rshooks wraps them in three macros — trace!, trace_num!, and trace_float! — along with two small compile-time helper macros, pad! and pad_left!, that are unrelated to tracing but live alongside these macros in rshooks’s macros module. This page covers all five, and where tracing output actually goes.

trace!, trace_num!, trace_float!

trace!(msg);              // message only
trace!(msg, data);        // message + a byte slice
trace_num!(msg, number);  // message + an i64
trace_float!(msg, value); // message + an XFL value

msg is a raw byte slice, as with accept!/rollback! — there’s no format! available in a no_std hook, so build any dynamic content as bytes ahead of time rather than reaching for string formatting. trace!’s two-argument form’s data is rendered as raw bytes by the underlying host call, not hex — see the hex-dump note below for the raw-layer alternative if you need that.

Underneath the macros, rshooks::api::trace exposes the same three operations as plain functions you can call directly regardless of feature flags:

pub fn trace(msg: &[u8], data: &[u8], as_hex: bool) -> Result<i64>;
pub fn trace_num(msg: &[u8], number: i64) -> Result<i64>;
pub fn trace_float(msg: &[u8], value: XFL) -> Result<i64>;

trace’s as_hex parameter is the hex-dump switch the macros don’t expose: passed as true, data is rendered as a hex dump by the host instead of raw bytes — useful for inspecting something like a raw AccountId or transaction hash byte-for-byte. Reach for rshooks::api::trace::trace(msg, data, true) directly when you need that; the trace! macro’s (msg, data) form always passes as_hex: false.

Where output goes

Trace output is not visible in a transaction’s result or metadata — it goes to the Hook host’s own debug/trace log, visible when the node it runs on is started with trace logging enabled. This makes tracing purely a development and debugging tool: useful while writing and testing a hook against a local or test node, but not something a production hook’s correctness should ever depend on, and not something an external observer (an indexer, a wallet) can see. If a value needs to be visible to the outside world, it belongs in the accept!/rollback! message or code (see Accept, Rollback, and Errors), not a trace call.

Tracing costs bytes and instructions

Every trace call — like every other host call — costs execution instructions and adds to a hook’s worst-case instruction count, and the msg/data bytes it’s given are real bytes the hook has to construct. That’s exactly why the trace!/trace_num!/trace_float! macros are feature-gated: they compile to nothing at all unless rshooks’s own trace feature is enabled.

rshooks = { version = "...", features = ["trace"] }

No feature re-declaration is needed in the hook crate itself beyond enabling it on the rshooks dependency — the macros expand to calls into rshooks::api::trace::__macro_support’s shim functions, which are always present but only forward to the real host call when the feature is on; otherwise they’re a no-op that still consumes (and thus doesn’t warn about) their arguments. This lets a hook crate leave trace!/trace_num!/ trace_float! calls in its source permanently, toggling them on only for a debug build, without editing the call sites at all. The plain functions in rshooks::api::trace (trace, trace_num, trace_float) are unconditional — call those directly instead if you want tracing regardless of feature flags.

pad! and pad_left!

These two macros are unrelated to tracing, but worth knowing alongside it since real hook code often builds byte buffers to hand to trace! (or accept!/rollback!) by hand. Both zero-pad a constant byte string to a fixed-size array, entirely at compile time — no runtime copy or zeroing loop, so no guard is needed for either:

use rshooks::pad;

let padded: [u8; 10] = pad!(b"hello");
assert_eq!(padded, [b'h', b'e', b'l', b'l', b'o', 0, 0, 0, 0, 0]);

pad! right-pads (src at the front, zero bytes at the end); pad_left! is its mirror image, left-padding instead:

use rshooks::pad_left;

let padded: [u8; 10] = pad_left!(b"hello");
assert_eq!(padded, [0, 0, 0, 0, 0, b'h', b'e', b'l', b'l', b'o']);

Both expand to an inline const block, so the source must be a constant expression, and a source longer than the destination is a compile error, never a runtime panic — the array length itself is inferred from the surrounding context (a let binding’s type, a struct field, and so on).

It’s worth noting what these are not for anymore: building a short hook- state key. A [u8; N] (with 1 <= N <= STATE_KEY_LEN) works directly as a StateKeyEncode key at its own real length — the host itself left-pads a short key internally, so a Rust hook doesn’t need to reproduce that padding locally (see Hook State). Reach for pad!/pad_left! when a fixed-size buffer genuinely needs local padding for some other reason — building an already-full-width constant on purpose, or padding a byte string for a use unrelated to hook-state keys.

Where to go next