Spray-and-Wait Routing¶
Spray-and-Wait limits message replication to prevent flooding while ensuring delivery.
Overview¶
| Property | Value |
|---|---|
| Algorithm | Binary Spray-and-Wait |
| Default Budget | 3 copies |
| Max Budget | 8 copies |
| Expansion Timeout | 60 seconds |
Copy Budget Constants¶
| Constant | Value | Purpose |
|---|---|---|
DEFAULT_COPY_BUDGET |
3 | Initial copies per destination |
MAX_COPY_BUDGET |
8 | Maximum after expansion |
COPY_INCREASE_TIMEOUT_MS |
60,000 | Timeout before doubling budget |
Per-Transport Budgets¶
Each transport type has independent copy limits:
| Transport | Max Copies | Rationale |
|---|---|---|
| nearby | 3 | Direct local mesh |
| nostr | 2 | Per-relay limit |
| relay | 2 | Multi-hop via neighbor |
Total Maximum: 8 copies across ALL transports combined.
BundleCopyState¶
Tracks copy usage per bundle with the following fields: - destUidHex - Destination user ID - maxCopies - Maximum allowed copies - shortRangeUsed - Legacy counter for backward compatibility - nextExpansionMs - Legacy backoff timing - lastUpdatedMs - Last state update time - transportBudgets - Per-transport budget tracking
TransportBudget¶
Each transport maintains: - used - Number of copies sent via this transport - maxCopies - Maximum for this transport (default: 3) - backoffUntilMs - Backoff expiration time
Slot Reservation¶
Before sending via a transport, a slot must be reserved. The reservation checks: 1. Is the transport in backoff? → Skip 2. Are total copies exhausted? → Skip 3. Is the transport budget available? → Reserve and send
When a transport reaches its limit, it enters backoff until the timeout expires.
Copy Plan¶
Per-destination routing preference that tracks: - preferredTransportId - Best transport for this destination - preferUntilMs - Preference expiration time - copyBudget - Current budget for this destination - lastSuccessMs - Time of last successful delivery - lastUpdatedMs - Last plan update time
Copy Plan Update on ACK¶
When delivery is confirmed: 1. Set preferred transport based on successful path 2. Update last success time 3. Reduce copy budget (optimize for known-good paths) 4. Release bundle state
Copy Plan Expansion¶
When delivery fails (timeout): 1. Double the copy budget (up to maximum) 2. Clear preferred transport 3. Try all available paths
Transport Independence¶
Critical Design: One transport being exhausted does NOT block others.
If the "nearby" budget is full, messages can still be sent via "nostr" or "relay". Only when ALL transports are exhausted or in backoff is sending blocked.
Bundle Categories¶
Bundles are classified for routing decisions:
| Category | Routing Strategy |
|---|---|
| DM | PRoPHET + Spray-Wait + Geo |
| SUBSCRIPTION | Interest-aware + Spray |
| MESH | Always forward (mesh_local hint) |
| OTHER | Default forward |
Timeout Constants¶
| Constant | Value | Purpose |
|---|---|---|
| Multiplier | 3.5× | Timeout = latency × 3.5 |
| Minimum | 10 seconds | Floor for fast paths |
| Maximum | 5 minutes | Ceiling for slow paths |
Slot Release on Timeout¶
When a transport slot times out: 1. Decrement the used counter 2. Apply route penalty to PRoPHET table 3. Log the timeout event
JSON Events¶
| Event | When | Key Fields |
|---|---|---|
ROUTE.SLOT.BACKOFF |
Transport in backoff | bundle, transport, backoff_remaining_ms |
ROUTE.SLOT.TOTAL_EXHAUSTED |
All copies used | bundle, total_used, max |
ROUTE.SLOT.TRANSPORT_EXHAUSTED |
Transport budget used | bundle, transport, used, max |
ROUTE.SLOT.TIMEOUT_RELEASE |
Slot freed on timeout | bundle, transport, penalty_p |
ROUTE.SKIP.COPY_PLAN |
Send skipped | bundle, reason, transport |
Diagnostics¶
Via meshctl:
Shows destination count, pending bundles, transport preferences, and sample entries.
Next: Geo Routing | PRoPHET