Tutorial 2 — A Provable Shared Registry
Open this outcome in App-Chain Studio
- Level: beginner to intermediate
- Time: about 15 minutes
- Outcome: write owner-controlled data, show that an unauthorized write is a final no-op, and prove the current value with an MPF proof.
The default cluster hosts registry-chain, which runs the stock
kv-registry state machine. The first
member to write a key owns it; only that member can update or delete it.
You need curl, jq and python3. Run every command from the top-level
directory of the extracted release, the directory that contains yano.sh.
What you're seeing: the commands of this tutorial, one step at a time. Watch the state table: the root changes with every block, but the proven entry changes only when its owner writes.
- Write as node 1 The launcher sends PUT
supplier-42=activethrough node 1. Node 1 signs the envelope, so its member key becomes the owner when the block is final../yano.sh appchain cluster kv registry-chain set supplier-42 active --node 1 - Wait for the proof Poll the proof endpoint until the key is
PRESENT. The response carries the height, the state root, the value and an MPF proof of that value under that root.What the proof shows (illustrative heights and roots) Height State root supplier-424 1a2b…[node 1 key, "active"] - Write as node 2 You send PUT
supplier-42=suspendedthrough node 2 over REST, then wait until that message is final. Node 2 does not own the key, so the decision isKV_NOT_OWNERand nothing is written.- fails: The key has no entry, or the sender owns it
KV_NOT_OWNER
- fails: The key has no entry, or the sender owns it
- Compare Read the proof again. The entry is byte-for-byte the same. The root is not: every block also writes a record of its own messages root, so the root moves with every block. Compare the proven entry, not the root.
Before and after the rejected write Height State root supplier-424 1a2b…[node 1 key, "active"]5 7c9d…[node 1 key, "active"] - Update as owner Node 1 writes
suspended. It owns the key, so the value changes, and the next proof shows it../yano.sh appchain cluster kv registry-chain set supplier-42 suspended --node 1- passes: The key has no entry, or the sender owns it
After the owner’s write Height State root supplier-424 1a2b…[node 1 key, "active"]5 7c9d…[node 1 key, "active"]6 e04f…[node 1 key, "suspended"]
- your shell
- member node
- deterministic state machine
- no effect
1. Start the default cluster
Section titled “1. Start the default cluster”Skip this section if Tutorial 1’s cluster is still running.
export YANO_CLUSTER_DIR=/tmp/yano-tutorial-registry./yano.sh appchain cluster start 32. Create a registry entry
Section titled “2. Create a registry entry”The commands below reach node n on HTTP port HTTP_BASE + n. Set
HTTP_BASE to the first port of the range the launcher printed. It is 7070
unless the launcher printed a line such as
HTTP range 7070-7072 unavailable; using 7073-7075:
HTTP_BASE=7070Write through node 1, so node 1 becomes the owner:
./yano.sh appchain cluster kv registry-chain set supplier-42 active --node 1The command body is CBOR, but the launcher builds it for you. 202 means only
that node 1 queued it. Wait until the key appears in a proof:
BASE=http://127.0.0.1:$HTTP_BASE/api/v1/app-chain/chains/registry-chainKEY_HEX=$(python3 -c 'print("supplier-42".encode().hex())')
until curl -sf "$BASE/state/proof/$KEY_HEX" | jq -e '.presence == "PRESENT"' >/dev/null; do sleep 1done3. Prove the current value
Section titled “3. Prove the current value”The state key is the UTF-8 business key itself:
curl -s "$BASE/state/proof/$KEY_HEX" | jq '{committedHeight, stateRoot, presence, valueHex}'valueHex is CBOR [owner, value]: 82 opens an array of two, 5820 is
followed by node 1’s 32-byte member key, and 46616374697665 is the 6-byte
value active. The response also carries proofWireHex, an MPF proof that
this value sits under stateRoot. Showing JSON from one node is not
independent verification; a verifier checks the proof against a root it
trusts.
4. Try an unauthorized write
Section titled “4. Try an unauthorized write”Record the proven entry and the root, then write the same key through node 2 over REST, so you can wait for exactly that message:
ENTRY_BEFORE=$(curl -s "$BASE/state/proof/$KEY_HEX" | jq -r .valueHex)ROOT_BEFORE=$(curl -s "$BASE/state/proof/$KEY_HEX" | jq -r .stateRoot)
TOOL=docs/appchain/tutorials/tools/stdlib_command.pyPUT_HEX=$(python3 "$TOOL" kv-registry put supplier-42 --value-text suspended)
MESSAGE_ID=$(curl -sS -X POST \ "http://127.0.0.1:$((HTTP_BASE + 2))/api/v1/app-chain/chains/registry-chain/messages" \ -H 'Content-Type: application/json' \ -d "{\"topic\":\"kv-registry.command.v1\",\"bodyHex\":\"$PUT_HEX\"}" | jq -r .messageId)
until curl -sf "$BASE/messages/$MESSAGE_ID" >/dev/null; do sleep 1; donecurl -s "$BASE/messages/$MESSAGE_ID" | jq '{height, sender}'The message is final at the printed height, sent by node 2. Now compare:
ENTRY_AFTER=$(curl -s "$BASE/state/proof/$KEY_HEX" | jq -r .valueHex)ROOT_AFTER=$(curl -s "$BASE/state/proof/$KEY_HEX" | jq -r .stateRoot)
[ "$ENTRY_BEFORE" = "$ENTRY_AFTER" ] && echo "entry unchanged"[ "$ROOT_BEFORE" != "$ROOT_AFTER" ] && echo "state root changed"You should see both lines. The entry did not change: node 2 does not own the
key, so the decision was KV_NOT_OWNER. The root did change, and it would
have changed anyway: every block also records its own messages root in state.
So compare the proven entry, never the root, to decide whether a command had
an effect. A final message, a 202, or a growing height is not the
authorization decision; the resulting state is.
5. Update as the owner
Section titled “5. Update as the owner”./yano.sh appchain cluster kv registry-chain set supplier-42 suspended --node 1
until [ "$(curl -s "$BASE/state/proof/$KEY_HEX" | jq -r .valueHex)" != "$ENTRY_AFTER" ]; do sleep 1donecurl -s "$BASE/state/proof/$KEY_HEX" | jq '{committedHeight, stateRoot, valueHex}'The value now ends in 73757370656e646564, suspended. The owner key is the
same.
6. Clean up
Section titled “6. Clean up”./yano.sh appchain cluster cleanunset YANO_CLUSTER_DIRWhere this pattern fits
Section titled “Where this pattern fits”- allow-lists shared by several organizations;
- product, asset, credential or DID-document registries;
- shared configuration with explicit ownership;
- a current pointer whose exact value must be proved to a third party.
It does not provide organization roles, multi-party governance or document
indexing. Use authenticated-map,
the role workflow, or a plugin when the authorization is richer than “first
writer owns this key”.
Go deeper
Section titled “Go deeper”- The
kv-registryreference covers configuration, typed proofs, Java encoding and design choices. - Verify
proofWireHexwith the Java client and a trusted root instead of trusting the serving node; Tutorial 7 connects a root to a Cardano anchor. - Continue with choosing a stock state machine.