A multi-service P2P battleship game using namespaces, private storage, CRDT-based state sync, and a SHA256 commit-reveal anti-cheat scheme
2WASM Services
P2PNo Server
CRDTAuto-Sync
SHA256Commit-Reveal
01
How It Works
Two players on separate nodes, syncing state via CRDTs. Ship placements stay private until hit.
◆
Shared state replicates via CRDT deltas over gossipsub. Both nodes converge automatically.
✕
Private boards never leave the node. Shots are resolved locally, only hit/miss results are shared.
02
Namespaces & Access Control
A namespace scopes identity, governance, and app instances. Each battleships lobby is a namespace.
03
Multi-Service Bundle
One .mpk bundle, two WASM services. Each context picks its service at creation time.
04
Private Storage
Ship placements are the core secret: each player's board is stored locally and never replicated. A SHA256 commitment is published up-front so the placement cannot be changed mid-game.
◆
Commitment. At placement, each player computes SHA256(pristine_board || salt) and writes it to UserStorage. The slot is writer-authorised — only that player can ever write their own commitment, and a second write is rejected with AlreadyCommitted.
✕
Reveal + audit. On the winning shot (or via reveal_board) the contract recomputes the hash against the pristine snapshot and replays every recorded shot against the revealed board. Tampering or lying hits produce AuditFailed.
05
Cross-Context Calls (xcall)
When a game ends, the game context notifies the lobby to update stats and history.
06
Complete Game Flow
Eight steps from namespace creation to final score update.
1
Create Namespace
Host creates a namespace with the battleships app. Sets default capabilities (bits 0+1+3 = 11).
2
Create Lobby
Lobby context created in namespace root group with service_name: lobby
3
Invite Player
Recursive namespace invitation covers root + all subgroups.
Lobby allocates match ID. Create subgroup → add P2 → create game context with service_name: game
6
Place Ships + Commit
Ships stored in private storage. SHA256(pristine || salt) published to writer-authorised UserStorage. Write-once: second attempt returns AlreadyCommitted.
7
Take Turns
propose_shot → event triggers acknowledge_shot_handler on target node → resolves against private board → per-cell UnorderedMap entries merge via LWW.
8
Winning Shot + Audit
All ships sunk → inline audit recomputes commitment from pristine snapshot → replays every shot against the revealed board → AuditPassed/AuditFailed → xcall to lobby updates Counter-backed stats.
07
CRDT State Sync
All shared state replicates automatically between nodes. Each mutation creates a delta broadcast via gossipsub. Primitives used: LwwRegister for single-writer fields, UnorderedMap for per-key add-wins, Counter for G-Counter stat increments, UserStorage for writer-authorised per-player slots, and Vector for append-only history.
DAG-based
Deltas form a causal DAG. Concurrent writes merge automatically without coordination.
Encrypted
Deltas are encrypted with the group's shared key before broadcast over the network.
Eventual
Nodes converge to the same root hash. No central coordinator needed.
The sync layer is service-aware: when applying a delta from a peer, the node loads the correct WASM service (lobby or game) based on the context's service_name to execute __calimero_sync_next.