Quickstart
If you need to trust who wrote content and why it was accepted, run Oak Chain locally and follow one signed write. Start from observable behavior first, then read the architecture. The walkthrough below starts a 3-validator cluster, confirms leader and follower roles, submits one signed write, waits for it to commit, and reads it back.
It runs in mock mode: payments are simulated and signatures are only format-checked. It exercises ordering, replication and durability. It does not exercise payment or signature verification.
After the first win, Consensus Model explains the mechanics; the rest of the guide goes deeper from there.
Prerequisites
- JDK 17 or 21 (a full JDK with
javacis required) - Maven 3.8+
- Node.js 20+ and npm (to build signed test writes)
- IPFS (Kubo) with the
ipfscommand on your PATH. Validators do not start without a reachable IPFS API; the start script launches the daemon if it is not running. git,curlandjq- 8GB RAM minimum
- Free ports: 8090 to 8095 (validator HTTP and Oak standby), 9000 to 9207 (Aeron, UDP), 5001 (IPFS API)
- A sibling workspace containing
oak-chain-infraandjackrabbit-oak
The Four Checks
- Start a 3-validator local cluster
- Confirm one node is
LEADERand two areFOLLOWER - Submit one signed write and wait until it is
COMMITTED - Read it back from the API
If these four checks pass, the local write path works: the cluster orders the write, replicates it, and serves it back.
Start the Cluster
# Create a sibling workspace for the infra and Oak repos
mkdir oak-chain-workspace
cd oak-chain-workspace
# Clone the repositories the workflow expects
git clone https://github.com/somarc/oak-chain-infra.git
git clone https://github.com/somarc/jackrabbit-oak.git
# Install the oak-chain CLI dependencies (used to sign the test write)
cd oak-chain-infra
npm ci
# Use a disposable runtime root; --fresh refuses the default ~/oak-chain
export CLUSTER_RUNTIME_ROOT=/tmp/oak-chain
# Build, then start three validators
./modes/mock/validators/lifecycle/start-cluster.sh --build --fresh
# For faster iteration after the first build (same shell)
./modes/mock/validators/lifecycle/start-cluster.sh
start-cluster.sh is the current public path for a local validator cluster. It starts IPFS if needed, builds jackrabbit-oak (with --build), and starts three validators on HTTP ports 8090, 8092 and 8094. On a fresh runtime root it asks whether to generate a cluster wallet; press Enter to accept.
It also starts a local OPS proxy if oak-chain-edge-worker is cloned next to the other two repositories. Without it, the script prints OPS proxy skipped and ends with a "not fully healthy" warning. You can ignore that warning for this guide.
Stop the cluster with ./modes/mock/validators/lifecycle/stop-cluster.sh, using the same CLUSTER_RUNTIME_ROOT.
Verify It's Working
Check Validator Dashboard
Open http://localhost:8090 in your browser.
You should see:
- Role: LEADER or FOLLOWER
- Leader and Term
- Reachable
3/3and QuorumYES (2)
Check Consensus Status
# Ask each validator for its role and the leader it sees
for p in 8090 8092 8094; do
curl -s http://localhost:$p/v1/consensus/status \
| jq -c '{currentRole, currentLeader, reachableValidators}'
done
# Expected: one LEADER, two FOLLOWER, the same currentLeader on all three, e.g.
# {"currentRole":"LEADER","currentLeader":"http://localhost:8090","reachableValidators":3}
# {"currentRole":"FOLLOWER","currentLeader":"http://localhost:8090","reachableValidators":3}
# {"currentRole":"FOLLOWER","currentLeader":"http://localhost:8090","reachableValidators":3}
The full response also includes contractVersion (consensus.status.v1), consensusType, isLeader, currentEpoch, currentTerm, allFollowers and ethereumEpoch. Any of the three ports can be the leader.
Write Some Content
# Still in oak-chain-infra. Find the current leader.
LEADER=$(curl -s http://localhost:8090/v1/consensus/status | jq -r .currentLeader)
# Build a signed test write with public, deterministic test keys
BODY=$(node bin/oak-chain.mjs tx write --message 'Hello, Oak Chain!' --format form)
ID=$(printf '%s' "$BODY" | tr '&' '\n' | sed -n 's/^proposalId=//p')
# Submit it to the leader: expect HTTP 202 and "state": "PENDING"
RESP=$(curl -s -X POST -H 'Content-Type: application/x-www-form-urlencoded' \
--data-binary "$BODY" "$LEADER/v1/propose-write")
echo "$RESP" | jq '{state, proposalId, wallet, storagePath}'
# Repeat until "state" is "COMMITTED" (usually within seconds)
curl -s "$LEADER/v1/ops/operations/$ID" | jq '{state, durabilityState}'
Send writes to the leader. A follower answers 307 with the leader's URL in the Location header, and the operation record lives only on the validator that accepted the write. FAILED or TIMED_OUT means the write did not land.
Read It Back
WALLET=$(echo "$RESP" | jq -r .wallet)
curl -s "$LEADER/v1/wallets/content?wallet=$WALLET" | jq
# Expected: a "content" list with an entry whose "message" is "Hello, Oak Chain!"
Run the same query against the other two ports. Each validator answers from its own store.
What Just Happened
- Your write proposal went to the leader
- Leader checked the request and the signature format (mock mode does not verify signatures cryptographically or check payments)
- Leader queued the proposal, answered
202 PENDING, and proposed it to the Aeron cluster - Aeron ordered the write in its replicated log, and every validator, the followers included, applied it
- Content persisted to each validator's Oak segment store
- The write became
COMMITTEDonce a majority of validators had applied it and flushed it to disk - All three validators now hold the same content. Convergence is logical: same nodes, property types and values. Their RecordIds, journal heads and TAR files can differ.
Modes
Next
Developer Guide for the full map of concepts, contract surface, and reference.