logos-messaging-nim/docs/ARCHITECTURE_STUDY.md
Claude a0c637843c
docs: add protocol stack architecture study
Comprehensive top-to-bottom study of the logos-delivery stack: the three
API layers (Waku, MessagingClient, ReliableChannelManager), core protocols
(relay, lightpush, filter, store/archive/sync, discovery, RLN, mix), the
messaging and reliable-channel APIs, and the C FFI library.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KkHTPHnTiScRttjXQkfcdV
2026-06-28 21:53:54 +00:00

343 lines
27 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Logos Delivery — Protocol Stack Architecture Study
> A top-to-bottom study of how the logos-delivery (nwaku/Waku v2 fork) stack works:
> the layered API, the core protocols, how they interconnect, their dependencies,
> the messaging API, the reliable channel API, and the C FFI library.
---
## 0. The big picture
`logos-delivery` is a Nim implementation of a libp2p messaging stack (a rebrand/fork of
nwaku — Waku v2). It is organized as **three stacked API layers** sitting on top of a
**suite of libp2p protocols**, with a **C FFI library** wrapping the whole thing.
```
┌──────────────────────────────────────────────────────┐
C / mobile │ library/ (C ABI: liblogosdelivery.{so,dylib,a}) │
applications │ - logosdelivery_* (stable, messaging tier) │
│ - waku_* (kernel tier, raw protocols) │
└───────────────────────────┬──────────────────────────┘
│ one worker thread + chronos loop
┌───────────────────────────▼──────────────────────────┐
│ LogosDelivery (logos_delivery/logos_delivery.nim) │
│ pure concentrator: wires 3 layers, drives lifecycle │
└───────────────────────────┬──────────────────────────┘
┌──────────────────────┬─────────────┴───────────────┐
▼ ▼ ▼
ReliableChannelManager → MessagingClient ──────────────► Waku (WakuNode)
(E2E ordered, dedup, (send/recv with delivery (relay, lightpush,
segmentation, repair) confirmation + offline backfill) filter, store, sync,
discovery, RLN, mix)
```
The root type `LogosDelivery` (`logos_delivery/logos_delivery.nim`) is a **pure
concentrator**: it owns exactly one instance of each layer and chains them
`Waku ← MessagingClient ← ReliableChannelManager`, each layer driving the one below.
`new` builds bottom-up; `start` runs bottom-up; `stop` runs top-down so higher layers
drain first.
A recurring architectural device is the **broker context** (`brokers/broker_context`):
an in-process event/request bus that lets layers communicate without hard imports.
- `EventBroker` — fire-and-forget pub/sub (`MessageSentEvent`, `MessageReceivedEvent`, `ReadyToSendEvent`, …).
- `RequestBroker` — request/response with a single installable provider (`Encrypt`/`Decrypt`, `RequestRelayShard`, `RequestGenerateRlnProof`, shard/topic health). No provider ⇒ the request errors.
This bus is what keeps the reliable-channel layer fully decoupled from the layers below it,
and what lets RLN proof generation and shard resolution cross module boundaries.
---
## 1. Core data model (`waku/waku_core/`)
Every protocol imports these types. They are the universal vocabulary.
### WakuMessage — the universal envelope
`waku_core/message/message.nim`
| field | wire# | meaning |
|---|---|---|
| `payload: seq[byte]` | 1 | the application bytes (required) |
| `contentTopic: string` | 2 | logical topic (required) |
| `version: uint32` | 3 | legacy encryption discriminator |
| `timestamp: int64` | 10 | sender-set, **nanoseconds** (zigzag-encoded) |
| `meta: seq[byte]` | 11 | opaque marker, ≤ 64 bytes (used to route to the reliable-channel layer) |
| `proof: seq[byte]` | 21 | RLN rate-limit proof (RFC 17), opaque at core layer |
| `ephemeral: bool` | 31 | if true ⇒ never stored/archived |
Max message size: `DefaultMaxWakuMessageSize = 150 KiB`.
### Content topics & sharding
- **Content topic** (`topics/content_topic.nim`): `/<app>/<version>/<name>/<encoding>` (optionally with a leading generation). A string; structured form is `NsContentTopic`.
- **Pubsub topic / shard** (`topics/pubsub_topic.nim`): `RelayShard{clusterId, shardId}` rendered as `/waku/2/rs/<cluster>/<shard>`. Default = `/waku/2/rs/0/0`.
- **Auto-sharding** (`topics/sharding.nim`): a content topic deterministically maps to a shard via `sha256(application & version)` → last 64 bits mod shardCount. This is how a publish that supplies *only* a content topic deduces its pubsub topic.
### Deterministic hashing
`waku_core/message/digest.nim``computeMessageHash(pubsubTopic, msg) = SHA256(pubsubTopic ++ payload ++ contentTopic ++ meta ++ BE(timestamp))`. **`version`, `ephemeral`, and `proof` are deliberately excluded**, so the hash is a stable content identity even after an RLN proof is attached. This single 32-byte hash is used everywhere: relay message ID basis, store cursor, archive primary key, store-sync fingerprint, dedup keys.
### Protocol codecs (one place: `waku_core/codecs.nim`)
| Protocol | Codec |
|---|---|
| Relay | `/vac/waku/relay/2.0.0` |
| Lightpush v3 / legacy | `/vac/waku/lightpush/3.0.0` · `/vac/waku/lightpush/2.0.0-beta1` |
| Filter subscribe / push | `/vac/waku/filter-subscribe/2.0.0-beta1` · `/vac/waku/filter-push/2.0.0-beta1` |
| Store query | `/vac/waku/store-query/3.0.0` |
| Store-sync reconciliation / transfer | `/vac/waku/reconciliation/1.0.0` · `/vac/waku/transfer/1.0.0` |
| Metadata | `WakuMetadataCodec` |
| Peer exchange | `/vac/waku/peer-exchange/2.0.0-alpha1` |
| Rendezvous | `/vac/waku/rendezvous/1.0.0` |
---
## 2. The core protocols (`waku/`)
### 2.1 Relay — the gossipsub mesh (the hub)
`waku_relay/protocol.nim``WakuRelay = ref object of GossipSub` (subclasses libp2p GossipSub directly, RFC 29 tuning: `d=6, dLow=4, dHigh=8`, flood-publish, peer scoring, bad-peer disconnect).
- **Validators are the key extension point.** `addValidator` appends an app-level validator; `generateOrderedValidator` wraps them into one libp2p validator registered per topic. Any non-`Accept` short-circuits. **RLN is just one such validator.** The same validator list also gates lightpush via `validateMessage`.
- `subscribe` / `publish` / `unsubscribe`; `publish` returns peer count or a `PublishOutcome` error (`NoPeersToPublish`, `DuplicateMessage`, …).
- **Message ID** = `sha256(message.data)` (raw payload, implementation-agnostic).
- Per-topic **health** (UNHEALTHY / MINIMALLY / SUFFICIENTLY based on mesh peer count) is computed on a loop and emitted on the broker.
**Relay is the hub:** when a full node subscribes to a shard, `node/subscription_manager.nim`
installs one handler that fans every received message through, in order:
`trace(metrics) → filter.handleMessage → archive.handleMessage → storeSync.messageIngress → MessageSeenEvent(broker) → legacy app handler`.
That single chain is how relayed traffic reaches filter clients, the store, the sync mirror, and the app.
### 2.2 Lightpush — publish without running relay
`waku_lightpush/` (v3) and `waku_lightpush_legacy/` (v2). A light **client** sends one
`WakuMessage` to a **server** (a relay-running service node), which validates and publishes
it on the mesh.
- v3 wire: `LightpushRequest{requestId, pubSubTopic?, message}``LightPushResponse{requestId, statusCode, statusDesc?, relayPeerCount?}` with HTTP-like codes (200, 400, 413, 420, 429, 503, **504 OUT_OF_RLN_PROOF**, **505 NO_PEERS_TO_RELAY**). Auto-sharding can fill in the pubsub topic.
- The **relay bridge** is `callbacks.nim:getRelayPushHandler(relay, rlnPeer)`: attach RLN proof if needed → `relay.validateMessage``relay.publish`. So a lightpush request *becomes* a relay publish; the client never joins the mesh.
- Legacy v2 differences: single `PushRPC` envelope, boolean-only `PushResponse`, required pubsub topic, no auto-sharding, errors collapsed to "not published to any peer."
### 2.3 Filter v2 — receive without running relay
`waku_filter_v2/`. A light client registers `(pubsubTopic, contentTopic)` criteria with a
server and receives only matching messages.
- Two codecs: a **subscribe/request** channel (client→server: PING/SUBSCRIBE/UNSUBSCRIBE/UNSUBSCRIBE_ALL) and a **push** channel (server→client `MessagePush`, no response).
- Subscriptions are **soft-state with a 5-minute TTL** kept alive by client PINGs; the server prunes via a 1-minute maintenance loop and dedups pushes with a 2-minute cache.
- The filter *server* only has messages to push because it runs relay and is fed by the `subscription_manager` fan-out (`handleMessage`). The filter module has **no compile-time dependency on relay** — coupling is only at node assembly.
**Node types:** a **full/service node** mounts relay (joins the mesh) and optionally lightpush/filter/store *servers* to serve light clients. A **light node** runs no mesh: it publishes via the lightpush *client* and receives via the filter *client*.
### 2.4 Store / Archive / Store-sync — history & durability
- **Archive** (`waku_archive/`): local persistence behind an `ArchiveDriver` (driver pattern). Backends: **SQLite** (keyset-cursor pagination, schema v10), **Postgres** (partitioned, production/high-volume, `when defined(postgres)`), and an in-memory bounded `SortedSet` queue (25k cap). Ingests via `handleMessage` (computes hash, validates ±20 s timestamp drift, drops ephemeral, `driver.put`). Retention policies: time / capacity / size, on a 30-min loop.
- **Store** (`waku_store/`): the request/response query protocol over the archive. `StoreQueryRequest` carries content-topic/time filters, explicit `messageHashes`, and a **content-addressed cursor** (a `WakuMessageHash`, not an offset) for stable keyset pagination. The server is a thin shell: `requestHandler → archive.findMessages`. The client has `query` (one peer) and `queryToAny` (random peers, retry). `resume.nim` lets a node catch up on reconnect by querying everything since `max(lastOnline, now6h)`.
- **Store-sync** (`waku_store_sync/`): peer-to-peer **Range-Based Set Reconciliation** (Negentropy-family). Two sub-protocols: **reconciliation** (recursive 8-way range splitting with XOR fingerprints, escalating to explicit item-set exchange below a 100-element threshold) figures out the diff; **transfer** ships the missing messages. Seeds an in-memory `SeqStorage` mirror from the archive; transferred messages re-enter via `syncMessageIngress` (skips the timestamp validator — and inbound transfer messages are **not yet RLN-verified**, a known gap).
### 2.5 Networking, discovery & peers (`waku/net/`, `discovery/`, `waku_enr/`, `waku_metadata/`, `waku_peer_exchange/`, `waku_rendezvous/`)
- **PeerManager** (`node/peer_manager/`): the connectivity engine. Wraps the libp2p `Switch` and an extended **PeerStore** (custom "books": ENR, Shard, Source/origin, Connection, failure/backoff). Distinguishes **relay peers** (managed in bulk to in/out target counts, ~⅔ inbound) from **service peers** (store/filter/lightpush/px, individually pinned in `serviceSlots`). A 30 s **connectivity loop** maintains relay targets — shard- and capability-aware in sharded mode — with exponential backoff (120 s × 4ⁿ), parallel-dial caps, IP-colocation limits, and periodic store pruning + SQLite persistence.
- **Discovery** all funnels into `peerManager.addPeer(peer, origin)`:
- **discv5** (`waku_discv5.nim`): Ethereum node discovery, filters ENRs by a shard/cluster predicate, `searchLoop` feeds peers.
- **DNS** (EIP-1459 `enrtree://`): resolves bootstrap ENRs fed into discv5 setup.
- **Kademlia** (libp2p DHT): specialized here for **Mix**-capable peer/service discovery.
- **Rendezvous** (libp2p): cluster-scoped `rs/<cluster>/mix` namespace; distributes signed `WakuPeerRecord`s carrying mix pubkeys.
- **Peer exchange** (RFC 34): light nodes (no discv5) ask full nodes for a reservoir-sampled set of discv5-origin ENRs.
- **ENR** (`waku_enr/`): encodes a `waku2` **capability bitfield** (Relay=0, Store=1, Filter=2, Lightpush=3, **Sync=4, Mix=5** — the last two extend RFC 31), a len-prefixed `multiaddrs` field, and sharding (`rs` indices list < 64 shards, `rsv` 128-byte bit vector 64).
- **Metadata** (`waku_metadata/`): exchanged immediately on each connection; carries `clusterId` + live shards. **Gating** lives in the peer manager (`refreshPeerMetadata` on peer-join): **cluster mismatch ⇒ immediate disconnect+delete** this hard-partitions the network by cluster. Shard info only refines selection, it doesn't gate.
### 2.6 RLN — Rate Limiting Nullifier (live anti-spam)
`waku/rln/`. Enforces "N messages per epoch per member" cryptographically (RFC 17) and
**plugs into relay as a gossipsub validator** (`mountRlnRelay` `relay.addValidator`).
- **Validation** (`rln.nim:validateMessage`): parse `msg.proof` timestamp within 20 s epoch matches Merkle **root in the accepted window (≤ 50 roots)** **zk-SNARK verify** of the signal (`payload ++ contentTopic ++ BE(timestamp)`) **double-signal check** against the nullifier log (`Spam ⇒ Reject`).
- **Membership** comes from an on-chain Ethereum contract via web3 (`OnchainGroupManager`): no local Merkle tree; it caches this node's Merkle proof and refreshes accepted roots from `root()`/`getRecentRoots()`. A static/off-chain mode also exists.
- **Rate-limit enforcement is dual**: client-side `NonceManager` (monotonic message-id per epoch, `NonceLimitReached` at the limit) *and* cryptographic exceeding the limit reuses a slot, producing two Shamir shares on one line under the same nullifier, which (a) flags spam and (b) lets anyone interpolate the offender's identity secret (slashing primitive; on-chain slashing is a TODO).
- Proof **generation** at publish is broker-mediated (`RequestGenerateRlnProof`); the lightpush server attaches a proof if one is missing.
### 2.7 Mix — sender anonymity (opt-in)
`waku/waku_mix/protocol.nim` `WakuMix = ref object of MixProtocol`, a thin wrapper over
the external `libp2p_mix` **Sphinx mixnet** library. Sending is not a custom publish: the
caller wraps a normal lightpush stream via `wakuMix.toConnection(MixDestination.exitNode(peer), WakuLightPushCodec, MixParameters(...))` per-hop exponential delay (mean 50) defeats timing
correlation, SURBs enable anonymous replies. The mix pubkey (Curve25519) is advertised via
rendezvous peer records, `WakuInfo`, and Kademlia. Disabled by default; min pool size 4.
### 2.8 Incentivization (PoC, **not wired in**)
`waku/incentivization/` RFC 73 proof-of-concept: on-chain ETH-transfer txid eligibility
verification (`EligibilityManager`) + a ternary peer reputation table. Present and tested but
**not connected to any live path** (verified by grep).
---
## 3. The node integration hub (`waku/node/` + `waku/factory/`)
- **`WakuNode`** (`node/waku_node.nim`): a `ref object` holding *every* protocol instance
(relay, archive, store(+client/resume/sync/transfer), filter(+client), rln, lightpush(+legacy+clients),
peer exchange, metadata, mix, kademlia, rendezvous, libp2p ping, peer manager, switch, broker context,
subscription manager). Each protocol is attached by a `mount*` proc; `start`/`stop` cascade the lifecycle.
Publish/subscribe/query APIs live in `node/waku_node/{relay,lightpush,filter,store,…}.nim`.
- **`WakuConf`** (`factory/waku_conf.nim`): one validated config object. Convention:
**`Option[...]Conf` being `some` enables that protocol.** Assembled by per-concern
`conf_builder/*` builders.
- **`node_factory.nim`**: `setupNode builder.build (switch/peerManager/WakuNode.new)
setupProtocols (conditional mount*, order matters: metadata mix kademlia store
autosharding **relay** rendezvous/ping **RLN (after relay)** lightpush/filter/px)
startNode (connect bootstrap, start peer manager)`.
---
## 4. The messaging API (`logos_delivery/messaging/` + `logos_delivery/api/`)
This layer adds **delivery confirmation** and **offline backfill** on top of raw transport.
- **`MessageEnvelope`** (`api/types.nim`): `{contentTopic, payload, ephemeral, meta}` →
`toWakuMessage` stamps the timestamp. **`RequestId`** is a correlation handle returned by
`send`, and it is **shared across all three layers** — that is what lets the channel layer
correlate its segments with messaging-layer events.
- **`MessagingClient.send`**: auto-subscribes to the content topic (so the node sees its own
broadcast), mints a `RequestId`, builds a `DeliveryTask`, fires it at the `SendService`, and
returns the id immediately (fire-and-forget; completion arrives as events).
- **SendService** (reinforced publish): a fallback **processor chain** — `RelaySendProcessor`
(primary, gossipsub) → `LightpushSendProcessor` (fallback for edge/light nodes) — plus a 1 s
service loop that retries and, when `useP2PReliability` is on, **validates delivery by querying
a store node** for the message hash. State→event mapping:
- `SuccessfullyPropagated` → **`MessagePropagatedEvent`** (reached neighbors)
- `SuccessfullyValidated` → **`MessageSentEvent`** (confirmed archived in a store node)
- `FailedToDeliver` → **`MessageErrorEvent`**
- **RecvService**: dedups inbound (by hash), emits **`MessageReceivedEvent`**, and on
offline→online reconnect **backfills** missed messages from a store node over the offline gap.
---
## 5. The reliable channel API (`logos_delivery/channels/`)
A `ReliableChannel` gives an **end-to-end ordered, de-duplicated, gap-repairing** channel on
top of the messaging client. Spec: *reliable-channel-api*. One channel = one `ChannelId`
(= SDS channel id).
**Egress pipeline:** `segmentation SDS (reliability) rate-limit encryption dispatch`.
**Ingress pipeline:** the reverse. The manager builds a default `SendHandler` over
`MessagingClient.send`, so callers never wire transport.
### The reliability mechanism (SDS)
The actual state machine is the external **nim-sds** library (`ReliabilityManager`);
`channels/scalable_data_sync/` is the adapter mapping one manager to one channel. The scheme:
- **Outgoing**: each message gets a unique `SdsMessageID = keccak256(participantId ++ wrap-time-ns ++ content)`. `wrapOutgoingMessage` attaches a **Lamport timestamp** (channel clock) and a **causal history** (the last N delivered message IDs, N = `causalHistorySize`, default 2), and registers it in the outgoing buffer awaiting acknowledgement.
- **Acknowledgement** is implicit: a sent message is acked when it is **observed as a causal dependency of some peer's later message**. Unacked messages are **retransmitted** every `acknowledgementTimeoutMs` (default 5 s) up to `maxRetransmissions` (default 5).
- **Incoming**: deserialize, drop foreign channels, dedup against history (bloom/history), then:
- duplicate ⇒ consumed;
- **missing causal dependencies ⇒ park** the segment in `pendingContent` (bounded 32) until deps arrive;
- otherwise ⇒ deliver this message *plus* any parked segments that just became deliverable — **in causal order** (ingress is serialized by a lock).
- **SDS-R repair**: an `onRepairReady` callback rebroadcasts a full SDS envelope (skips the rate-limit queue, always `ephemeral`) to heal gaps for peers.
- **Persistence** (`waku/persistency/sds_persistency.nim`): snapshot model — lamport clock + outgoing/incoming + repair buffers persisted to SQLite; history reconstructed by sorting on `(lamportTimestamp, messageId)` — the same total order SDS uses for delivery. Channel state survives restart.
### Per-message-send bookkeeping
`ReliableChannel.send` segments the payload, SDS-wraps each segment, records a
`ChannelReqState{totalExpectedSegments, awaitingDispatch, inflightMessagingIds, confirmed, failed}`,
and enqueues. On dispatch each segment is encrypted (`Encrypt` request broker), tagged with
`meta = "RELIABLE-CHANNEL-API/1"` (the ingress-routing marker), and sent via the messaging client.
The channel listens for the messaging-layer `MessageSentEvent`/`MessageErrorEvent` (correlated by
the shared `RequestId`) and, when all segments resolve, emits **`ChannelMessageSentEvent`** or
**`ChannelMessageErrorEvent`**. Inbound, it filters by the `meta` marker + content topic, then
`Decrypt SDS handleIncoming reassemble ChannelMessageReceivedEvent`.
> **Component maturity (as of this study):** `segmentation` is currently a skeleton (one segment
> = whole payload; Reed-Solomon parity planned), `rate_limit_manager` is a pass-through (RLN-epoch
> budgeting planned), and `encryption` ships a no-op provider you must opt into. The SDS reliability
> core is real (delegated to nim-sds). Encryption requires installing an `Encrypt`/`Decrypt`
> provider on the broker or the request errors.
---
## 6. The C FFI library (`library/`)
Wraps `LogosDelivery` as a shared/static C library (`liblogosdelivery.{so,dylib,a}`). All FFI
plumbing (threading, request marshalling, callbacks, Nim runtime init) is delegated to the
external **nim-ffi** framework (pinned, not vendored in this checkout).
### Two-tier C API
- **Stable tier** `logosdelivery_*` (`library/logos_delivery_api/`, header `liblogosdelivery.h`):
`create_node`, `start_node`, `stop_node`, `destroy`, `subscribe`/`unsubscribe`, `send`,
`set_event_callback`. Calls into the **high-level** messaging client; protocol selection is hidden.
- **Kernel tier** `waku_*` (`library/kernel_api/`, header `liblogosdelivery_kernel.h`, "use at your
own risk"): ~45 functions reaching **straight into the node's protocols** — relay pub/sub,
filter, lightpush, store query, peer manager, discovery, ping. Raw `WakuMessage`/pubsub-level access.
Both tiers are `include`d into a single compilation unit and share one `FFIContext`; the split is
header/stability, not a binary boundary. The tiers mirror `LogosDelivery`'s own composition
(kernel→Waku node, stable→MessagingClient, events surface the reliable-channel layer).
### Calling & threading model
- Universal callback: `void (*FFICallBack)(int callerRet, const char *msg, size_t len, void *userData)`; return codes `RET_OK=0 / RET_ERR=1 / RET_MISSING_CALLBACK=2`.
- Universal shape: `fn(void *ctx, FFICallBack cb, void *userData, args)` returns a synchronous
dispatch status; the **real result/JSON/error arrives asynchronously via the callback**.
- **One worker thread, one chronos event loop.** `ctx` is an `FFIContext[LogosDelivery]` owning a
dedicated thread + watchdog + an SPSC request channel + signals. A C call packs args into an
`FFIThreadRequest`, hands it across the channel, and the worker runs the async body and fires the
callback **on the worker thread** (so callbacks must be fast, non-blocking, thread-safe). The C
example bridges back with `volatile` flags + polling.
### Event system
Two callback channels: (1) per-call result callbacks, and (2) a single **event callback**
(`set_event_callback`) fired repeatedly for the node's lifetime. Events are JSON strings. Two
families:
- **Low-level handlers** (wired at node creation): `message`, `relay_topic_health_change`, `connection_change`, `node_health_change`.
- **High-level broker events** (registered in `start_node`): `message_sent`, `message_error`, `message_propagated`, `message_received`, `connection_status_change`.
> Known rough edges flagged during the study: connection status surfaces under two different
> `eventType` strings (`node_health_change` vs `connection_status_change`), and `MESSAGE_EVENTS.md`
> documents only three of the message events.
---
## 7. End-to-end: the life of a message
**Sending (full app stack):**
1. App calls `ReliableChannel.send(channelId, payload)`.
2. Segmentation → SDS wrap (lamport ts + causal history, registered for ack) → rate-limit queue → encrypt → tag `meta="RELIABLE-CHANNEL-API/1"`.
3. `MessagingClient.send` → auto-subscribe + `DeliveryTask` → `SendService` chain: `relay.publish` (RLN proof attached via broker), or lightpush fallback.
4. Relay validators (incl. RLN) accept → gossipsub propagates → `MessagePropagatedEvent`.
5. SendService later queries a store node for the hash → `MessageSentEvent` (confirmed durable).
6. Channel tallies segment confirmations → `ChannelMessageSentEvent`.
**Receiving:**
1. Relay delivers the message; `subscription_manager` fan-out → filter, **archive (persist)**, store-sync mirror, and `MessageSeenEvent`.
2. `RecvService` dedups → `MessageReceivedEvent`.
3. Channel (matching `meta` + content topic) → decrypt → SDS `handleIncoming` (causal ordering, parks gaps, repairs) → reassemble → `ChannelMessageReceivedEvent`.
4. If the node was offline, `RecvService`/store-resume backfills the gap from a store node; SDS-R repairs any causal holes.
**Light node variant:** steps 34 of sending use the **lightpush client** instead of joining the
mesh; receiving uses the **filter client** (with PINGs to keep the 5-min subscription alive),
plus store backfill.
---
## 8. Dependency summary
- Everything depends on **`waku_core`** (WakuMessage, topics, sharding, digest, codecs).
- **relay** is the hub; **lightpush** depends on relay (via the push handler) and RLN; **filter**
is decoupled at compile time and coupled only at node assembly.
- **store / store-sync / store-resume** all depend on **archive**; archive depends only on its
driver + core.
- **discovery** (discv5/DNS/kademlia/rendezvous/PX) all converge on `peerManager.addPeer`;
**metadata** gates connections by cluster.
- **RLN** plugs into relay as a validator; reads membership from an on-chain contract.
- **mix** layers under lightpush; advertised via rendezvous/kademlia.
- The **broker context** decouples cross-layer calls (encryption, shard/health resolution, RLN proof gen) and carries the events the upper layers and the FFI subscribe to.
- The three API layers stack `Waku MessagingClient ReliableChannelManager`; the **FFI** wraps
all three and runs them on a single dedicated worker thread.
---
## Appendix — where to look
| Concern | Start here |
|---|---|
| Top-level wiring & lifecycle | `logos_delivery/logos_delivery.nim` |
| Messaging API | `logos_delivery/messaging/messaging_client.nim`, `api/types.nim` |
| Send reliability / store validation | `logos_delivery/messaging/delivery_service/` |
| Reliable channels | `logos_delivery/channels/reliable_channel.nim`, `…/reliable_channel_manager.nim` |
| SDS adapter | `logos_delivery/channels/scalable_data_sync/scalable_data_sync.nim` |
| Core data types | `logos_delivery/waku/waku_core/` |
| Node hub & mounting | `logos_delivery/waku/node/waku_node.nim`, `factory/node_factory.nim` |
| Relay | `logos_delivery/waku/waku_relay/protocol.nim` |
| Lightpush / Filter | `logos_delivery/waku/waku_lightpush/`, `waku_filter_v2/` |
| Store / Archive / Sync | `logos_delivery/waku/waku_store/`, `waku_archive/`, `waku_store_sync/` |
| Peers & discovery | `logos_delivery/waku/node/peer_manager/`, `discovery/`, `waku_enr/` |
| RLN anti-spam | `logos_delivery/waku/rln/` |
| Mix privacy | `logos_delivery/waku/waku_mix/protocol.nim` |
| C FFI | `library/` (+ `library/README.md`, `MESSAGE_EVENTS.md`) |