Skip to content

State and proofs

Application state lives in an authenticated tree. Every final block produces one state root that commits to all of it, identical on every member. A client can check a single record against that root without trusting the node that served it.

You’ll learn: what a state root commits to, what a proof contains, how to read a proof result precisely, and how much a verification actually proves.

Before you start: read Consensus and finality.

Every write a state machine makes is an entry in one authenticated tree. The tree’s type is chosen when the ledger is created and never changes:

ProfileTreeProofs verify
mpf-blake2b256-v1Merkle Patricia Forestry, the same construction as Aiken’s merkle-patricia-forestryOff-chain, and inside a Cardano validator
jmt-blake2b256-v1Jellyfish Merkle treeOff-chain only

The profile, its exact state.format-fingerprint, and a fresh state.genesis-id are configured together as state.commitment-profile, state.format-fingerprint, and state.genesis-id. Together they are the chain’s state identity. The stock cluster configuration uses MPF.

After each block, the root is:

  • identical on every honest member: each one re-executes the block and compares the root byte for byte before it votes;
  • provable: it supports inclusion and exclusion proofs; and
  • anchorable: the same root can be published to Cardano.

Effect records are committed through an effectsRoot entry, so an authorized but not yet executed external action is as provable as ordinary state. New chains also record each block’s [height, messagesRoot, messageCount] in state by default, which ties a message inclusion proof to the state root.

What a state proof containsSimplified
State rootafter block hRoot you trustpinned or anchoredNode 3…on the pathNode a…sibling: hash onlyNode 3c…on the pathNo child 3e…exclusionNode 3f…sibling: hash onlyLeaf 3c1…your recordLeaf 3c9…sibling: hash onlymust equalState rootafter block hRoot you trustpinned or anchoredNode 3…on the pathNode a…sibling: hash onlyNode 3c…on the pathNo child 3e…exclusionNode 3f…sibling hashLeaf 3c1…your recordLeaf 3c9…sibling hash
  • State root → Root you trust (must equal).
  • State root → Node 3….
  • State root → Node a….
  • Node 3… → Node 3c….
  • Node 3… → No child 3e….
  • Node 3… → Node 3f….
  • Node 3c… → Leaf 3c1….
  • Node 3c… → Leaf 3c9….
State root
The root the verifier recomputes. If it equals the root in the same response, the proof is internally consistent, and that is all it shows. It proves your record only against a root you obtained some other way.
Root you trust
A root obtained independently of the node that served the proof: one you pinned, one from a block whose finality certificate you checked against member keys you pinned, or one bound by a Cardano anchor. Where it came from sets the trust level of the result. Trust levels →
Node 3…
On the path from your record to the root. The verifier recomputes this node’s hash from the hash below it and the sibling hashes in the proof.
Node a…
Not on your path. The proof carries only this node’s hash, not its contents; the verifier needs it to recompute the parent.
Node 3c…
On the path from your record to the root. The verifier recomputes this node’s hash from the hash below it and the sibling hashes in the proof.
No child 3e…
An exclusion proof for key 3e…: the path ends at node 3…, which has no child for it. This shows the key is absent from this state. It does not show that the business fact never happened, and a height whose proof material was pruned gives no proof at all: unavailable, not absent.
Node 3f…
Not on your path. The proof carries only this node’s hash, not its contents; the verifier needs it to recompute the parent.
Leaf 3c1…
Your record: a canonical state key and its value. The proof carries the value, and the verifier hashes it itself, so a changed value changes every hash above it and the root no longer matches.
Leaf 3c9…
Not on your path. The proof carries only this node’s hash, not its contents; the verifier needs it to recompute the parent.
Simplified. Real MPF and JMT proofs use their own node layouts and wire formats, but the idea is the same: the record, plus the sibling hashes on its path, recompute one root. Key prefixes are invented.
  • on the path
  • your record
  • sibling: only its hash is in the proof
  • absent key
  • the root you trust

A state proof carries your record, its canonical key and value, and the hashes of the siblings along its path to the root. The verifier hashes the record itself and recomputes each node up to the root. If the recomputed root matches, the proof is consistent with that root, and nothing more. Whether the record is really in the ledger depends on where that root came from.

An exclusion proof shows that a key is absent from the state under that root. It does not show that the underlying business fact never happened: the data set might simply be incomplete. Do not expose a business-level absence check unless a marker, a paired subject, or an authenticated snapshot descriptor proves that the data set is complete.

Raw key proofs force the caller to know the physical storage layout. A proof subject is a typed descriptor that turns an application-level identity into its canonical state key, decodes only the value the verified proof carries, and evaluates only the claims it declares.

Terminal window
curl -s -X POST \
"http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/proof-subjects/finalized-message-v1/proof" \
-H 'Content-Type: application/json' \
-d '{
"coordinates": {"message-id": "<id>"},
"view": "latest",
"claim": {"claimId": "recorded", "operands": {}},
"includeEvidence": false
}' | jq

You supply coordinates in your own vocabulary: a message id, an account, a registry key, a document id. The response includes a trust field with the result’s trust level; see How much does a proof prove?.

finalized-message-v1 is built into ordered-log, which orders-chain uses. Other state machines provide it only when the chain is created with machines.finalized-message-index.enabled=true.

Stock v1 subjects cover finalized block messages, finalized message records, balances, registry entries, document heads, basic approval outcomes, authenticated-map entries, actor roles, role approval outcomes, and composite profile markers. Composite subjects are rebound to their component namespace automatically and get component-qualified ids.

Each of these statements means something different, and a proof response keeps them separate:

ResultWhat it asserts
Message inclusionThe message id is a leaf under one app block’s messagesRoot.
Finality certificateA membership threshold the caller pinned signed the block.
Authenticated block recordFinal state contains [height, messagesRoot, messageCount].
State recordingThe application wrote a typed fact under its canonical state key.
Anchor bindingA trusted Cardano output commits the selected application identity or root.
Claim satisfiedThe value carried by the proof satisfies the selected bounded predicate.
Locally retainedThis node has the bytes now. Durable availability is separately NOT_PROVEN.

A proof always recomputes a root. Its trust level names where the root it was checked against came from. There are five levels:

How much does this proof prove?Decision aid

What you're seeing: the five trust levels name where the root came from. Answer for the root your verifier compared the proof against.

Where did the root you checked the proof against come from?

A proof always recomputes a root. The question is whether anything other than the serving node vouches for that root.

  • From the same response as the proof
    INTERNAL_CONSISTENCY_ONLY

    The proof is consistent with the root it came with, and that root is whatever the node chose. A node’s proof response reports this level by default.

    Establishes
    the proof is well formed
    Does not establish
    that the root is the ledger’s certified root
  • From me: a root I pinned, or member keys and a threshold I pinned
    CALLER_PINNED_ROOT

    The proof matches a root you authenticated yourself: a root you pinned, or the root of a block whose finality certificate verifies under member keys and a threshold you pinned.

    Establishes
    the record is in the state your pinned members certified
    Does not establish
    any public Cardano record
    For example
    Attest --members keys.json; yano-explorer verify --members
  • From a Cardano anchor

    Who read the anchor from Cardano?

    • The node reported a confirmed anchor
      NODE_CONFIRMED_L1_REFERENCE

      The node proved the record at the height of its latest confirmed anchor and says Cardano carries that anchor. That is the node’s claim about Cardano, not something you checked.

      Establishes
      what the node reports about its anchor
      Does not establish
      that the anchor is on Cardano
      For example
      a proof request with "view": "latest-confirmed-anchor"
    • I supplied an anchor datum to my verifier
      CALLER_PINNED_ANCHOR

      The proof is bound to an anchor datum you supplied: its height, root, block hash, genesis, and member set, without the verifier reading Cardano itself. Yano defines this level, but no Yano X command reports it today: the tools that take an anchor datum report the independent level below.

      Establishes
      the record matches the anchor data you supplied
      Does not establish
      that the datum came from Cardano
    • I or my verifier read the anchor output from Cardano
      INDEPENDENTLY_VERIFIED_L1_ANCHOR

      The root is bound to anchor data read from Cardano, independently of any member or node. This is the strongest level.

      Establishes
      the record is in the state the anchored root commits to
      Does not establish
      that the data is still available: availability is always reported separately
      For example
      Attest, Trust Registry, and yano-explorer verify with --anchor-datum-hex, using a datum you read from Cardano

Availability is reported separately, with its own two values: LOCALLY_RETAINED (this node has the bytes now) and NOT_PROVEN (nothing proves the data will stay available).

A node keeps proof material for past heights so it can answer historical proof requests. Optional MPF proof pruning is experimental and off by default; when enabled, it removes material below a retained horizon. Check where retained history starts with oldestProvableHeight:

Terminal window
./yano.sh appchain state identity --url http://node:8080/api/v1 --chain registry
./yano.sh appchain state oldest --url http://node:8080/api/v1 --chain registry

A pruned proof is unavailable. It is not evidence that the fact was absent.

Proofs are portable. The Java client SDK (yano-x-client) verifies them in your application, and the composite client (yano-x-composite-client) also verifies governed-profile finality, one-root MPF, epoch chains, and authorization policy.

Retrieve a proof from a node, then verify the saved proof offline:

Terminal window
# Online: save the proof for a canonical key at a retained height.
./yano.sh appchain state proof --url http://node:8080/api/v1 \
--chain registry --key <canonical-key-hex> --height <height> > proof.json
# Offline: supply a root and identity authenticated independently of that file.
./yano.sh appchain state verify --proof-file proof.json \
--trusted-root <root-hex> --profile <profile-id> \
--genesis-id <64-hex-genesis-id> --chain registry --height <height> \
--root-source caller-pinned

Never take the trusted root from the proof itself. Obtain it from an independently authenticated block, a finality policy you pinned, or a Cardano commitment.

New chains enable state-index:finalized-block-messages-v1 by default. Its enabled flag and configuration digest are part of the chain’s state identity, so an existing database with a different configuration fails to start instead of drifting silently. Disable it only in a new genesis profile.

Rough raw growth for that index, before MPF or JMT node and RocksDB amplification, is one 32-byte key plus a canonical CBOR value of about 38–47 bytes per block:

Block intervalRecords per dayRaw logical growth per day
1 second86,4005.8–6.5 MiB
5 seconds17,2801.15–1.30 MiB
20 seconds4,3200.29–0.33 MiB

Capacity planning must measure backend amplification, compaction, snapshots, and retained proof history on the intended workload.

For a custom subject, implement ProofSubjectProvider next to the module that owns the canonical key and value codec. Each descriptor is closed data: coordinates, fact fields, claims, completeness, verification targets, retention hints, and fixed bounds. Its descriptorDigest goes into the capability manifest, and the runtime activates the provider only when the subject id, version, component id, and digest all match. Resolution must be deterministic and free of side effects.

Next: Effects.