Introduction
The Transaction Processor is the native blueprint that turns a transaction manifest into engine calls. Every user transaction on Radix begins in it: it decodes the manifest's instructions, creates the worktop, executes the instructions in order against the Radix Engine, resolves the manifest's named buckets and proofs into real objects at the moment a call needs them, and collects the per-instruction outputs the receipt reports back.
It is deliberately small. The manifest format is where the expressive work happens; the processor is the loop that walks it, and almost every rule it enforces is a rule about keeping resources accounted for while it does.
A package with a single function
The package definition declares one blueprint with one function, Run, and nothing else β no methods, no fields, no collections, no events, no hooks. Like the worktop it is is_transient, so no part of it is ever written to state.
The interesting line is its authorisation. Run is declared FunctionAuth::RootOnly, meaning only the root call frame may invoke it: a component cannot call the transaction processor, and a manifest cannot recursively re-enter one. The source gives the reasoning in a comment β it is a safety precaution to reduce the surface area of attack, and it may be relaxed if the processor is ever verified to be safe. A native blueprint carrying an explicit note that its own restriction is provisional is a useful reminder of where the engine's trust boundaries are drawn by construction rather than by proof.
What Run receives
The input is the manifest after validation, not the raw text a user signed: the SBOR-encoded instruction list, the global address reservations the transaction allocated up front, the set of references the transaction declared, and the blobs it carries. References are passed through explicitly so that the kernel makes them visible inside the processor's own call frame.
Blobs are the one place the processor carries a hard limit, and it is versioned. The V1 package has two minor versions: version zero applied no ceiling at all, while version one caps the total size of blobs per invocation at 1024 * 1024 bytes β one mebibyte β and raises TotalBlobSizeLimitExceeded beyond it. The limit is cumulative across the invocation rather than per blob, and it is counted as the manifest resolves each blob reference, so a manifest that references the same large blob repeatedly is charged for it each time.
The instruction loop
The blueprint itself is a thin wrapper over an intent processor, which holds the queue of remaining instructions, the worktop, the registry of named objects, the running instruction index, and the outputs so far. Initialisation decodes the instructions and creates the worktop; execution pops one instruction at a time, tells the kernel the current instruction index β which is what lets a failure be reported against the instruction that caused it β dispatches it, and pushes an InstructionOutput for it.
Manifest names do not survive into the engine. A named bucket or proof is a numeric id at runtime, and the processor keeps the registries that resolve them, substituting the real object into a call's arguments at the moment the call is made. The errors are correspondingly literal β BucketNotFound, ProofNotFound, AddressReservationNotFound and BlobNotFound all name the id or hash that could not be resolved. A bucket can only be resolved once: taking it out of the registry to pass it into a call is what makes a manifest bucket single-use.
Return values are auto-moved rather than dropped on the floor. After every call the processor walks the returned value's owned nodes: buckets go onto the worktop, proofs are pushed onto the auth zone, and other kinds of owned node are passed over. The pattern a manifest author sees β call a method, then TAKE_FROM_WORKTOP β is this behaviour, not an implicit instruction.
From one thread to many
The original processor is strictly single-threaded, and says so: after running the instruction queue to completion it asserts that no yield occurred, and panics if one did. That is sound for V1 manifests, where there is nothing to yield to.
Cuttlefish's subintents changed the shape. A V2 transaction is executed by a multi-thread intent processor that builds one intent processor per intent, each on its own kernel stack, each with its own auth zone and its own worktop, and drives them as a tree: a yield to a child switches stacks and pushes the caller onto a parent stack, a yield to a parent pops it, and a child that finishes has its stack cleaned up after the value is delivered. VERIFY_PARENT is resolved by switching to the parent's stack and testing an access rule against its auth zone, which is why it cannot be used from a root intent β there is no parent to test. Execution ends with the parent stack empty.
Worth noting for anyone reading the code: on this path the multi-thread processor does not invoke the blueprint's Run export. It sets each thread's call-frame actor to that function directly and drives the intent processor itself, so the blueprint's identity is what appears in the call frame even where the export is not the entry point.
