How independent Xudanu servers connect, trust each other, replicate content, reach consensus, recover from failures, and scale — explained with diagrams for operators and developers.
A Xudanu federation is a set of independent server peers, each a full node (it serves the web UI, the client WebSocket /xudanu, and the peer WebSocket /federation). Peers connect to each other, replicate content (chunks verified by BLAKE3), maintain a shared membership via a web-of-trust of Ed25519 endorsements, and make cluster-wide decisions with PBFT consensus. Users connect to any peer's web UI; the peers federate behind the scenes.
Three full-node peers on a private network. Users hit any peer's web UI; peers replicate content and membership over /federation.
The activation layer (FR-3) — now implemented
Five pieces of runtime wiring turn the finished components into a live federation. Each was previously missing; all five are now implemented and merged.
1
Outbound dialer — On startup, a task per --peer dials ws://peer/federation, performs the mutual handshake as the client side, and holds the encrypted channel in a PeerPool. Reconnects with exponential backoff (1s→30s cap).
2
Startup bootstrap — membership_bootstrap_init() creates a self-endorsed membership entry so the node can act as a founder or be validated by others.
3
Peer-key registration — --trusted-peer-key <hex> registers trusted peer keys, fixing the is_peer_known reject-all trap. Without it, every connection is refused when federation is enabled.
4
Periodic sync / heartbeat — Every 30s sends Heartbeat; every 60s sends SyncPull, MembershipSyncPush, StateSyncPush, EndorsementSyncPush. Turns "connected" into "converged."
5
PBFT broadcast — GovernancePropose broadcasts GovernancePrePrepare to all peers via the governance_tx broadcast channel; peers relay votes and seal.
Federation is off by default. A standalone server (no --peer flag) is completely unaffected — the dialer never spawns, the broadcast channel has no subscribers, and is_peer_known returns true for everyone. You opt in at startup with CLI flags.
PBFT: Making decisions together
Xudanu uses Practical Byzantine Fault Tolerance (PBFT) for cluster-wide decisions — things like admitting a new member, expelling a bad actor, or recording a royalty payment. PBFT is a consensus protocol that lets a group of nodes agree on a decision even if some nodes are malicious or offline.
What PBFT is — in plain language
Imagine a committee of N people who must vote on a proposal. PBFT guarantees that:
The group can reach a decision as long as at least 2/3 of the members are honest and reachable (formally: tolerates f faulty nodes out of 3f+1 total).
No two honest members will ever disagree on the outcome.
Once a decision is sealed, it cannot be undone.
PBFT three-phase flow with 4 nodes (1 offline). The cluster tolerates f faulty nodes out of 3f+1. PrePrepare broadcasts from the leader; Prepare and Commit votes flow between all replicas; sealing requires 2f+1 Commit votes.
The quorum rule, simply
For a cluster of N nodes, PBFT tolerates up to f faulty/offline nodes, where f = (N - 1) / 3. To make a decision, you need at least 2f+1 nodes to vote yes:
Cluster size (N)
Fault tolerance (f)
Votes needed (2f+1)
Can lose
3
0
2
1 node OK
4
1
3
1 node OK
7
2
5
2 nodes OK
10
3
7
3 nodes OK
2
0
2
0 nodes fragile
Practical guidance: 3 nodes is the minimum for a resilient federation (tolerates 1 offline). 2 nodes works but both must always be online. 4 nodes is the sweet spot — tolerates 1 failure with margin.
Broadcasting & synchronization
Once peers are connected, they need to stay in sync. Xudanu uses two complementary mechanisms: periodic sync (every 60 seconds) and event-driven broadcast (immediate, for governance).
How the periodic sync loop works
Every 60 seconds, each outbound connection sends a batch of sync frames to the peer on the other end. The peer processes them and replies with its own state. This ensures both sides converge even if they were temporarily disconnected.
Each outbound connection runs a select! loop that multiplexes inbound frames, broadcast frames (from PeerPool), heartbeats, and periodic sync. All data crosses an encrypted AEAD channel. CRDTs ensure convergence regardless of frame duplication or arrival order.
Why duplicates don't matter: Xudanu uses CRDTs (Conflict-free Replicated Data Types) for all federated state — membership (OR-Set), reconcile states (LWW Register + OR-Set alternatives), and content (BLAKE3-addressed). CRDTs are mathematically guaranteed to converge to the same state regardless of delivery order or duplication. A frame received 5 times has exactly the same effect as received once.
How Xudanu shares content
Xudanu's document model is content-addressed: every piece of content (a chunk of text, an edition, a blob) is identified by its BLAKE3 hash, not by a server-assigned ID. This makes replication straightforward — you can verify any piece of data by recomputing its hash.
The chunk replication flow
When a user edits a document on Peer A, the change flows through the system and reaches all other peers within one sync interval (60 seconds):
A user edits on Peer A. The edition is stored with a BLAKE3 fingerprint. During the next sync cycle, the SyncPush frame carries the edition payload across the encrypted channel. Peer B and C recompute the hash — if it matches, they import; if not, the data is rejected as tampered or corrupt.
What gets replicated
Data type
How it's verified
Deduplication
Works (documents)
BLAKE3 hash of all edition entries
Existing fingerprints checked before import
Editions (versions)
Content-addressed within the work
Per-work fingerprint set
Blobs (images, files)
BLAKE3 hash of raw bytes
Hash-addressed on disk
Membership
Ed25519 signature on each endorsement
OR-Set tags (idempotent merge)
Endorsements
Signed by the endorsing member's key
OR-Set (idempotent)
Reconcile state
LWW timestamp + content hash
Highest timestamp wins
Only published works are exported. Private drafts stay on the originating server. The federation_export_works function filters to read_club == public_club, ensuring private content never leaves its origin.
How a donated machine joins
Identity is the server's existing Ed25519 keypair (data/server.key); trust is a web-of-trust of signed endorsements (phase-19a, already built). A donated host proves who it is by its key, and gets vouched for by an existing member.
A donated host bootstraps, dials a founder, completes the mutual handshake, has its key registered, then joins with an endorsement — membership converges cluster-wide.
No central authority. Trust is peer-signed and auditable; a compromised donor can be ejected via signed retraction or a PBFT Expel transaction. Each donor bears their own machine, so cost stays distributed — there is no expensive central fleet.
When things go wrong: failure & recovery
Federations run over real networks, and real networks fail. Xudanu is designed to self-heal — when a failed or partitioned node comes back, it catches up automatically. Here are the common scenarios and how the system handles them.
A peer's network goes down (VPN drops, host reboots, ISP hiccup). The other peers continue operating. When the partitioned node returns, it reconnects and syncs.
Network partition and automatic recovery. CRDTs guarantee convergence without conflict resolution — C simply pulls the state it missed.
Scenario 2: Node crash (data dir survives)
If a peer process crashes but its /data directory survives (common case — process killed, OOM, panic), the node restarts, restores from its chunk store, reconnects, and syncs. Data written before the crash is durable (checkpointed every 5 seconds). No manual intervention needed.
Scenario 3: Byzantine (malicious) node
A compromised node tries to inject tampered data or forge endorsements. The system rejects these automatically:
Tampered blobs/chunks: BLAKE3 hash mismatch on import → rejected.
False membership claims:MembershipJoinRequest with invalid proofs → rejected.
Cluster-wide removal: A PBFT Expel transaction removes the bad node's membership from all peers.
Scenario 4: Split-brain (two partitions, both active)
If the network splits into two groups (e.g., A-B and C-D), both partitions continue accepting edits independently. PBFT cannot reach quorum in either partition (each has fewer than 2f+1 votes for a 4-node cluster with f=1). When the partition heals, CRDTs merge the divergent states — both sets of edits are preserved as alternatives, and the reconcile store's LWW (Last-Writer-Wins) registers pick the most recent timestamp.
Split-brain caveat: PBFT governance decisions cannot complete during a partition (by design — this prevents conflicting decisions). Content edits are NOT blocked — they are CRDT-merged on heal. If both partitions edited the same document, both versions are preserved as reconcile alternatives, and the user can choose which to keep.
Scenario 5: Slow or degraded node
A peer that is reachable but very slow (high latency, low bandwidth, overloaded CPU). Heartbeats detect this (30s timeout). The outbound dialer doesn't block on slow peers — each connection has its own task. Other peers continue syncing among themselves at full speed. The slow node catches up when it can.
Scaling: what to expect as you grow
Adding more machines increases resilience but also increases network traffic, because each peer connects to every other peer (full mesh). Here's what to expect at different scales.
Connection mesh: O(n²)
Each peer maintains an outbound connection to every other peer. With N peers, there are N × (N-1) directional connections (or N(N-1)/2 bidirectional pairs):
Peers (N)
Bidirectional pairs
Connections per peer
Assessment
3
3
2
trivial
5
10
4
easy
10
45
9
fine
20
190
19
moderate
50
1,225
49
heavy
100
4,950
99
very heavy
Future optimization: For 50+ nodes, a gossip protocol or relay-based topology (instead of full mesh) would dramatically reduce connections. This is on the roadmap but not needed for v1 — most federations will have 3–10 peers.
Bandwidth per sync cycle
Each 60-second sync cycle sends all published works, membership, states, and endorsements to each peer. For a fresh/small federation this is kilobytes. As content grows, bandwidth scales with total published content × number of peers.
Federation size
Content volume
Per-peer bandwidth (60s)
Notes
3 peers
~10 MB
~10 MB
Trivial on any connection
5 peers
~50 MB
~50 MB
Fine on broadband
10 peers
~200 MB
~200 MB
~3 Mbps sustained — fine for VPS
10 peers
~1 GB
~1 GB
~14 Mbps — needs a good link
20 peers
~1 GB
~1 GB × 19 peers
~270 Mbps aggregate — datacenter
Incremental sync is the next optimization. Currently SyncPull sends up to MAX_SYNC_ENTRIES (1000) entries per cycle. Deduplication by BLAKE3 fingerprint prevents re-import, but the full payload still transmits. A future "known fingerprints" filter (the field exists in the struct but isn't populated yet) will skip data the peer already has.
Memory and CPU per peer
RAM: Each peer holds its own chunk store, the membership OR-Set, reconcile states, and CRDT buffers. A peer with 100 works uses ~50–100 MB RAM. Federation adds ~2–5 MB per connected peer (channel buffers + sync state).
CPU: Dominated by BLAKE3 hashing (on import) and Ed25519 signature verification (on handshakes/endorsements). Both are fast (< 1ms per operation). A 4-core VPS handles 10+ peers comfortably.
Disk: Content-addressed storage grows with total published works. BLAKE3 deduplication means the same content stored on multiple peers takes space only once per peer.
Recommended configurations
Use case
Peers
Min specs per peer
Network
Personal / testing
2–3
2 vCPU, 2 GB RAM, 10 GB disk
Any broadband
Small team
3–5
2 vCPU, 4 GB RAM, 20 GB disk
10 Mbps uplink
Organization
5–10
4 vCPU, 8 GB RAM, 50 GB disk
50 Mbps uplink
Large federation
10–20
4 vCPU, 16 GB RAM, 100 GB disk
100 Mbps+ datacenter
Research / max scale
20–50
8 vCPU, 32 GB RAM, 200 GB SSD
Gigabit datacenter
Problem checklist by scale
A diagnostic reference for operators. If something seems wrong, check these items.
2–3 nodes (personal / small team)
CommonKeys not cross-registered: Both peers must register each other's verifying key via --trusted-peer-key. If you see "peer not in trusted peers list," check that the hex keys match.
CommonWrong peer address format:--peer expects ws://host:port/federation or host:port. Check that the port matches the listening port of the remote peer.
CommonFirewall blocks /federation: The WebSocket upgrade on /federation must be reachable. If using Docker, ensure the port is published.
WatchBoth nodes offline: With 2 nodes and f=0, if one goes down, PBFT cannot function. Content sync still works when it returns (CRDTs), but governance is blocked until both are up.
WatchBootstrap not called: If membership is empty, check that --peer was specified (which enables federation and triggers membership_bootstrap_init).
5–10 nodes (organization)
WatchKey management: Each new peer's verifying key must be registered on all existing peers. Consider maintaining a shared trusted-peers.txt file.
WatchSync bandwidth: With 10 peers and significant content, each sync cycle sends data to 9 peers. Monitor outbound bandwidth.
WatchStale membership: If a peer leaves without sending MembershipLeave, its entry persists until explicitly removed. Use the admin API to clean up.
TipUse 4+ nodes for PBFT: With 4 nodes (f=1), you can lose 1 node and still reach quorum. With 3 nodes (f=0), losing any node blocks governance.
PlanStartup thundering herd: If all peers restart simultaneously, they all try to dial each other at once. The exponential backoff helps, but consider staggered restarts.
WatchEndorsement chain growth: The membership OR-Set grows with each endorsement. At 20 nodes with cross-endorsements, the set can be several hundred KB. Not a problem, but watch for sync latency.
WatchPBFT leader bottleneck: The current leader proposes all transactions. At 20 nodes, if the leader is slow, governance stalls. Leader rotation exists but view-change (automatic leader failover) is a future feature.
20–50+ nodes (research / max scale)
ChallengeFull mesh doesn't scale: 50 nodes = 2,450 connections. Each peer dials 49 others. This works but is wasteful. A gossip/relay topology is needed for sustainable scaling.
ChallengeSync payload size: With 50 peers and GBs of content, the 60-second full sync is impractical. Incremental sync (using known_fingerprints) is essential.
ChallengePBFT message complexity: O(n²) vote messages per round. At 50 nodes, each round generates ~2,500 vote messages. Practical but slow.
ChallengeMembership convergence latency: With 50 peers, a new endorsement takes several sync cycles to reach all nodes. Plan for eventual consistency, not immediate.
WatchNAT / reachability: Donated hosts behind NAT cannot be dialed. Each peer must be directly reachable (public IP or port-forward). A relay/TURN server is a future feature.
Bottom line: For most use cases (3–10 peers on VPS or donated machines), Xudanu's federation works well out of the box. The full-mesh + periodic sync model is simple, robust, and self-healing. Scaling beyond 20 nodes is possible but will benefit from the incremental sync and gossip relay optimizations on the roadmap.
Testing it: Docker test bed
The docker-compose.yml runs three full-node peers on a private bridge network (host ports 8081/8082/8083), each with --peer pointing at the other two. On a 32 GB machine this is trivial for small/fresh data dirs — you could run 5–10+ peers; 3–4 is the sweet spot for exercising PBFT quorum and membership.
docker compose up --build
# then browse:
open http://localhost:8081 # peer A
open http://localhost:8082 # peer B
open http://localhost:8083 # peer C
For manual testing with two peers (no Docker):
# Terminal 1: Peer A
cargo run --release --features server --bin xudanu-server -- \
run 127.0.0.1:8081 data-a \
--peer 127.0.0.1:8082 \
--trusted-peer-key <B's verifying key>
# Terminal 2: Peer B
cargo run --release --features server --bin xudanu-server -- \
run 127.0.0.1:8082 data-b \
--peer 127.0.0.1:8081 \
--trusted-peer-key <A's verifying key>
Each server prints its verifying key at startup: Federation: this server's verifying key: <hex>. Copy it to the other peer's --trusted-peer-key flag.
Sequencing (implementation order)
1
Peer-key registration + bootstrap — smallest; fixes the reject-all trap so anything can connect. done
2
Outbound dialer — the hard blocker; without it nothing is active. done
3
Sync / heartbeat loop — turns a connection into convergence. done