How BitChat Handles Migration and Sync Recovery After Network Partitions or Restarts
BitChat uses a two-component architecture combining GossipSyncManager for gossip-based synchronization and RequestSyncManager for time-bound request tracking to automatically recover and resynchronize peers after network partitions or device restarts.
BitChat's mesh messaging protocol guarantees eventual consistency even when Bluetooth Low Energy (BLE) links drop, devices reboot, or network partitions split the peer mesh. The recovery mechanism is engineered specifically for unreliable, intermittent connectivity common in permissionless p2p networks. This article examines the source-level implementation of migration and sync recovery in the permissionlesstech/bitchat repository, tracing how the system detects gaps, rebuilds shared state, and prevents replay attacks.
Core Architecture: Two-Component Recovery System
BitChat separates concerns between synchronization orchestration and request lifecycle management. This design prevents state corruption and bounds recovery latency.
GossipSyncManager: The Synchronization Engine
Located in bitchat/Sync/GossipSyncManager.swift, this class manages per‑type packet stores with configurable capacity limits for:
- Public messages
- Fragments (file transfer chunks)
- File transfers
- Group messages
- Pre‑key bundles
- Board posts
The manager runs periodic maintenance every maintenanceIntervalSeconds (default 30 seconds) via performPeriodicMaintenance(). This maintenance routine:
- Cleans expired messages (
cleanupExpiredMessages) - Removes stale announcements (
cleanupStaleAnnouncementsIfNeeded) - Persists public-message archives (
persistArchiveIfDirty) - Purges expired sync requests via
requestSyncManager.cleanup()
On application launch, restoreArchivedMessages() (lines 21‑34) reloads any persisted public messages still within the freshness window. This gives the node immediate historical data to offer reconnecting peers, even before new traffic arrives.
RequestSyncManager: Time-Bounded Request Tracking
Implemented in bitchat/Sync/RequestSyncManager.swift, this component prevents synchronization abuse and stale response processing.
Key data structure:
private var pendingRequests: [PeerID: Date] = [:]
When a peer initiates recovery, registerRequest(to:) timestamps the outbound request. Inbound packets undergo strict validation in isValidResponse(from:isRSR:) (lines 48‑65):
- The packet must have
isRSR = true - The original request must be within the
responseWindowSecondsduration (default 30 seconds)
Expired or unsolicited responses are silently discarded (lines 53‑55). The cleanup() method sweeps stale entries periodically.
The Seven-Step Recovery Flow
BitChat's migration and sync recovery follows a deterministic protocol when peers reconnect after partitions or restarts.
1. Detecting Connection Gaps with Targeted REQUEST_SYNC
Upon detecting a peer reconnection (via BLE service callbacks), GossipSyncManager invokes sendRequestSync(to:peerID,types:) (lines 61‑62). This unicast message signals explicit intent to resynchronize.
private func sendRequestSync(to peerID: PeerID, types: SyncTypeFlags) {
requestSyncManager.registerRequest(to: peerID) // record pending request
let payload = buildGcsPayload(for: types) // builds GCS filter
// ... packet construction and transmission
}
The call simultaneously registers the request timestamp and constructs an efficient Golomb-Coded Set (GCS) filter describing local state.
2. Building Space-Efficient Sync Payloads
The buildGcsPayload(for:types:) method generates compact set reconciliation data:
- Empty stores: Returns minimal filter (lines 52‑56)
- Non-empty stores: Encodes packet identifiers with
sinceTimestampcursor (lines 91‑95)
The timestamp cursor prevents redundant retransmission of older packets the requester already possesses. This optimization is critical for bandwidth-constrained BLE mesh networks.
3. Processing Request-Sync Responses (RSR)
When a peer receives REQUEST_SYNC, it:
- Parses the GCS filter to determine missing items
- Marks outbound packets with
isRSR = true - Transmits unicast responses
Implementation examples per packet type:
| Packet Type | RSR Marking Location |
|---|---|
| Public messages | Lines 19‑22 |
| Fragments | Lines 30‑34 |
| File transfers | Lines 40‑44 |
4. Strict Response Validation
Inbound RSR packets undergo verification before processing:
func handleIncomingPacket(_ pkt: BitchatPacket, from peerID: PeerID) {
guard requestSyncMgr.isValidResponse(from: peerID, isRSR: pkt.isRSR) else {
// ignore unsolicited or stale packet
return
}
// Process validated packet
}
This validation mitigates replay attacks and stale data injection from peers with clock skew or malicious intent.
5. Periodic State Maintenance
Every 30 seconds, performPeriodicMaintenance() executes four critical cleanup operations (lines 68‑73):
private func performPeriodicMaintenance() {
cleanupExpiredMessages()
cleanupStaleAnnouncementsIfNeeded()
persistArchiveIfDirty()
requestSyncManager.cleanup() // purge expired sync requests
}
This ensures that after restarts, no phantom request state persists and archived data remains bounded.
6. Archive Restoration on Cold Start
Application launch triggers restoreArchivedMessages():
// GossipSyncManager initialization path
restoreArchivedMessages() // lines 21-34
The method reloads serialized public messages from disk, validates freshness against configured TTL windows, and populates in-memory stores. Discarded messages are those that aged out while the application was offline.
7. Targeted Fragment Recovery
For stalled file transfers, requestMissingFragments(fragmentStreamID:) (lines 41‑57) issues fragment-ID-filtered sync requests. This bypasses general GCS rounds and accelerates recovery for specific transfer streams.
Configuration Parameters
Tune sync recovery behavior through GossipSyncManager.Config:
| Parameter | Default | Purpose |
|---|---|---|
maintenanceIntervalSeconds |
30 | Cadence of cleanup operations |
responseWindowSeconds |
30 | Maximum RSR validity window |
| Packet store capacities | Type-specific | Memory pressure limits |
Adjust these based on expected partition duration and device memory constraints.
Practical Integration Example
Instantiate and connect the recovery components in your transport layer:
// Initialize managers
let requestSyncMgr = RequestSyncManager()
let gossipMgr = GossipSyncManager(
myPeerID: myPeerID,
config: .init(),
requestSyncManager: requestSyncMgr,
archive: myArchive
)
// Set delegate for signing and transmission
gossipMgr.delegate = self
gossipMgr.start()
// Schedule recovery when peer reconnects
func peerDidConnect(_ peerID: PeerID) {
gossipMgr.scheduleInitialSyncToPeer(peerID, delaySeconds: 5.0)
}
The 5-second delay accommodates BLE link stabilization before flooding the network with sync traffic.
Summary
BitChat's migration and sync recovery delivers robust eventual consistency through:
- Explicit request tracking with
RequestSyncManagerenforcing time-bounded response windows - Space-efficient set reconciliation via GCS filters with timestamp cursors
- Periodic maintenance cycles that purge stale state and persist critical data
- Cold-start archive restoration ensuring immediate participation in mesh synchronization
- Fragment-level targeted recovery for large file transfer resumption
The architecture prioritizes bandwidth efficiency and attack resistance over raw speed—appropriate tradeoffs for permissionless mesh networks operating over unreliable BLE links.
Frequently Asked Questions
How does BitChat prevent replay attacks during sync recovery?
BitChat prevents replay attacks through isValidResponse checks in RequestSyncManager. Each inbound packet with isRSR = true must correspond to a pending request registered within the last responseWindowSeconds (default 30). Stale or unsolicited responses are rejected at lines 53‑55 of RequestSyncManager.swift, ensuring only contemporaneous responses are processed.
What happens to sync requests if a device restarts mid-recovery?
Uncompleted sync requests are automatically purged. On launch, GossipSyncManager calls requestSyncManager.cleanup() as part of performPeriodicMaintenance. This removes any pendingRequests entries that exceeded the response window while the application was offline. Peers must re-initiate synchronization after reconnection.
Can BitChat recover partial file transfers after network partitions?
Yes, through requestMissingFragments(fragmentStreamID:) in GossipSyncManager.swift. When fragment reassembly stalls, this method issues a targeted sync request filtered to specific fragment streams. The narrow scope avoids waiting for general GCS rounds and reduces recovery latency for large files.
How bandwidth-efficient is BitChat's sync protocol?
BitChat uses Golomb-Coded Set (GCS) filters in buildGcsPayload to achieve sub-linear bandwidth growth relative to set size. The sinceTimestamp cursor further eliminates redundant transmission of historical packets. For empty stores, minimal filters are sent (lines 52‑56). These optimizations target BLE's ~2 Mbps practical throughput limit.
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 →