How Waku's Topic Filters Cut Bandwidth for Resource-Constrained Nodes
Waku's topic-based subscription filter uses GossipSub's scored peer swarm to forward only matching messages, cutting bandwidth for mobile wallets and light clients. Exact-match filtering keeps evaluation O(1) but requires deliberate topic design.
14 Apr 2026, 08:15 UTC

The Bandwidth Problem in Decentralized Messaging
Running a full Ethereum node already demands significant bandwidth. Adding a gossip-based messaging layer on top can push resource-constrained devices—mobile wallets, light clients, embedded hardware—past their limits. Floodsub, the naive pub/sub approach, broadcasts every message to every peer. In a network of n nodes, that's O(n) bandwidth per message. For a chat application or status update feed where a node only cares about a handful of topics, forwarding irrelevant traffic wastes capacity and battery.
Waku v2 addresses this with a selective topic-based subscription filter built on GossipSub. Instead of flooding, each node evaluates topic filters locally and forwards only matching messages. This is a core practical engineering decision that makes decentralized messaging viable on constrained hardware.
GossipSub: Controlled Swarm Instead of Flood
GossipSub replaces floodsub's indiscriminate broadcast with a scored peer swarm. Peers are selected by distance (Kademlia XOR metric in the DHT) and score (behavioral reputation: valid signatures, timely propagation, no spam). Each node maintains a mesh of ~6–12 peers per topic. Messages propagate through this mesh via IHAVE/IWANT exchanges, not raw pushes.
The result: a message published to topic /waku/2/default-content/protobuf reaches only nodes that subscribed to that topic, hopping through peers that also serve the topic. Nodes not subscribed never see the payload. Bandwidth scales with relevant subscriptions, not total network size.
Local Filter Evaluation: Each Node Decides
When a GossipSub message arrives, the node checks its local subscription set. If the message's topic string exactly matches a subscribed filter, the node:
- Delivers the message to the application layer (local inbox)
- Forwards it to mesh peers for that topic
- Caches it for potential
IWANTrequests from late-joining peers
If the topic doesn't match, the message is dropped immediately—no forwarding, no cache storage. This evaluation happens in the libp2p pubsub layer before the payload reaches the Waku application logic. The filter is a simple string equality check: topic == subscribedTopic.
Configurable Caching for Offline Sync
Nodes cache messages with a configurable TTL (default ~20 minutes in go-waku) and optional persistence to disk. When a node restarts or reconnects after being offline, it can request missed messages via IWANT from mesh peers that retained them. The cache key is the message ID (hash of payload + metadata), so duplicates are deduplicated automatically.
This design lets a mobile wallet go offline for minutes or hours, then resync only the topics it cares about—without downloading the full network history.
Worked Example: Subscribing to a Single Topic
Start a go-waku node (v0.24+) subscribed only to /myapp/1/chat/protobuf:
waku --nodekey=key.pem \
--topic=/myapp/1/chat/protobuf \
--discv5-bootnodes=enr://... \
--rpc-admin=true
In another terminal, publish to a different topic:
waku-cli --rpc-admin=true pubsub publish \
--topic=/otherapp/1/notifications/protobuf \
--payload='{"text":"ignored"}'
Then publish to the subscribed topic:
waku-cli --rpc-admin=true pubsub publish \
--topic=/myapp/1/chat/protobuf \
--payload='{"text":"delivered"}'
Query the local inbox via the JSON-RPC API:
curl -X POST http://127.0.0.1:8645 \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"waku_messages","params":[{"topic":"/myapp/1/chat/protobuf"}]}'
Only the second message appears. The first was dropped at the pubsub layer—never written to disk, never forwarded. Remove the --topic flag and restart; both messages would now arrive (if still cached by peers).
Trade-off: Exact Match Only
The base protocol supports exact topic string equality only. No wildcards (/myapp/*), no regex, no hierarchical matching. If your application uses /myapp/1/chat, /myapp/2/chat, /myapp/3/chat, you must subscribe to each individually. This keeps the filter evaluation O(1) and the mesh management simple, but it pushes topic hierarchy design onto the application layer.
Workarounds exist: use a single broad topic with content-based filtering in the application, or run multiple light nodes each subscribed to a subset. Both add complexity. The Waku team has discussed wildcard support in Waku v3 (RLN-based privacy layer), but it's not in the current stable release.
Verify It Yourself
Run the example above with --log-level=debug and watch the pubsub traces. You'll see REJECT for non-matching topics and ACCEPT for matching ones. For programmatic verification, use the go-waku SDK:
sub, err := waku.SubscribeTopic(ctx, "/myapp/1/chat/protobuf")
if err != nil { log.Fatal(err) }
count := 0
for msg := range sub.Ch {
count++
fmt.Printf("Got message %d: %s\n", count, string(msg.Payload))
}
// Unsubscribe and verify count stops increasing
sub.Unsubscribe()
This confirms the filter operates at the libp2p layer, not as a post-hoc application filter.
Closing: Start Narrow, Measure, Expand
If you're integrating Waku into a resource-constrained client, start with the minimal topic set your feature needs. Measure actual bandwidth with ifconfig or nethogs on the device. Add topics only when user behavior demands it. The exact-match limitation means your topic naming scheme is a architectural decision—design it like an API versioning strategy, not an afterthought.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.