Single-binary CASPaxos acceptor + CLI (KV + config + dump) with:
- UDP transport, big-endian wire format
- HLC (Hybrid Logical Clock) folded into ballots
effective_promise = max(promise, accepted.ballot)- Atomic RocksDB WriteBatch on
ACCEPT - Read-only fast path (1‑RTT
READ) + fallback Phase‑1 - Joint reconfiguration (OldOnly → Joint → NewOnly)
- Seed bootstrap (learn config
con startup ifCF_SYSempty) - Namespaces:
NS_CFG='c'(single config key),NS_DATA='d'(user keys)
paxon.c contains both the acceptor and the UDP‑speaking CLI.
You need RocksDB and its C API (rocksdb/c.h) plus common compression libs.
brew install rocksdb snappy lz4 zstd bzip2
make # or: make ROCKSDB_PREFIX=/opt/homebrewsudo apt-get install -y build-essential librocksdb-dev zlib1g-dev libbz2-dev libsnappy-dev liblz4-dev libzstd-dev
makeIf RocksDB isn’t in a default path, point the Makefile at it:
make ROCKSDB_PREFIX=/custom/prefixInstall / clean:
sudo make install
make cleanRun 2–3 acceptors (each with its own RocksDB dir):
# Terminal 1
make run-acceptor BIND=127.0.0.1:9000 DB=./db1
# Terminal 2
make run-acceptor BIND=127.0.0.1:9001 DB=./db2 SEEDS="127.0.0.1:9000"
# Terminal 3
make run-acceptor BIND=127.0.0.1:9002 DB=./db3 SEEDS="127.0.0.1:9000 127.0.0.1:9001"Note: If
CF_SYS(the local mirror of the chosen config) is empty, an acceptor will try to bootstrap from--seedpeers by reading the config key{'c'}.
Push a human‑readable config (replicated under key {'c'}):
make cfg-set \
CFG='epoch=1
phase=old
old=127.0.0.1:9000,127.0.0.1:9001,127.0.0.1:9002' \
SEEDS="127.0.0.1:9000 127.0.0.1:9001 127.0.0.1:9002"Read it back:
make cfg-get SEEDS="127.0.0.1:9000 127.0.0.1:9001 127.0.0.1:9002"# Set
make kv-set KEY=foo VAL=bar SEEDS="127.0.0.1:9000 127.0.0.1:9001 127.0.0.1:9002"
# Get
make kv-get KEY=foo SEEDS="127.0.0.1:9000 127.0.0.1:9001 127.0.0.1:9002"The CLI talks UDP directly to the acceptors. GET attempts the read‑only fast path (1‑RTT) and falls back to Phase‑1 if needed.
Example: migrate from {9000,9001} to {9001,9002}.
- Enter joint:
make cfg-set \
CFG='epoch=2
phase=joint
old=127.0.0.1:9000,127.0.0.1:9001
new=127.0.0.1:9001,127.0.0.1:9002' \
SEEDS="127.0.0.1:9000 127.0.0.1:9001 127.0.0.1:9002"- Finalize to new (after clients have learned the new epoch/phase):
make cfg-set \
CFG='epoch=2
phase=new
new=127.0.0.1:9001,127.0.0.1:9002' \
SEEDS="127.0.0.1:9001 127.0.0.1:9002"-
Column Families:
- CF_STATE — protocol state
p:<rawkey>→ 24B promise (ballot)a:<rawkey>→ 24B ballot + 4B vlen + value
- CF_SYS — mirror of chosen config under key
"cfg"(fast restarts)
- CF_STATE — protocol state
-
Namespaces (first byte of the replicated “raw key”):
NS_CFG = 'c'— single config keyNS_DATA = 'd'— user KV keys ('d' + user_key)
- Big‑endian fields on the wire.
header_tcarries:msg_type,key_len,value_len,epoch,proposer_id,proposer_age, and HLC (physical ms + logical).- Ballot =
(epoch, counter, proposer_id)with counter folded from HLC.
Local effective floor =max(promise, accepted.ballot); proposals below the floor are rejected.
Message types:
PREPARE → PROMISE | REJECTACCEPT → ACCEPTED | REJECTREAD → READ_RSP | REJECT(fast path)
Dump local RocksDB state:
./paxon dump --db ./db1You’ll see p: and a: entries plus the mirrored config in CF_SYS.
This project is licensed under the Apache License, Version 2.0.
- SPDX:
Apache-2.0 - See the included
LICENSEfile or https://www.apache.org/licenses/LICENSE-2.0