Skip to content

State and Execution

This page covers what a “state” actually is on Ootle, and how a transaction changes it:

  • The state model: SubstateId → versioned Substate.
  • The ten kinds of substate, and the containers (components, resources, vaults, buckets) built from them.
  • How a 256-bit address space is sharded, and how a substate’s address is formed.
  • The per-shard Jellyfish Merkle Tree and the state root it commits to.
  • Versioning: the Up / Down transition model, and why Down is not deletion.
  • The TVM: the WASM runtime, deterministic metering, and how execution becomes a fee.
  • Why fee instructions run before the transaction’s own instructions.
  • How Ootle anchors its state to the base layer at epoch boundaries.

Ootle state is a map from an identity to a versioned value:

SubstateId⟶Substate{ substate, version }\text{SubstateId} \longrightarrow \text{Substate}\{\ \text{substate},\ \text{version}\ \}

The identity is a SubstateId — a typed address. The value is a Substate, which pairs the payload with a monotonically increasing version (crates/engine_types/src/substate.rs). A transaction does not mutate state in place; it produces a new version of each substate it touched, leaving the old version as history.

A SubstateId is one of ten variants. Two of them are the containers most application code deals with; the rest are system and asset types:

crates/engine_types/src/substate.rs
pub enum SubstateId {
Component(ComponentAddress),
Resource(ResourceAddress),
Vault(VaultId),
ClaimedOutputTombstone(ClaimedOutputTombstoneAddress),
NonFungible(NonFungibleAddress),
TransactionReceipt(TransactionReceiptAddress),
Template(PublishedTemplateAddress),
ValidatorFeePool(ValidatorFeePoolAddress),
Utxo(UtxoAddress),
ConfidentialOutput(ConfidentialOutputAddress),
}
SubstateHolds
ComponentAn instance of a template: its state, access rules, and vault references.
ResourceA resource definition: kind (fungible/NFT/confidential/stealth), divisibility, total supply, access rules.
VaultA container of a specific resource inside a component.
TemplateA published WASM template — a global substate.
NonFungibleA single NFT.
UtxoAn unspent output, including stealth outputs.
ConfidentialOutputA confidential output created by a burn claim.
ClaimedOutputTombstoneA marker recording that a commitment has been claimed, preventing re-claim.
TransactionReceiptThe receipt for a transaction.
ValidatorFeePoolAccumulated fees for a validator.

The three containers that most template code manipulates:

  • A Component is a struct instance with persistent fields. Its substate holds the serialized state plus the access rules that govern who may call its methods.
  • A Resource is the definition of an asset. Its kind is the key distinction for privacy: a Confidential resource hides amounts (balances are commitments), while a Stealth resource hides both amount and owner (funds live as UTXOs, not in vaults). See Privacy and Templates and Assets.
  • A Vault holds a quantity of one resource, inside one component. Moving funds between components is vault-to-vault; the unit of movement on the instruction workspace is the bucket.

The containment relationship is worth stating once, because the vocabulary is circular until you see which thing holds which:

ContainerHoldsIs it a substate?
Componentits own state, its access rules, and references to its vaultsYes — the persistent instance.
Vaultthe amount of exactly one resourceYes — lives inside a component.
Bucketan amount of one resource, transientlyNo — it exists only during execution.
Resourcethe definition: kind, divisibility, supply, access rulesYes — separate from any vault holding it.
NonFungibleone NFT, belonging to a resourceYes.
Utxoone unspent stealth output, belonging to a resourceYes.

Read the middle two rows together: the resource is the definition and the vault is a holding of it, so many vaults across many components can hold the same resource. The resource’s total supply is what makes those holdings add up.


A substate’s address is not a counter — it is derived from its identity. Ootle forms it as a 32-byte object key concatenated with the version as a big-endian u32:

crates/common_types/src/substate_address.rs
pub const LENGTH: usize = ObjectKey::LENGTH + size_of::<u32>(); // 32 + 4 = 36 bytes
pub fn from_object_key(object_key: &ObjectKey, version: u32) -> Self {
// concatenate (entity_id, component_key), and version
buf[..ObjectKey::LENGTH].copy_from_slice(object_key);
buf[ObjectKey::LENGTH..].copy_from_slice(&version.to_be_bytes());
// ...
}

So a SubstateAddress is 256+32=288256 + 32 = 288 bits: it identifies both which substate and which version of it.

The 256-bit object key is the address space, divided into N = 2^k equal shards. The shard number is read from the top k bits of the key:

crates/common_types/src/substate_address.rs
pub fn to_shard(&self, num_shards: NumPreshards) -> Shard {
if num_shards.is_one() { return Shard::first(); }
let shift = u8::BITS - num_shards.as_u32().trailing_zeros();
let mask = u8::MAX << shift;
Shard::from_u32(u32::from((self.0[0] & mask) >> shift) + 1) // +1: shard numbers are 1-indexed
}

Two facts follow, both confirmed in code:

  • The number of preshards is 256 (NumPreshards::current() == P256), so the network has 256 shards, numbered 1 to 256.
  • Shard 0 is the global shard. It holds global substates — templates and other SubstateId::is_global() values. Shards are numbered from 1 precisely because 0 is reserved.
From a bit string to a shardA SubstateAddress is 288 bits. Only the first byte of the object key picks the shard. (Schematic — not to scale.)first bytethe object key’s remaining identity bitsversionu32 BEObjectKey — 32 bytes / 256 bits+ 4 bytesthe first byte, expanded — 8 bits = 256 valueswith 256 preshards, k = 8: every bit is usedshard = byte value + 1, so 1..=256Stable across updatesThe version is the last 4 bytes, so it playsno part in the shard computation. A substatekeeps its shard — and its committee — for life.The address is derived from an identity, not assigned; anyone can compute a substate’s shard from the address alone.

A validator can compute a substate’s shard directly from its address, without any lookup — which is how transactions are routed to the committee that owns the relevant state. See Architecture for how shards are grouped into committees.


Each shard maintains a Jellyfish Merkle Tree over its substate addresses. The state tree crate wraps a Jellyfish implementation:

crates/state_tree/src/lib.rs
pub use tari_jellyfish::*;

A Merkle tree gives two things a blockchain state store needs:

  1. A single commitment per shard — the state root. Bumping any substate changes the root, so a block can commit to the entire state of a shard with 32 bytes.
  2. Inclusion and exclusion proofs — a proof that a substate (or the absence of one) is consistent with a published root. This is what lets a client verify state without trusting an indexer.

At each epoch boundary, a committee creates an EpochCheckpoint carrying a TreeRootSummary for the global shard and for each shard in that committee’s shard group (crates/storage/src/consensus_models/epoch_checkpoint.rs). The checkpoint commits the group’s state at the end of the epoch and is used to validate and synchronize state across epoch changes.


A transaction’s effects are summarized as a SubstateDiff: the substates written (up) and the version numbers retired (down), plus any fee withdrawals:

crates/engine_types/src/substate.rs
pub struct SubstateDiff {
up_substates: Vec<(SubstateId, Substate)>,
down_substates: Vec<(SubstateId, u32)>,
fee_withdrawals: Vec<ValidatorFeeWithdrawal>,
}

Up and Down are transitions applied to a version, not permanent states of an object:

TransitionEffect
UpA new version of a substate is written, with version + 1.
DownA substate version is marked unavailable at the consensus level.
Up and Down are transitions, not statesOne identity, four versions. The identity is fixed; only the version number moves.v1version 1v2version 2v3version 3v4version 4, same idUpUpUpDown — this version is retiredthe identity is not destroyedA later write to the same SubstateId starts a new version in place. There is no Empty state between the Down and the next Up.

The practical consequence: a component can be removed and later a new version created at the same address, and the state tree records the whole history rather than an irreversible deletion.


Ootle executes WASM templates in the Tari Virtual Machine (TVM), built on Wasmer with the Cranelift compiler backend (crates/engine/Cargo.toml enables the cranelift feature).

Blockchain execution must be deterministic and resource-bounded: every validator has to compute exactly the same result and charge exactly the same cost. Wasmer’s Metering middleware enforces this by charging each WASM operator a fixed cost as it executes:

crates/engine/src/wasm/metering.rs
Metering::new(limit, cost_function)
fn cost_function(op: &Operator) -> u64 { /* deterministic per-operator cost */ }

When the meter is exhausted, execution traps. Because the cost function is a pure function of the instruction stream, two validators running the same template with the same inputs exhaust the meter at the same instruction and produce the same result. Gas is therefore counted deterministically, not estimated.

Metered work is converted to a fee through a WasmMeteringRate (crates/engine/src/fees/fee_table.rs). The transaction pays for the work it actually performs — there is no priority bidding layer in the fee model; a transaction either covers its metered cost or is rejected. Fee distribution is covered in Tokenomics.


A transaction is a sequence of instructions. Crucially, it carries two instruction streams: fee instructions and the transaction’s own instructions. The engine executes them in a fixed order:

crates/engine/src/transaction/processor.rs
let (mut runtime, fee_exec_results) =
Self::process_instructions(&template_provider, runtime, instructions.fee, &blobs);
// ... check fee payment ...
// then run the main instructions against a state checkpointed after fee payment
  1. Fee instructions run first. They withdraw funds and pay the fee.
  2. The state is checkpointed at the end of fee payment.
  3. Main instructions run. If they fail, the state is rolled back to the checkpoint — the fee is still charged, but the transaction’s effects are discarded.

This ordering is what makes fee payment unconditional: a transaction cannot avoid paying by failing half-way through its own logic. The checkpoint is why a reverted transaction still costs money but leaves no state change behind.


Ootle’s state is committed by its own consensus, block by block. At an epoch boundary, each committee’s EpochCheckpoint records the global-shard root, the roots for that committee’s shard group, and the end-of-epoch commit proof. The base layer’s role is to be the clock and the membership register: the epoch length and the validator set are read from the base layer through the epoch oracle (crates/epoch_oracles/src/base_layer/oracle.rs).

The division is deliberate: the base layer fixes who may validate and when epochs turn over; Ootle owns what the state is and when a change is final. Ootle does not post its state roots to the base layer for verification — the base layer does not re-execute Ootle transactions (see Overview).


  • State is a map from a typed SubstateId to a versioned Substate{substate, version}.
  • There are ten substate variants; templates are global, and buckets are not substates at all.
  • A SubstateAddress is ObjectKey(32 bytes) ‖ version(u32 BE) = 288 bits.
  • Shards are derived from the top bits of the object key; there are 256 shards, numbered 1–256, with shard 0 reserved for global state.
  • Each shard keeps a Jellyfish Merkle Tree; its root is the shard’s state commitment.
  • Up writes a new version; Down retires one — and a downed substate can be re-created.
  • The TVM runs WASM (Wasmer + Cranelift) with deterministic operator metering that becomes the fee.
  • Fee instructions run first, then the state is checkpointed, then the main instructions run.
  • Each committee’s epoch checkpoint covers its shard group plus the global shard; the base layer supplies the clock and membership but does not receive the checkpoint.

Next: the cryptography that lets state carry hidden value — Privacy.