Appearance
HookNamespace
Purpose: a 256-bit value that partitions a hook's persistent Hook State into independent key-spaces. Two hooks installed under different namespaces never see each other's state even if their state keys collide; the same namespace value is also the target a hsoNSDELETE operation wipes.
Field declaration: UINT256, code 32.
It is soeOPTIONAL on sfHook and soeREQUIRED on ltHOOK_DEFINITION; every definition has a default namespace baked in at creation time.
Do not confuse with sfHookNamespaces (plural). That is a separate VECTOR256 field stored directly on ltACCOUNT_ROOT, tracking the set of namespaces the account currently has live Hook State in as state is written and capped at 256, enforced with TOO_MANY_NAMESPACES. sfHookNamespace (singular, documented here) is the per-hook-entry/per-definition field that selects which of those namespaces a given hook reads/writes.
Effective namespace resolution — entry overrides definition. Whenever code needs "the" namespace for an installed hook, the pattern is: use the per-account sfHook entry's own sfHookNamespace if present, otherwise fall back to the ltHOOK_DEFINITION's sfHookNamespace. This applies in the fee/execution path, the grant-matching path (see HookGrants), and hook-update bookkeeping.
hsoCREATE requires it.
It produces temMALFORMED when omitted.
hsoINSTALL/hsoUPDATE: optional, storage-optimized like HookOn. Both operations may supply a different namespace than the target definition's default. It is written to the per-account sfHook entry only if it differs from the resolved definition namespace; if it matches, any stale override on the entry is cleared instead — the same pattern documented in HookOn's "Effective value resolution" section. Resetting HookNamespace back to the definition's zero value removes sfHookNamespace from the stored entry.
hsoDELETE forbids it; hsoNSDELETE requires it — opposite rules on the two operations that both use sfCreateCode-adjacent flags.hsoDELETE rejects a transaction that includes sfHookNamespace alongside empty sfCreateCode (DELETE_FIELD, verified as temMALFORMED). hsoNSDELETE requires both sfHookNamespace and sfFlags == hsfNSDELETE to be present (NSDELETE_FIELD/NSDELETE_FLAGS), and forbids every other field — grants, parameters, HookOn/HookOnIncoming/HookOnOutgoing, HookCanEmit, HookApiVersion, HookName.
Operation inference depends on the (sfHookNamespace, hsfNSDELETE) pair, not on the hook chain slot's actual current namespace.
Because hsoNSDELETE is inferred the instant both are present, the sfHookNamespace value in an hsoNSDELETE entry is an arbitrary target namespace, not necessarily the one currently associated with any hook at that chain slot — you can NSDELETE a namespace that belongs to a different hook in the same account entirely, or one left over from a hook that has since been deleted; namespace deletion doesn't check that a live hook still references it.
hsfNSDELETE as a side effect on other operations. The flag bit hsfNSDELETE is not exclusive to the hsoNSDELETE operation — it is also in hsoDELETE's allowed-flags whitelist (hsfOVERRIDE | hsfNSDELETE | hsfCOLLECT), and nothing stops it appearing (unchecked) in hsoCREATE/hsoINSTALL flags either. The apply loop handles this generically, for any operation except NOOP/INVALID.
So a hsoDELETE (delete the hook) combined with Flags: hsfOVERRIDE|hsfNSDELETE destroys the outgoing hook's old namespace in the same transaction — deleting the hook and wiping its state atomically. op == hsoNSDELETE is the only branch that destroys the newly specified namespace; every other operation that carries the flag destroys the slot's prior namespace instead.
Deletion mechanics. Namespace deletion is called once per unique namespace queued for destruction, after all other ledger insert/update work for the transaction. If the namespace's state directory doesn't exist or is already empty, this is treated as success (opportunistic delete — "the flag can't block the SetHook transaction just because... the namespace was deleted in the previous transaction"). Otherwise it walks the directory and erases every ltHOOK_STATE entry found.
fixNSDeletegates two behaviors (ctx.rules.enabled(fixNSDelete), with the feature declared in the server source):- A per-transaction cap. With the fix, at most 256 entries are deleted per
SetHook; if more remain, the function returnstesPARTIALand the caller must resubmit an identicalhsoNSDELETEto continue. Without the fix, the loop is unbounded and deletes the entire namespace in one pass. - Owner-reserve refund. With the fix,
sleAccount'ssfOwnerCountis reduced according to the number of entries deleted. Without the fix, the reserve is never refunded even though the state entries are gone — a real pre-fix reserve accounting bug, confirmed by the verified example below.
- A per-transaction cap. With the fix, at most 256 entries are deleted per
sfHookStateCounton the account is decremented by the number of entries actually deleted regardless of the fix.- The namespace is removed from the account's
sfHookNamespacestracking vector only on a complete (non-partial) delete.
Verified example — pre-fix reserve leak vs. post-fix partial delete: a hook writes 256 Hook State entries (sfOwnerCount reaches 258). One hsoNSDELETE transaction is submitted for that namespace:
fixNSDelete | Result | Directory after | sfOwnerCount after |
|---|---|---|---|
| disabled | tesSUCCESS (single pass, unbounded) | gone | still 258 (reserve not refunded — bug) |
| enabled | tesPARTIAL (capped at 256) | still present | 2 (256 entries refunded) |
| enabled, resubmitted | tesSUCCESS | gone | 1 (fully refunded) |
Common mistakes:
- Assuming
hsoNSDELETE'ssfHookNamespacemust match a namespace currently in use by an installed hook. It doesn't — the operation deletes whatever namespace value you give it, opportunistically succeeding even if nothing exists there. - Assuming
hsfNSDELETEonly does something when the operation is literallyhsoNSDELETE. It also fires as a side effect on any other operation (notablyhsoDELETE), but targets the chain slot's previous namespace rather than a namespace named in the same transaction. - Relying on pre-
fixNSDeletebehavior for reserve accounting — before the fix, deleting a namespace's state does not return the owner reserve it was consuming. - Forgetting
tesPARTIALrequires a resubmission with the samesfHookNamespace(andhsfNSDELETEflag) to finish deleting namespaces larger than 256 entries.
Related documents
- HookOn — the storage-optimization pattern (
sfHookentry overrides definition default) thatsfHookNamespacealso follows. - HookGrants — grant matching resolves the same entry-vs-definition namespace before checking a grant's hash/authorize.
- Flags —
hsfNSDELETEand the othersfHook-level flags. - Operation Matrix — full per-operation legality table.