How Public History Synchronization Works in BitChat: The Gossip Sync Protocol
BitChat synchronizes public chat history across mesh peers using a lightweight gossip-sync protocol that exchanges compact Golomb-coded set (GCS) filters every 15 seconds to identify and request missing messages, maintaining a rolling 6-hour retention window for text and 15 minutes for media fragments.
BitChat is a decentralized, offline-first messaging application designed for permissionless mesh networking. To ensure all participants maintain a consistent view of recent public conversation without relying on central servers, the app implements Public History synchronization—a gossip-based mechanism formalized in the project's whitepaper. This protocol leverages space-efficient probabilistic filters and selective message retrieval to minimize bandwidth usage across intermittent Bluetooth and peer-to-peer links.
The Gossip Sync Architecture
Persistent Local Cache
Every device maintains a local cache of approximately 1,000 recent public broadcast packets stored in a persistent ConversationStore. According to docs/CONVERSATION-STORE-DESIGN.md, this cache survives app restarts by writing to disk, enabling devices that reconnect after network partitions to serve recent history to peers. The bitchat/App/PublicChatModel.swift file houses the core view-model that owns this cache and orchestrates the synchronization lifecycle.
Periodic Reconciliation with GCS Filters
At the heart of the protocol lies the Golomb-coded set (GCS) filter, a space-efficient probabilistic data structure implemented in bitchat/Utils/GcsFilter.swift. Approximately every 15 seconds, each peer advertises a compact GCS filter summarizing the hashes of messages currently held in its local cache. This interval balances synchronization freshness with power consumption constraints on mobile devices operating in Bluetooth mesh environments.
The Synchronization Workflow
Filter Exchange and Delta Detection
When PublicChatModel triggers runGossipSync(), the system generates a local GCS filter from cachedPublicMessages and transmits it via peerConnector.send(filter:). Upon receiving a remote peer's filter, the local device executes missingHashes(comparedTo:) to compute the set difference—identifying exactly which message hashes exist on the remote side but are absent locally.
Selective Message Retrieval
Rather than broadcasting full message contents, peers request only the specific packets they lack. The requestMissingMessages(hashes:) method iterates through missing hashes, calling peerConnector.requestMessage(hash:) for each. This targeted approach keeps bandwidth minimal in low-throughput mesh environments. Received packets are immediately added to the local cache via cachePublicMessage(), and the local GCS filter updates to reflect the new state.
Retention Policies and Storage Constraints
Text Message Retention Window
Cached public messages remain synchronizable for 6 hours before automatic eviction. As documented in docs/WHITEPAPER.md Section 6.3, this window provides sufficient overlap for transient network partitions while preventing unbounded storage growth. The persistent disk backing ensures that even devices offline for extended periods retain their 6-hour history buffer to share with reconnecting peers.
Media Fragment Lifecycle
Media fragments and file-transfer packets operate under stricter constraints. These larger payloads utilize a 15-minute retention window, after which they are discarded regardless of synchronization status. This differential treatment prioritizes text conversation consistency over resource-intensive media distribution in bandwidth-constrained mesh topologies.
Implementation Details in Swift
The gossip-sync loop begins via startGossipSync(every:) in PublicChatModel.swift, which establishes a timer-driven reconciliation process:
// Start the periodic gossip sync (called from AppRuntime)
publicChatModel.startGossipSync(every: 15.0) // 15‑second interval
// Inside PublicChatModel.swift (simplified)
func startGossipSync(every interval: TimeInterval) {
Timer.publish(every: interval, on: .main, in: .common)
.autoconnect()
.sink { [weak self] _ in self?.runGossipSync() }
.store(in: &cancellables)
}
private func runGossipSync() {
let localFilter = GcsFilter.create(from: cachedPublicMessages)
peerConnector.send(filter: localFilter) { remoteFilter in
let missingHashes = remoteFilter.missingHashes(comparedTo: localFilter)
self.requestMissingMessages(hashes: missingHashes)
}
}
Selective retrieval of missing packets occurs through the networking layer, typically implemented in bitchat/Networking/PeerConnector.swift:
// Request missing packets from a peer
func requestMissingMessages(hashes: [MessageHash]) {
for hash in hashes {
peerConnector.requestMessage(hash: hash) { message in
self.cachePublicMessage(message)
}
}
}
The GcsFilter.create(from:) method in bitchat/Utils/GcsFilter.swift handles the Golomb-coded serialization of message hashes, producing filters sized at just a few dozen bytes—critical for efficient transmission over Bluetooth Low Energy.
Summary
- Public History synchronization relies on a gossip-sync protocol with 15-second reconciliation intervals.
- Golomb-coded set (GCS) filters enable efficient set reconciliation using only tens of bytes per exchange.
- The system maintains approximately 1,000 public messages in a persistent disk cache to survive app restarts and network partitions.
- Text messages remain synchronizable for 6 hours, while media fragments expire after 15 minutes.
- Core implementation resides in
PublicChatModel.swift, utilizingPeerConnectorfor transport andGcsFilterfor hash summarization.
Frequently Asked Questions
How does BitChat handle devices that reconnect after long disconnections?
Devices persist their ~1,000 message cache to disk via ConversationStore, allowing them to retain the most recent 6 hours of public history even after app restarts or extended offline periods. When reconnecting to the mesh, these devices immediately advertise their cached history through GCS filters, allowing peers who missed messages during the partition to request the specific packets they lack.
What is a GCS filter and why does BitChat use it?
A Golomb-coded set (GCS) filter is a space-efficient probabilistic data structure that compresses a set of message hashes into a compact binary format. BitChat uses GCS filters—implemented in bitchat/Utils/GcsFilter.swift—because they require only a few dozen bytes to represent hundreds of message hashes, making them ideal for bandwidth-constrained Bluetooth mesh networks where exchanging full message lists would be prohibitively expensive.
How frequently do peers synchronize their public chat history?
Peers initiate synchronization approximately every 15 seconds, as configured by the startGossipSync(every: 15.0) timer in PublicChatModel.swift. This interval provides near-real-time consistency while conserving battery life on mobile devices operating in decentralized mesh topologies.
Why are media fragments retained for a shorter period than text messages?
BitChat applies a 15-minute retention window to media fragments compared to the 6-hour window for text messages because media files consume significantly more storage and bandwidth. This distinction ensures that the limited resources of mesh nodes prioritize the consistency of text conversation history over the distribution of large file transfers.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →