State and Execution
Covered in this page
Section titled “Covered in this page”This page covers what a “state” actually is on Ootle, and how a transaction changes it:
- The state model:
SubstateId→ versionedSubstate. - 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/Downtransition model, and whyDownis 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.
The state model
Section titled “The state model”Ootle state is a map from an identity to a versioned value:
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.
Ten kinds of substate
Section titled “Ten kinds of substate”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:
pub enum SubstateId { Component(ComponentAddress), Resource(ResourceAddress), Vault(VaultId), ClaimedOutputTombstone(ClaimedOutputTombstoneAddress), NonFungible(NonFungibleAddress), TransactionReceipt(TransactionReceiptAddress), Template(PublishedTemplateAddress), ValidatorFeePool(ValidatorFeePoolAddress), Utxo(UtxoAddress), ConfidentialOutput(ConfidentialOutputAddress),}| Substate | Holds |
|---|---|
| Component | An instance of a template: its state, access rules, and vault references. |
| Resource | A resource definition: kind (fungible/NFT/confidential/stealth), divisibility, total supply, access rules. |
| Vault | A container of a specific resource inside a component. |
| Template | A published WASM template — a global substate. |
| NonFungible | A single NFT. |
| Utxo | An unspent output, including stealth outputs. |
| ConfidentialOutput | A confidential output created by a burn claim. |
| ClaimedOutputTombstone | A marker recording that a commitment has been claimed, preventing re-claim. |
| TransactionReceipt | The receipt for a transaction. |
| ValidatorFeePool | Accumulated fees for a validator. |
Components, resources, vaults
Section titled “Components, resources, vaults”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:
| Container | Holds | Is it a substate? |
|---|---|---|
| Component | its own state, its access rules, and references to its vaults | Yes — the persistent instance. |
| Vault | the amount of exactly one resource | Yes — lives inside a component. |
| Bucket | an amount of one resource, transiently | No — it exists only during execution. |
| Resource | the definition: kind, divisibility, supply, access rules | Yes — separate from any vault holding it. |
| NonFungible | one NFT, belonging to a resource | Yes. |
| Utxo | one unspent stealth output, belonging to a resource | Yes. |
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.
Addresses and shards
Section titled “Addresses and shards”The address is a commitment to identity
Section titled “The address is a commitment to identity”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:
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 bits: it identifies both which substate and which
version of it.
From address to shard
Section titled “From address to shard”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:
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.
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.
The state tree
Section titled “The state tree”Each shard maintains a Jellyfish Merkle Tree over its substate addresses. The state tree crate wraps a Jellyfish implementation:
pub use tari_jellyfish::*;A Merkle tree gives two things a blockchain state store needs:
- 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.
- 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.
Versioning: Up and Down
Section titled “Versioning: Up and Down”A transaction’s effects are summarized as a SubstateDiff: the substates written (up) and the
version numbers retired (down), plus any fee withdrawals:
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:
| Transition | Effect |
|---|---|
| Up | A new version of a substate is written, with version + 1. |
| Down | A substate version is marked unavailable at the consensus level. |
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.
The TVM: execution
Section titled “The TVM: execution”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).
Deterministic metering
Section titled “Deterministic metering”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:
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.
From metering to fees
Section titled “From metering to fees”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.
The instruction flow
Section titled “The instruction flow”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:
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- Fee instructions run first. They withdraw funds and pay the fee.
- The state is checkpointed at the end of fee payment.
- 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.
Epoch checkpoints and the base layer
Section titled “Epoch checkpoints and the base layer”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).
Summary
Section titled “Summary”- State is a map from a typed
SubstateIdto a versionedSubstate{substate, version}. - There are ten substate variants; templates are global, and buckets are not substates at all.
- A
SubstateAddressisObjectKey(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.
Upwrites a new version;Downretires 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.