# How BitChat Handles Persistence for Board Posts and Public Messages During Network Partitions

> Learn how BitChat ensures board posts and public messages persist through network partitions using its immutable signed packet store and automatic reloading.

- Repository: [permissionlesstech/bitchat](https://github.com/permissionlesstech/bitchat)
- Tags: internals
- Published: 2026-08-02

---

**BitChat uses an immutable, signed-packet store that persists board posts and public messages to disk via the `BoardStore` singleton, automatically reloading them on launch to survive network partitions and re-synchronizing via gossip when connectivity returns.**

BitChat, an open-source mesh messaging application from the `permissionlesstech/bitchat` repository, guarantees message durability across network outages by treating posts as immutable cryptographic packets written to local storage. The persistence layer ensures that both bulletin-board notices and public chat messages survive temporary partitions and seamlessly reconcile when peers reconnect.

## Board Post Persistence Architecture

### On-Disk Storage Format

In [`bitchat/Services/Board/BoardStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Board/BoardStore.swift), the store saves each raw signed packet exactly as received (`BitchatPacket`) alongside a retention deadline for tombstones. This design preserves cryptographic authenticity while providing explicit expiration controls for resource management.

### The BoardStore Singleton

`BoardStore` operates as a singleton via `static let shared`, maintaining live posts and tombstones in memory while mirroring changes to disk. When a new packet is accepted, the store immediately persists the updated state, ensuring that data survives app termination or device restarts without corruption.

### Ingestion and Validation Pipeline

The `BoardStore.ingest(_:, packet:)` method in [`bitchat/Services/Board/BoardStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Board/BoardStore.swift) handles the validation pipeline:

1. Decodes the packet into a `BoardWire` enum (either `.post` or `.tombstone`).
2. Validates the packet signature, checks expiration times, enforces clock-skew limits, and applies duplicate-detection rules.
3. Upon acceptance, calls `persistLocked()` to write to disk and emits `postArrivals` on the main thread for UI updates.

### Retention Rules and Quota Enforcement

The store enforces strict resource limits to ensure devices with constrained storage can still participate in the gossip protocol:

- **Post expiry**: Posts are retained until the author-specified expiration, capped at 7 days.
- **Tombstone retention**: Tombstones persist until the original post's expiry, or for an `orphanTombstoneLifetimeMs` window if the post was never received.
- **Storage quotas**: Each author is limited to 5 posts, with a global cap of 200 total posts. When limits are exceeded, the oldest entries are evicted.

## Public Message Persistence

Public chat messages follow the identical persistence pattern but utilize the generic `GossipMessageArchive` defined in [`bitchat/Models/GossipMessageArchive.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Models/GossipMessageArchive.swift). Each signed chat packet is stored as raw `Data` with an optional retain-until timestamp, allowing the `ChatTransport` layer to retrieve synchronization candidates via `syncCandidates()`. This unified approach ensures that public messages survive partitions using the same immutable packet store as board posts.

## Recovery After Network Partitions

When the app launches, `loadFromDisk()` (called from the `BoardStore` initializer) re-ingests all persisted packets from [`bitchat/Services/Board/BoardStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Board/BoardStore.swift), rebuilding the in-memory state from the raw signed data. Because packets are stored unchanged, any messages missed during a partition are automatically offered to peers during the next gossip synchronization cycle managed by [`GossipSyncManager.swift`](https://github.com/permissionlesstech/bitchat/blob/main/GossipSyncManager.swift). This mechanism ensures eventual consistency across the mesh network without requiring complex conflict resolution algorithms.

## Implementation Examples

The following Swift patterns demonstrate how BitChat interacts with the persistence layer during normal operation and synchronization:

```swift
// Ingest a board post (called from the radio or sync handler)
let result = BoardStore.shared.ingest(wire, packet: incomingPacket)
if result == .accepted {
    // UI will be notified via `postArrivals`
}

// Retrieve posts for a specific geohash (e.g. local area)
let localPosts = BoardStore.shared.posts(forGeohash: myGeohash)

// Get all packets that should be sent in the next gossip round
let gossipCandidates = BoardStore.shared.syncCandidates()

```

## Summary

- **Immutable storage**: Both board posts and public messages are stored as raw signed packets (`BitchatPacket`) in [`BoardStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BoardStore.swift) to preserve cryptographic authenticity.
- **Singleton pattern**: `BoardStore.shared` manages in-memory state and disk persistence via `persistLocked()` and `loadFromDisk()`.
- **Strict validation**: The `ingest()` method filters expired, duplicate, or clock-skewed packets before storage.
- **Resource limits**: Hard caps of 5 posts per author and 200 total posts prevent storage exhaustion on resource-constrained devices.
- **Automatic recovery**: On launch, `loadFromDisk()` restores state, and `syncCandidates()` makes missed messages available for gossip reconciliation once connectivity returns.

## Frequently Asked Questions

### How does BitChat ensure messages aren't lost when the network goes down?

BitChat writes every accepted packet to disk immediately via `BoardStore.persistLocked()`, maintaining the raw signed data in [`bitchat/Services/Board/BoardStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/Board/BoardStore.swift). When the app restarts after a partition, `loadFromDisk()` reconstructs the in-memory state from these immutable packets, ensuring no data is lost due to connectivity issues.

### What limits prevent the board storage from growing indefinitely?

The implementation enforces two quota rules: a maximum of 5 posts per author and a global limit of 200 total posts. When these caps are reached, the oldest entries are automatically evicted during the ingestion process, as implemented in the persistence logic of [`BoardStore.swift`](https://github.com/permissionlesstech/bitchat/blob/main/BoardStore.swift).

### How are deleted posts handled during a network partition?

When a post is deleted, a tombstone record is created that persists until the original post's expiration date or for a defined orphan window (`orphanTombstoneLifetimeMs`). These tombstones are stored alongside posts in `BoardStore` and re-broadcast after partitions to notify peers of deletions even if they missed the original content.

### Can public chat messages use the same persistence mechanism as board posts?

Yes, public messages utilize the same architectural pattern through `GossipMessageArchive`, storing raw signed packets that are retrieved via `syncCandidates()` and synchronized through the `ChatTransport` layer when connectivity resumes, ensuring consistent persistence across both message types.