Assessment reports>Radix>Threat Model>Transaction processor

Transaction processor

When a transaction is being executed by the Radix engine, the initial entry point is a native blueprint called the transaction processor. It acts as a dispatcher for the transaction manifest and invokes the appropriate blueprints to execute the user instructions.

fn start(...) -> Result, RuntimeError> { // ...Prepare system, allocate global addresses let rtn = system.call_function( TRANSACTION_PROCESSOR_PACKAGE, TRANSACTION_PROCESSOR_BLUEPRINT, TRANSACTION_PROCESSOR_RUN_IDENT, scrypto_encode(&TransactionProcessorRunInputEfficientEncodable { manifest_encoded_instructions, global_address_reservations, references, blobs, }) .unwrap(), )?; // ... }

Special privileges

The transaction-processor blueprint is more privileged than other blueprints with certain restrictions on user resources relaxed by exceptions being made for it, both at the engine level as well as at the access-control level of other native blueprints.

Some exceptions made for the transaction processor include the following:

  1. Publishing a new package

    This operation is restricted only to the transaction processor by the following access rule:

FunctionAuth::AccessRules( indexmap!( PACKAGE_PUBLISH_WASM_IDENT.to_string() => rule!(require(package_of_direct_caller(TRANSACTION_PROCESSOR_PACKAGE))), PACKAGE_PUBLISH_WASM_ADVANCED_IDENT.to_string() => rule!(require(package_of_direct_caller(TRANSACTION_PROCESSOR_PACKAGE))), PACKAGE_PUBLISH_NATIVE_IDENT.to_string() => rule!(require(AuthAddresses::system_role())), ) ),
  1. Auth-zone creation

    Auth zones created by a call from the transaction processor start with the initial proofs and virtual resources as opposed to being empty.

let auth_zone = { // TODO: Remove special casing use of transaction processor and just have virtual resources // stored in root call frame let is_transaction_processor_blueprint = blueprint_id .package_address .eq(&TRANSACTION_PROCESSOR_PACKAGE) && blueprint_id .blueprint_name .eq(TRANSACTION_PROCESSOR_BLUEPRINT); let is_at_root = api.kernel_get_current_depth() == 0; let (virtual_resources, virtual_non_fungibles) = if is_transaction_processor_blueprint && is_at_root { let auth_module = &api.kernel_get_system().modules.auth; ( auth_module.params.virtual_resources.clone(), auth_module.params.initial_proofs.clone(), ) } else { (BTreeSet::new(), BTreeSet::new()) }; Self::create_auth_zone(api, None, virtual_resources, virtual_non_fungibles)? };

Dispatcher loop

This loop forms the core of transaction execution, translating high-level manifest instructions into actual state changes in the ledger through the Radix Engine.

fn TransactionProcessorBlueprint::run( manifest_encoded_instructions: Vec, global_address_reservations: Vec, _references: Vec, blobs: IndexMap>, version: TransactionProcessorV1MinorVersion, api: &mut Y, ) -> Result, RuntimeError>

The dispatcher loop consists of three main phases that handle the complete lifecycle of transaction processing:

  1. Initialization

    It creates a worktop component, decodes the manifest instructions from the input bytes, and initializes a TransactionProcessor instance with provided blobs and address reservations.

  2. Main processing loop

    It iterates through each instruction in the manifest sequentially, tracks current instruction index for execution tracing and error handling, processes each instruction based on its variant type through a large match statement, and collects outputs from each instruction execution for the receipt.

  3. Output handling

    Each instruction execution may produce an InstructionOutput. Outputs are collected in order of execution. Any failures during processing will terminate execution and return an error.

Zellic © 2025Back to top ↑