Skip to content

ordered-log

ordered-log is the state machine built into Yano. It gives a group of members one agreed, final order of opaque events, and a proof of where each event was finalized. It never reads payloads and never rejects a message for business reasons.

This page summarizes the machine for Yano X users. The complete reference is ordered-log in the Yano repository.

Machine idordered-log, built into Yano
Maturitystable
Commandsnone: any non-empty body, on any topic that does not start with ~
Stateper message, sha256("~yano/finalized-message/v1/" ‖ message id) → [1, height, index, topic, sender]; plus one tip record
Proof subjectfinalized-message-v1: coordinate message-id, claim recorded
Result codesnone
Eventsordered-log.message-finalized.v1, declared by its transition kernel for composition

The topic is a label for routing and filtering. It does not create a separate log; one chain can carry many topics.

An event through ordered-logIllustrative data

What you're seeing: one order event on the stock orders-chain, from the HTTP call to a proof of where it was finalized. The full consensus round is in “Life of a message”.

  1. Submit Your application posts a topic and a body to one member. The member checks framework limits, signs the envelope with its member key, pools it and answers 202 with a message id.
    ./yano.sh appchain cluster submit orders-chain order-created '{"orderId":"A-1001"}' --node 1
  2. Order and certify The leader puts the message in a block, every member re-executes it, and a threshold of members certifies the block. Only then is the message final.
  3. Record the position ordered-log writes one record per message, keyed by a hash of a reserved namespace and the message id, and updates a tip record. It never decodes the body and never rejects a message for business reasons.
    ordered-log state
    KeyValue
    sha256("~yano/finalized-message/v1/" ‖ id)[1, height, index, topic, sender]
    sha256("~yano/finalized-message/v1/" ‖ "tip")height
  4. Find the message A lookup by id returns 404 until the message is final, then its height, index, topic, sender and body.
    curl -sf http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/messages/$MESSAGE_ID
  5. Prove it Ask for the typed proof subject finalized-message-v1 with the message id and the claim recorded. The answer proves the record under the returned state root. Check that root against evidence you trust.

What if: The same payload twice

Identical bytes submitted twice are two messages.

  1. Submit Your application posts a topic and a body to one member. The member checks framework limits, signs the envelope with its member key, pools it and answers 202 with a message id.
    ./yano.sh appchain cluster submit orders-chain order-created '{"orderId":"A-1001"}' --node 1
  2. Submit again Each envelope carries its own sender sequence and expiry, so the second submission gets a different message id and, once final, its own record. Deduplicating business events is your application’s job.
    ordered-log state
    KeyValue
    record for id 1[1, height, index, topic, sender]
    record for id 2[1, height, index, topic, sender]

What if: A reserved topic

Topics that start with ~ belong to the framework.

  1. Submit Your application posts to topic ~orders.
  2. Refused at ingress The member refuses the topic before signing, with 400. This is a framework rule; ordered-log itself has no admission rules.
    • passes: Body is not empty and within max-message-bytes
    • fails: Topic does not start with ~ 400
  • your application or verifier
  • member node
  • deterministic state machine
  • final
  • refused

The stock local cluster hosts orders-chain, an ordered-log chain. From the top-level directory of the extracted release, start a cluster and submit an event through node 1:

Terminal window
./yano.sh appchain cluster start 3
./yano.sh appchain cluster submit orders-chain order-created \
'{"orderId":"A-1001","quantity":4}' --node 1

The launcher sends the payload as UTF-8 text. To keep the message id, submit over REST instead. Ports 7070, 7071 and 7072 are nodes 0, 1 and 2:

Terminal window
MESSAGE_ID=$(curl -sS -X POST \
http://127.0.0.1:7071/api/v1/app-chain/chains/orders-chain/messages \
-H 'Content-Type: application/json' \
-d '{"topic":"order-created","body":"{\"orderId\":\"A-1001\"}"}' \
| jq -r .messageId)

Use "bodyHex" instead of "body" for binary payloads. 202 means queued, not final. Wait until the lookup succeeds:

Terminal window
until curl -sf \
"http://127.0.0.1:7070/api/v1/app-chain/chains/orders-chain/messages/$MESSAGE_ID" \
| jq '{height, index, topic, sender}'; do sleep 1; done

Then request the typed proof:

Terminal window
curl -sS -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 "$(jq -nc --arg id "$MESSAGE_ID" \
'{coordinates:{"message-id":$id}, view:"latest",
claim:{claimId:"recorded",operands:{}}, includeEvidence:false}')" \
| jq '{stateRoot:.proof.stateRoot, presence:.proof.presence, claim:.claimResult.satisfied}'

The proof binds the record to the returned state root. For an audit, check that root against evidence you trust, such as a Cardano anchor, rather than the node that served it.

From Java, use AppChainClient from yano-x-client:

var client = AppChainClient.builder("http://127.0.0.1:7071/api/v1")
.chainId("orders-chain")
.build();
var submitted = client.submitText("order-created", "{\"orderId\":\"A-1001\"}");

orders-chain in the stock cluster file shows the full chain entry, including the three state-identity settings:

yano:
app-chain:
chains[0]:
chain-id: "orders-chain"
state-machine: ordered-log
state:
commitment-profile: mpf-blake2b256-v1
format-fingerprint: 91ee14091200f1e24659112d640e877e9177779dcc81dd06117f013e9190082b
genesis-id: c2b9c92a865dfa7c218a1a6e49f1dd88163372e40466009876458c01609d0d70
membership:
mode: governed
block:
interval-ms: 1000

ordered-log has no settings of its own. Capacity, expiry, retention and anchoring use the common chain settings.

The framework, not the machine, refuses a submission when:

  • the body is empty or larger than max-message-bytes (65,536 by default);
  • the topic is longer than 256 UTF-8 bytes, contains NUL, or starts with ~;
  • the member’s pending pool is full (429).

Submitting the same bytes twice creates two messages with two ids. Business deduplication, schemas and transitions need another machine or a plugin.

  • Only ordered-log has these records by default. Another machine gets them only when its chain sets machines.finalized-message-index.enabled: true. By default every chain, whatever its machine, also records each block’s messages root, so the state root changes with every block.
  • Composition. The transition kernel emits ordered-log.message-finalized.v1 with topic, sender, messageId, height, index and bodyHash.
  • When to choose something else. Use kv-registry for current values, doc-trail for a history per entity, or a plugin for business rules.