Skip to content

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, otherwise tecREQUIRES_FLAG ("can't/can set extant hook hash over other hook without/with override flag").
  • The target ltHOOK_DEFINITION must exist at apply time too, otherwise tecNO_ENTRY (tested with an install against a nonexistent DEADBEEF... 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, and sfHookName are all accepted on install and are stored on the per-account sfHook entry 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:

StepActionaccept definition refcountrollback definition refcount
1alice creates accept at position 01—
2alice 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)—
3alice 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 sfHookHash and sfCreateCode in one sfHook entry — always temMALFORMED (hsoINVALID), regardless of whether sfCreateCode is empty or not.
  • Assuming hsoCREATE always allocates a new ltHOOK_DEFINITION. If the bytes hash to an already-existing definition, it silently becomes an install of that definition (subject to install's hsfOVERRIDE rule if something else already occupies the target chain slot).
  • Forgetting that preclaim checks hash existence for every sfHook entry with sfHookHash present, before any ledger mutation — a transaction with one valid install and one install-by-nonexistent-hash fails entirely with terNO_HOOK, nothing is partially applied.
  • HookGrants — sfHookGrant also carries a required sfHookHash, used for a different purpose (authorizing state_foreign_set access), not chain installation.
  • Operation Matrix — full per-operation table including where sfHookHash is required/forbidden.
  • README — field-location table and sfHook/ltHOOK_DEFINITION templates.