What a transport-mode node does when it receives an inbound announce destined for a non-local destination. This is the flow that makes the mesh actually mesh — without it, announces never propagate beyond direct radio range. Pinned against **RNS 1.2.4**; cross-references [`../SPEC.md`](../SPEC.md) §4.5 (validation), §12.3 (rebroadcast rules), §12.4 (path table).
This flow only runs on a node with `enable_transport = Yes` per §12.1. Leaf clients can ignore it entirely.
---
## Sequence
### 1. Inbound announce passes validation
The receive-announce flow ([`receive-announce.md`](receive-announce.md)) runs first — signature check, dest_hash recompute, public-key collision check, ingress rate limit, path table population. By the time the rebroadcast logic runs, the announce is known-valid and the path_table entry is already updated.
if (Reticulum.transport_enabled() or is_from_local_client) \
and packet.context != PATH_RESPONSE \
and not rate_blocked:
# rebroadcast
```
-`transport_enabled OR is_from_local_client` — leaf clients only forward announces that came from a local-client interface (the rare "client behind shared rnsd" case).
-`packet.context != PATH_RESPONSE` — path-response announces are NOT rebroadcast (they go on a single specific interface back to the requester per [`path-discovery.md`](path-discovery.md) step 7).
-`not rate_blocked` — per-interface announce-rate limits aren't tripped for this destination.
### 3. Insert into `announce_table`
`Transport.py:1833-1844`. The relay adds an entry:
received_from, # 3 IDX_AT_RCVD_IF — interface NOT to rebroadcast on
announce_hops, # 4 IDX_AT_HOPS
packet, # 5 IDX_AT_PACKET — full Packet object
local_rebroadcasts, # 6 IDX_AT_LCL_RBRD — count of times peers retransmitted
block_rebroadcasts, # 7 IDX_AT_BLCK_RBRD — true if a peer beat us to it
attached_interface, # 8 IDX_AT_ATTCHD_IF
]
```
`retransmit_timeout` for non-local-client originated announces is `now + PATH_REQUEST_GRACE = 0.4s` (giving directly-reachable peers time to rebroadcast first; if they do, `block_rebroadcasts = True` is set and we suppress our own emission). Local-client originated announces fire `now` with no grace.
### 4. Periodic `Transport.jobs` walk drains the table
The relay's per-second-or-so housekeeping loop walks `announce_table` and for entries whose `retransmit_timeout <= now`, queues them for emission on each suitable interface:
```python
# Pseudocode of the relevant Transport.jobs branch
Each interface independently throttles its outbound announces against `interface.announce_cap` (default `Reticulum.ANNOUNCE_CAP = 2.0` = 2% airtime). `Interface.process_announce_queue` (`RNS/Interfaces/Interface.py:237-272`) drains the queue at a rate the cap permits, **picking the lowest-hop-count entry first** so closer destinations propagate before further ones:
The `wait_time` is what enforces the cap: on a 5kbps LoRa channel, a 200-byte announce takes 320ms airtime; with cap=2%, the next announce isn't allowed for `320ms / 0.02 = 16s`. This is what makes Reticulum's mesh well-behaved on slow shared channels.
### 6. Rebroadcast emission with hop increment
When the queue actually emits, the wire bytes are the original announce's `packet.raw` with the `hops` byte already incremented (`Transport.inbound` did this at line 1395 on receive). No re-signing — the signature in the announce body covers the original hop=0 emission, and signature validation ignores the outer hops byte (it's not in `signed_data`). What's wire-visible is the same body, the same dest_hash, the same random_hash, and a hops byte that's now (hops_received + 1).
### 7. Local-rebroadcast counter cleanup
If the relay later **hears its own rebroadcast** (a peer further along in the chain re-emitted it), `Transport.inbound` at line 1660-1668 increments `entry[IDX_AT_LCL_RBRD]`. Once `local_rebroadcasts >= LOCAL_REBROADCASTS_MAX`, the entry is removed from `announce_table`:
```python
if announce_entry[IDX_AT_LCL_RBRD] >= LOCAL_REBROADCASTS_MAX:
announce_table.pop(packet.destination_hash)
```
This is what stops the rebroadcast loop: once enough downstream peers have echoed the announce back, the relay stops trying.
### 8. `random_blob` replay defence
§4.5 step 6.3 / §12.3.2 already covers this — the relay won't even queue a rebroadcast if the inbound announce's `random_hash` is already in the cached `random_blobs` for this destination. The receive-announce flow drops it at the path-table-update stage.
---
## Wire-byte summary
The forwarded announce is wire-identical to the original, except:
- The `hops` byte is one higher.
- If the relay is between a HEADER_1-emitting originator and a destination > 1 hop away, the relay does NOT do the §2.3 conversion for ANNOUNCE packets — announces always travel HEADER_1+BROADCAST. The HEADER_2 conversion is only for DATA packets.
```
Before relay (received): After relay (emitted):
[ flags: HEADER_1+BROADCAST+SINGLE+ANNOUNCE ] [ same flags ]