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.
At a glance
Section titled “At a glance”| Machine id | ordered-log, built into Yano |
| Maturity | stable |
| Commands | none: any non-empty body, on any topic that does not start with ~ |
| State | per message, sha256("~yano/finalized-message/v1/" ‖ message id) → [1, height, index, topic, sender]; plus one tip record |
| Proof subject | finalized-message-v1: coordinate message-id, claim recorded |
| Result codes | none |
| Events | ordered-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.
How it works
Section titled “How it works”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”.
- 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 - 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.
- Record the position
ordered-logwrites 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 Key Value sha256("~yano/finalized-message/v1/" ‖ id)[1, height, index, topic, sender]sha256("~yano/finalized-message/v1/" ‖ "tip")height - 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 - Prove it Ask for the typed proof subject
finalized-message-v1with the message id and the claimrecorded. 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.
- 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 - 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 Key Value 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.
- Submit Your application posts to topic
~orders. - Refused at ingress The member refuses the topic before signing, with 400. This is a framework rule;
ordered-logitself has no admission rules.- passes: Body is not empty and within
max-message-bytes - fails: Topic does not start with
~400
- passes: Body is not empty and within
- your application or verifier
- member node
- deterministic state machine
- final
- refused
Try it
Section titled “Try it”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:
./yano.sh appchain cluster start 3./yano.sh appchain cluster submit orders-chain order-created \ '{"orderId":"A-1001","quantity":4}' --node 1The 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:
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:
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; doneThen request the typed proof:
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\"}");Configure
Section titled “Configure”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: 1000ordered-log has no settings of its own. Capacity, expiry, retention and
anchoring use the common chain settings.
Bounds
Section titled “Bounds”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.
Advanced
Section titled “Advanced”- Only
ordered-loghas these records by default. Another machine gets them only when its chain setsmachines.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.v1withtopic,sender,messageId,height,indexandbodyHash. - When to choose something else. Use
kv-registryfor current values,doc-trailfor a history per entity, or a plugin for business rules.