Appearance
HookHash
Purpose: references an existing, already-uploaded WASM binary by its content hash, so a SetHook entry can install that code onto an account's hook chain without resubmitting the bytes (hsoINSTALL), and so hsoCREATE can deduplicate identical WASM across the whole ledger via reference counting.
Field declaration: UINT256, code 31.
soeOPTIONAL on sfHook and soeREQUIRED on sfHookGrant (see HookGrants) and on ltHOOK_DEFINITION, where it identifies the definition object itself.
Operation inference. The operation inference logic reads presence of sfHookHash and sfCreateCode together: if both are present the object is hsoINVALID; if only sfHookHash is present, it's hsoINSTALL.
Both present is hsoINVALID → temMALFORMED ("SetHook must provide only one of sfCreateCode or sfHookHash"), confirmed by dedicated test coverage ("Cannot have both CreateCode and HookHash") and by a direct unit test of inferOperation. sfHookHash alone infers hsoINSTALL.
Existence check (preclaim). For every sfHook entry carrying sfHookHash, preclaim requires the referenced ltHOOK_DEFINITION to already exist on the ledger, independent of which operation it turns out to be. If it doesn't, the transaction fails with terNO_HOOK ("Malformed transaction: No hook exists with the specified hash.").
keylet::hookDefinition hashes under LedgerNameSpace::HOOK_DEFINITION = 'D', matching the ltHOOK_DEFINITION ledger-entry type code 'D'.
Install validation (hsoINSTALL): Parameters and Grants are optionally validated; sfHookApiVersion is forbidden (API_ILLEGAL — see HookApiVersion); Namespace/HookOn/Flags are explicitly allowed ("may be present if the user so chooses").
Apply-time semantics (hsoINSTALL):
- Overriding an existing hook at that chain position requires
hsfOVERRIDE, otherwisetecREQUIRES_FLAG("can't/can set extant hook hash over other hook without/with override flag"). - The target
ltHOOK_DEFINITIONmust exist at apply time too, otherwisetecNO_ENTRY(tested with an install against a nonexistentDEADBEEF...hash). - On success the definition's reference count is incremented, and if an old hook occupied that chain slot, its old definition's reference count is decremented (calling
reduceReferenceCount— see below). sfHookNamespace,sfHookOn/sfHookOnIncoming/sfHookOnOutgoing,sfHookCanEmit, andsfHookNameare all accepted on install and are stored on the per-accountsfHookentry only when they differ from the target definition's own value — the same storage-optimization pattern documented in HookOn. Grants are stored verbatim if the array is non-empty.
Reference counting. ltHOOK_DEFINITION carries sfReferenceCount (UINT64, soeREQUIRED). A pair of internal helper functions maintain it: one decrements the count and reports whether it has reached zero (in which case the definition should be erased), the other increments it.
Every operation that removes a hash from a chain slot — hsoDELETE, hsoUPDATE's implicit replace-in-place path is not applicable (an update never changes sfHookHash), hsoCREATE when it overrides an existing installed hash, and hsoINSTALL overriding an existing hash — calls reduceReferenceCount on the old definition and, if it reaches zero, queues that keylet in keyletsToDestroy for erasure at the end of setHook(). A fresh hsoCREATE sets the new definition's count to exactly 1; a fresh hsoINSTALL of a hash that isn't otherwise referenced would raise it from whatever it already was.
hsoCREATE deduplicates by falling through to hsoINSTALL. If the uploaded WASM's SHA-512Half hash already matches an existing ltHOOK_DEFINITION — either already on the ledger or inserted earlier in the same transaction's sfHooks loop — hsoCREATE does not create a second definition. It reuses the existing one and [[fallthrough]]s directly into the hsoINSTALL case, so install's flag/reference-count/field rules above apply verbatim to a "CREATE" that turns out to be identical code someone else already uploaded.
Verified example — reference count lifecycle:
| Step | Action | accept definition refcount | rollback definition refcount |
|---|---|---|---|
| 1 | alice creates accept at position 0 | 1 | — |
| 2 | alice creates accept again at position 1 (second install target via the same hash) | 3 (2 from alice + 1 from an earlier bob create in the same test) | — |
| 3 | alice overrides position 0 with rollback (hsfOVERRIDE) | 2 (decremented) | 1 (new) |
Verified example — deletion drops a definition at zero: after creating four hooks (accept, makestate, rollback, accept2) and deleting only the third (rollback), the rollback definition's ledger entry is removed entirely because its reference count hit zero, while the other three definitions remain. Deleting the remaining three then removes all their definitions and the account's ltHOOK object itself.
Common mistakes:
- Supplying both
sfHookHashandsfCreateCodein onesfHookentry — alwaystemMALFORMED(hsoINVALID), regardless of whethersfCreateCodeis empty or not. - Assuming
hsoCREATEalways allocates a newltHOOK_DEFINITION. If the bytes hash to an already-existing definition, it silently becomes an install of that definition (subject to install'shsfOVERRIDErule if something else already occupies the target chain slot). - Forgetting that
preclaimchecks hash existence for everysfHookentry withsfHookHashpresent, before any ledger mutation — a transaction with one valid install and one install-by-nonexistent-hash fails entirely withterNO_HOOK, nothing is partially applied.
Related documents
- HookGrants —
sfHookGrantalso carries a requiredsfHookHash, used for a different purpose (authorizingstate_foreign_setaccess), not chain installation. - Operation Matrix — full per-operation table including where
sfHookHashis required/forbidden. - README — field-location table and
sfHook/ltHOOK_DEFINITIONtemplates.