# What Is the LRU Seen-Set in BitChat Flood Control?

> Understand the LRU seen-set in BitChat flood control. Discover how this duplicate detection mechanism prevents redundant packet processing in Bluetooth mesh networks.

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

---

**The LRU seen-set is a duplicate-detection mechanism that prevents BitChat's Bluetooth mesh network from processing the same packet multiple times during message flooding.**

BitChat spreads messages across a decentralized Bluetooth mesh using a flooding protocol where every relay rebroadcasts packets to its neighbors. Because packets can arrive via multiple paths or route-failure fallbacks, the protocol must recognize and discard duplicates instantly. According to the `permissionlesstech/bitchat` source code, the LRU seen-set provides this critical deduplication layer through three specialized cache implementations that ensure each flooding packet is processed **once** per node.

## Core Components of the LRU Seen-Set

The flood-control system relies on two distinct deduplication layers, both implementing LRU (Least-Recently-Used) eviction strategies to balance memory constraints against duplicate detection accuracy.

### MessageDeduplicationService

`MessageDeduplicationService` (located in [`bitchat/Services/MessageDeduplicationService.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/MessageDeduplicationService.swift)) provides the high-level deduplication logic for application-layer data. It maintains three separate LRU caches:

- **Content keys** – for near-duplicate message content
- **Nostr event IDs** – for Nostr protocol events
- **Nostr ACK keys** – for acknowledgment packets

Each cache is an instance of `LRUDeduplicationCache`, a generic LRU implementation. When a new ID or key arrives, the service checks `contains(_:)`. If the ID is present, the packet is ignored; otherwise, it is recorded using `record(_:value:)`. The service uses `@MainActor` to guarantee thread-safe access from the mesh-relay code.

### MessageDeduplicator

`MessageDeduplicator` (found in [`bitchat/Utils/MessageDeduplicator.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Utils/MessageDeduplicator.swift)) serves as the lower-level, thread-safe deduplicator used directly by the network layer (e.g., the BLE service). It maintains an ordered array of `Entry(id:timestamp)` structures plus a lookup dictionary for **O(1)** membership checks.

Old entries are evicted based on dual criteria:
- **Time-based eviction** – entries older than `maxAge` are removed
- **Capacity-based eviction** – the cache purges least-recently-used items when exceeding `maxCount`

This component uses `NSLock` to ensure safe concurrent access during high-throughput flooding scenarios.

### TransportConfig

`TransportConfig` (defined in [`bitchat/Services/TransportConfig.swift`](https://github.com/permissionlesstech/bitchat/blob/main/bitchat/Services/TransportConfig.swift)) supplies the capacity and expiry limits that bound the LRU sets. Key parameters include:

- `contentLRUCap = 2000` – maximum entries for content deduplication
- `messageDedupMaxAgeSeconds` – default 5-minute expiration for deduplication entries

These caps ensure the seen-set stays bounded, preventing unbounded memory growth while still covering the typical flood lifetime of a few seconds to minutes.

## Why an LRU Cache for Mesh Flooding?

Bluetooth mesh flooding creates specific technical constraints that make LRU caches the optimal deduplication strategy:

- **Floods are short-lived:** Packets travel only a few hops and are typically discarded after a few seconds. Evicting the *oldest* entries keeps the cache sized to recent traffic, which is exactly the window where duplicates can appear.

- **Bounded memory guarantees:** The cache never exceeds the configured capacity (e.g., 2,000 entries) and purges entries automatically when they age out, protecting embedded devices from memory exhaustion.

- **Thread-safe concurrency:** Both `MessageDeduplicationService` (via `@MainActor`) and `MessageDeduplicator` (via `NSLock`) guarantee safe concurrent accesses from multiple mesh-relay threads.

## Using the LRU Seen-Set in Practice

The following Swift examples demonstrate how to interact with the deduplication layer directly:

```swift
// 1️⃣ Create a deduplication service (uses the LRU caches)
let dedupService = MessageDeduplicationService()

// 2️⃣ When a new public message arrives, check for duplicate content:
if let _ = dedupService.contentTimestamp(for: incomingMessage.content) {
    // Duplicate – ignore this packet
} else {
    // First-time seen – record its timestamp for future dedup checks
    dedupService.recordContent(incomingMessage.content, timestamp: Date())
    // …handle the message normally
}

// 3️⃣ For a Nostr event ID:
if dedupService.hasProcessedNostrEvent(event.id) {
    // Duplicate event – drop it
} else {
    dedupService.recordNostrEvent(event.id)
    // Process the event
}

// 4️⃣ Direct use of the low-level deduplicator (e.g., in BLE service):
let dedup = MessageDeduplicator()
if dedup.isDuplicate(packet.id) {
    // Already seen – skip forwarding
} else {
    // Forward the packet to neighbors
}

```

## Summary

- The **LRU seen-set** prevents duplicate processing during BitChat's message flooding by tracking recently seen packet IDs and content hashes.
- `MessageDeduplicationService` handles application-layer deduplication for content and Nostr events using three specialized LRU caches.
- `MessageDeduplicator` provides the underlying thread-safe LRU implementation used by the BLE networking layer, with O(1) lookup and automatic eviction.
- Configuration parameters in `TransportConfig` (such as `contentLRUCap` and `messageDedupMaxAgeSeconds`) bound memory usage to safe limits.
- Together, these components eliminate duplicate deliveries, prevent replay loops, and maintain efficient mesh propagation.

## Frequently Asked Questions

### How does the LRU seen-set prevent replay attacks in BitChat?

The seen-set prevents replay attacks by maintaining a historical record of processed Nostr event IDs and message content hashes. When a packet arrives, the system checks against this set using `contains(_:)` or `isDuplicate()`. If the identifier exists, the packet is discarded immediately, ensuring old messages cannot be rebroadcast maliciously through the mesh.

### What happens when the LRU cache reaches its capacity limit?

When the cache reaches its configured capacity (e.g., `contentLRUCap = 2000`), the LRU eviction policy removes the least-recently-used entries to make room for new ones. In `MessageDeduplicator`, this works alongside time-based eviction (`maxAge`), ensuring the cache always contains the most relevant recent traffic while maintaining strict memory bounds suitable for mobile devices.

### How does `MessageDeduplicator` differ from `MessageDeduplicationService`?

`MessageDeduplicator` is a low-level utility class used directly by the BLE networking layer for raw packet deduplication, while `MessageDeduplicationService` is a higher-level service that manages three separate caches for application-specific data (content, Nostr events, and ACKs). The service acts as a facade with specialized methods like `hasProcessedNostrEvent()`, whereas the deduplicator provides generic `isDuplicate()` functionality with explicit timestamp tracking.

### What is the default expiration time for entries in the seen-set?

According to `TransportConfig`, the default expiration for message deduplication entries is controlled by `messageDedupMaxAgeSeconds`, typically set to 5 minutes. This duration covers the maximum expected lifetime of a flooding packet across the mesh while ensuring stale entries do not accumulate in memory. Content-specific caches may have separate capacity limits but follow similar aging strategies.