Multicast Transaction NACK Retransmission Protocol
One-to-many network delivery does not tell the sender when a piece went missing. This lets a receiver report exactly what it did not get, and the sender resend only that.
Summary
- Why
- Multicast transaction delivery is best-effort, so listeners need a way to detect exactly which frame they missed and get it retransmitted without overwhelming the network or requiring persistent connections.
- What
- BRC-126 defines a UDP-based NACK retransmission protocol, with ADVERT, NACK, MISS, ACK, and THROTTLED datagrams, for recovering lost frames in BSV multicast transaction distribution.
- How
- A listener notices its per-flow SeqNum jumped by more than one, sends a 64-byte NACK to a retry endpoint chosen from its ADVERT-built registry, and reacts to the endpoint's ACK, MISS, or THROTTLED reply by cancelling the gap, escalating to another endpoint, or holding and retrying per the flow-control hint.
What this lets you do
- Detect a missing transaction frame from a gap in sequence numbers
- Send a NACK to request retransmission of exactly the missing frame
- Interpret ACK, MISS, and THROTTLED responses to decide whether to retry, escalate, or back off
- Discover retry endpoints automatically via periodic ADVERT beacons, sorted by tier and preference
- Avoid retransmit storms using cache TTLs, jitter, and exponential backoff
Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.
Reference for an AI
Everything an assistant needs to answer questions about BRC-126 accurately, including what it depends on.
The specification
Abstract
This BRC specifies the NACK-based retransmission and endpoint discovery protocol for the BSV multicast transaction distribution pipeline. It defines five UDP datagram formats — ADVERT, NACK, MISS, ACK, and THROTTLED — along with tier/preference-based endpoint selection, an escalation state machine, and configurable retransmit modes. The protocol operates on top of the BRC-124 data-plane and enables reliable gap recovery without requiring connection state at the ingress or listener tiers.
Copyright
This BRC is licensed under the Open BSV License.
Motivation
The BRC-124 data-plane delivers BSV transactions over IPv6 multicast with
best-effort semantics. Network congestion, interface buffer overflows, and MLD
snooping transitions can cause individual frames to be lost. Because BRC-124
frames carry a stable per-flow HashKey and a monotonic SeqNum stamped by the
ingress proxy, listeners can detect exactly which frame is missing without a
central sequence authority.
This BRC adds the reliability layer that allows listeners to recover those gaps:
- Gap detection — listeners identify missing frames when a frame's
SeqNumadvances by more than 1 from the last-seen value for a givenHashKeyflow. - NACK dispatch — listeners send a 64-byte NACK datagram to a retry
endpoint identifying the missing frame by its flow (
HashKey) and sequence number. - ACK/MISS/THROTTLED responses — every served NACK receives a deterministic 16-byte response; ACK confirms retransmit dispatched, MISS triggers immediate escalation, and the optional THROTTLED signals honest congestion (hold and retry the same endpoint without escalating).
- Endpoint discovery — retry endpoints periodically multicast a 56-byte ADVERT beacon; listeners maintain a dynamic registry sorted by tier and preference, with no manual configuration required in well-connected deployments.
- Flood prevention — deduplication and fill-suppression mechanisms prevent retransmit storms at scale.
Specification
Common Message Preamble
All BRC-126 datagrams begin with a 7-byte preamble:
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Network Magic | 0xE3E1F3E8 (BSV mainnet P2P magic) |
| 4 | 2 | Protocol Ver | 0x02BF (703, BSV large-block baseline) |
| 6 | 1 | MsgType | Identifies the message type (see below) |
The MsgType byte at offset 6 is in the same position as FrameVersion in
BRC-124 data frames. Values 0x10–0x2F are reserved for BRC-126 control
messages and are distinct from the data-frame version codes (0x01–0x07).
MsgType Values
| MsgType | Name | Size | Direction |
|---|---|---|---|
0x10 | NACK | 64 B | Listener → Retry endpoint |
0x11 | MISS | 16 B | Retry endpoint → Listener |
0x12 | ACK | 16 B | Retry endpoint → Listener |
0x13 | THROTTLED | 16 B | Retry endpoint → Listener |
0x20 | ADVERT | 56 B | Retry endpoint → Beacon group |
ADVERT Wire Format (MsgType 0x20) — 56 bytes
Sent periodically by retry endpoints to the beacon multicast group. Listeners use it to build and maintain their endpoint registry.
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Network Magic | 0xE3E1F3E8 |
| 4 | 2 | Protocol Ver | 0x02BF |
| 6 | 1 | MsgType | 0x20 (ADVERT) |
| 7 | 1 | Scope | 0x05 = site-local, 0x08 = org, 0x0E = global |
| 8 | 16 | NACKAddr | IPv6 unicast address listeners use for NACK requests |
| 24 | 2 | NACKPort | UDP port for NACK requests (default 9300) |
| 26 | 1 | Tier | Operator-assigned proximity tier; 0 = source-adjacent |
| 27 | 1 | Preference | Within-tier priority; higher = more preferred (default 128) |
| 28 | 2 | BeaconInterval | Beacon send interval in seconds; listeners TTL = 3 × this |
| 30 | 2 | Flags | Capability bitmask (see below) |
| 32 | 4 | InstanceID | CRC32c of hostname; stable across restarts |
| 36 | 4 | Reserved | Must be 0x00000000 |
| 40 | 16 | Reserved | Must be all zeros; reserved for future capability bitmap |
ADVERT Flags Bitmask
| Bit | Name | Meaning |
|---|---|---|
0x01 | (reserved) | Unused; must be zero |
0x02 | HasParent | Endpoint forwards cache-miss NACKs to an upstream endpoint |
0x04 | Draining | Entering shutdown; listeners should stop routing new NACKs |
0x08 | UnicastRetransmit | Supports unicast frame delivery to the NACK source |
0x10 | MulticastRetransmit | Retransmits via multicast to the original shard group |
HasParent: When set, the endpoint maintains a connection to a parent (higher-tier) endpoint and forwards NACKs on local cache miss. Listeners need only know their local tier; inter-tier forwarding is handled transparently.
Beacon group addresses are derived from the shard address scheme using the
reserved control-plane index 0xFFFD:
| Scope | Beacon Group |
|---|---|
Site (0x05) | FF05::B:FFFD |
Org (0x08) | FF08::B:FFFD |
Global (0x0E) | FF0E::B:FFFD |
Addresses use the IANA-aligned layout: bytes 0–1 carry the scope prefix, bytes
2–11 are zero (IANA 96-bit boundary), bytes 12–13 carry the IANA Bitcoin
group-id (0x000B), and bytes 14–15 carry the group index. Operators MAY
override the group-id via -mc-group-id.
NACK Wire Format (MsgType 0x10) — 64 bytes
Sent by a listener to a retry endpoint when a gap is detected. Identifies the
missing frame by its flow (HashKey) and sequence number range. For current
single-frame retrieval, StartSeq == EndSeq; range requests
(StartSeq < EndSeq) are reserved for future use.
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Magic | 0xE3E1F3E8 |
| 4 | 2 | ProtoVer | 0x02BF |
| 6 | 1 | MsgType | 0x10 (NACK) |
| 7 | 1 | Flags | Bit 0 (0x01) = Proxied; bits 1–7 reserved, must be 0 |
| 8 | 8 | HashKey | Stable per-flow XXH64 identifier; from BRC-124 frame bytes 40–47 |
| 16 | 8 | StartSeq | First missing SeqNum (inclusive) |
| 24 | 8 | EndSeq | Last missing SeqNum (inclusive); equals StartSeq for single-frame retrieval |
| 32 | 32 | SubtreeID | 32-byte batch identifier; from BRC-124 frame bytes 56–87; zeros = unset |
The listener opens a per-request ephemeral UDP socket ([::]:0), sends the
NACK, and waits up to 300 ms for a single response (MISS or ACK).
HashKey (offset 8) is the
HashKeyfield from the BRC-124 frame, computed asXXH64(senderIPv6 ∥ groupIdx ∥ subtreeID). It uniquely identifies the flow. The retry endpoint usesHashKeyas a per-flow rate-limiting key (NACK storm cap). A value of0bypasses the per-flow check.StartSeq / EndSeq (offsets 16/24) specify the range of missing sequence numbers. For current single-frame retrieval,
StartSeq == EndSeq. The retry endpoint looks up the frame using the 16-byte cache keyHashKey ∥ StartSeq.SubtreeID (offset 32) is carried for informational purposes; the cache key is
HashKey ∥ SeqNumand does not require SubtreeID for disambiguation.Flags / Proxied (offset 7, bit
0x01) marks a NACK that an endpoint issued on behalf of a downstream multicast domain (cross-domain proxying — see NACK Proxying). An endpoint receiving a NACK with this bit set MUST serve it from its own cache but MUST NOT re-proxy it, bounding any proxy chain to a single hop. The bit was previously reserved and is ignored by legacy endpoints, which simply never re-proxy.
NACK Proxying (cross-domain recovery)
A retry endpoint serving a downstream multicast domain (one fed by a listener's multicast egress rather than directly by the ingress proxy) can only cache what the listener actually emitted. A frame the listener never put on the downstream wire — egress send error, interface flap, or in-fabric loss — is missed identically by the downstream endpoint and every downstream consumer, so a downstream-only cache cannot repair it. NACK proxying recovers such frames from an upstream endpoint that received them directly from the ingress proxy:
- A downstream consumer NACKs the downstream endpoint, which suffers a local
cache miss and returns
MISSimmediately. - The downstream endpoint forwards the NACK to a statically configured upstream
endpoint with the Proxied flag set (
0x01). Recovery is asynchronous ("cache-warm"); no NACK worker is held waiting. - The upstream endpoint serves the proxied NACK from its cache. Because the requester (the downstream endpoint) is not joined to the upstream shard groups, the frame is returned by unicast to the NACK source — a proxied NACK is always served a unicast copy regardless of the upstream's advertised retransmit mode.
- The downstream endpoint re-caches the recovered frame (keyed
HashKey ∥ SeqNum, per-FrameVer TTL) and multicast-retransmits it into the downstream domain; the consumer's gap auto-fills viaTracker.Fill().
One-hop bound. The Proxied flag prevents an upstream endpoint from
re-proxying, so any proxy chain is at most one hop. Upstream discovery is by
static configuration, because a separated downstream domain generally cannot
receive upstream multicast ADVERT beacons. The HasParent ADVERT flag (0x02)
signals that an endpoint has an upstream parent configured. When several
downstream endpoints run with proxying enabled against a shared cache backend,
an in-flight claim deduplicates the upstream NACKs so only one endpoint recovers
each gap.
MISS Response (MsgType 0x11) — 16 bytes
Sent unicast to the NACK source when the requested frame is not in the endpoint's cache. On receipt, the listener advances immediately to the next endpoint (no backoff delay).
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Magic | 0xE3E1F3E8 |
| 4 | 2 | ProtoVer | 0x02BF |
| 6 | 1 | MsgType | 0x11 (MISS) |
| 7 | 1 | Flags | Reserved; must be 0x00 |
| 8 | 8 | SeqNum | Always 0 on MISS |
ACK Response (MsgType 0x12) — 16 bytes
Sent unicast to the NACK source when the frame was found and retransmit
dispatched. Whether the listener may cancel the gap on receipt depends on the
Flags byte — see Repair confirmation below.
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Magic | 0xE3E1F3E8 |
| 4 | 2 | ProtoVer | 0x02BF |
| 6 | 1 | MsgType | 0x12 (ACK) |
| 7 | 1 | Flags | 0x01 = multicast sent; 0x02 = unicast sent |
| 8 | 8 | SeqNum | SeqNum of the retransmitted frame (from BRC-124 bytes 48–55) |
The listener uses the SeqNum echo to confirm the gap entry matches before
cancelling it.
Repair confirmation. An ACK asserts that a retransmit was dispatched, not
that it was delivered, so the Flags byte determines whether the listener may
close the gap. On an ACK with 0x02 (unicast sent) the listener MUST NOT cancel
the gap entry: it MUST wait for the retransmitted frame itself and, if the frame
does not arrive, MUST resume escalation as though the attempt had timed out. This
matters because the retransmit can be lost on the same path that dropped the
original. An ACK without 0x02 conveys no such confirmation channel, so a
listener MAY cancel on the ACK alone, accepting that a lost retransmit will not be
retried; deployments that require repair to be verifiable SHOULD therefore enable
unicast retransmit.
THROTTLED Response (MsgType 0x13) — 16 bytes
Sent unicast to the NACK source when the request was rejected by a congestion-control tier that limits per-gap or per-flow request rate (see Flood Prevention). It is a flow-control signal, not a failure: the endpoint is healthy, and for a per-gap throttle a retransmit for this exact gap was likely just served and is propagating over the multicast data plane. On receipt a listener MUST hold the gap for the hinted backoff and retry the same endpoint; it MUST NOT escalate to another endpoint and MUST NOT count the throttle as a recovery failure.
| Offset | Size | Field | Description |
|---|---|---|---|
| 0 | 4 | Magic | 0xE3E1F3E8 |
| 4 | 2 | ProtoVer | 0x02BF |
| 6 | 1 | MsgType | 0x13 (THROTTLED) |
| 7 | 1 | Flags | Bits 0-3 = backoff bucket; 4-7 reserved |
| 8 | 8 | SeqNum | Echo of the throttled request's StartSeq |
Backoff hint. The suggested hold is ThrottleHintBase << bucket, where
ThrottleHintBase = 125 ms and bucket is the Flags low nibble. Endpoints
SHOULD use bucket 2 (~500 ms) for a per-gap throttle and bucket 3 (~1 s) for
a per-flow throttle. Listeners SHOULD apply jitter (e.g. uniform over
[hold/2, hold]) to de-synchronise and MAY clamp the hold to a local maximum.
The gap's absolute TTL remains the upper bound; a multicast repair cancels the
gap regardless.
Emission rules.
- THROTTLED is OPTIONAL and SHOULD default to disabled; it is a load-shedding refinement for high-fan-out deployments.
- An endpoint MUST NOT send THROTTLED for a flood-tier (per-source) rejection: that tier sheds abusive or spoofed-source load, and answering it would permit reflection. (The 16-byte response is smaller than the 64-byte NACK, so the protocol is never a bandwidth amplifier regardless.)
- An endpoint that does not implement THROTTLED stays silent; the listener falls back to timeout + backoff.
- A listener that does not recognise
0x13treats it as an unparseable response (timeout-equivalent), so emission is backward-compatible.
Tier / Preference Model
Retry endpoints are organised into tiers representing proximity to the transaction source, and assigned a preference within each tier.
| Tier | Meaning |
|---|---|
0 | Same AS as the ingress proxy (source-adjacent) |
1 | One AS boundary from source |
N | N AS hops from source |
0xFF | Static seed (no beacon received; lowest priority) |
Endpoints discovered via ADVERT carry their operator-assigned Tier (0–254) and
Preference (0–255). Endpoints registered via static configuration seed the
registry at Tier=0xFF, Preference=0.
Listeners sort the endpoint registry by (Tier ASC, Preference DESC). NACK
dispatch always selects the head of the sorted list; on MISS or timeout, the
listener advances to the next position.
Escalation State Machine
┌──────────┐
│ PENDING │ gap registered; hold-off jitter applied
└────┬─────┘
│ timer fires
▼
┌────────────────┐
│ NACKED(Tier-K) │ NACK sent to current (tier, preference) endpoint
└───┬────┬───┬───┘
│ │ │
│ │ └── Timeout ──► exponential backoff; retry next sweep
│ │
│ ├── MISS ──► advance to next endpoint at same tier (Preference DESC);
│ │ if tier exhausted, escalate to Tier K+1;
│ │ retry IMMEDIATELY (no backoff on MISS)
│ │
│ └── THROTTLED ──► hold for the hinted backoff; retry the SAME endpoint;
│ do NOT escalate, do NOT count as a recovery failure
│
├── ACK (no 0x02) ──► gap entry cancelled; done
└── ACK (0x02 unicast sent) ──► await frame ──► FILLED
└── timeout ──► advance endpoint
Any state ──► FILLED (multicast repair arrived via data plane)
gap entry cancelled; in-flight NACK socket times out harmlessly
- ACK received,
Flagswithout0x02: Cancel gap entry. No further NACKs sent for this gap (the repair is trusted, not confirmed). - ACK received with
0x02(unicast sent): Do NOT cancel. Await the retransmitted frame; on arrival the gap is FILLED, and on timeout resume escalation from the next endpoint. - MISS received: Advance to next endpoint at same tier by Preference; if tier exhausted, move to next tier; retry immediately.
- Timeout: Apply exponential backoff (capped at
nack-backoff-max); retry on next sweeper tick. - THROTTLED received: Hold the gap for the hinted backoff and retry the same endpoint. Do not escalate and do not count it as a recovery failure — it is congestion control (see THROTTLED Response).
- Multicast fill: The data-plane receive goroutine calls
Tracker.Fill()independently; gap cancelled regardless of NACK state.
Beacon Scopes
Three scope bytes are defined for the ADVERT wire format. The reference
implementation's -beacon-scope flag accepts site, org, global, both,
or all (both and all are equivalent: the ADVERT is sent to the site, org,
and global beacon groups simultaneously):
| Scope | Scope byte | Beacon group | Use case |
|---|---|---|---|
Site (0x05) | 0x05 | FF05::B:FFFD | Intra-site discovery; all listeners join at startup |
Org (0x08) | 0x08 | FF08::B:FFFD | Organisation-wide discovery for multi-site AS |
Global (0x0E) | 0x0E | FF0E::B:FFFD | Inter-AS discovery via MP-BGP MVPN / MSDP |
Beacon group addresses are shown in ASM (FF0x) form. Under SSM, substitute the
FF3x prefix per BRC-129 (FF35::B:FFFD site, FF3E::B:FFFD
global; inter-domain scope is SSM-only per RFC 8815). Because beacon groups are
control-plane, their source — the emitting retry endpoint — cannot be discovered
from within the group; SSM receivers (S,G)-join using the per-group bootstrap
source lists defined in BRC-129, not via ADVERT. The ADVERT and NACK wire
formats are unchanged across modes.
Scope byte 0xFF is used in ADVERT datagrams when the sender intends to cover
site, org, and global simultaneously (sends three ADVERTs). Listeners parse the
scope byte from each individual datagram.
To cover multiple scopes, run separate endpoint instances each configured with a
different -beacon-scope value. Each scope can use independent Tier and
Preference tuning.
Listeners compute TTL as 3 × BeaconInterval. An endpoint not heard for that
duration is evicted from the registry. Static seeds (-retry-endpoints) are
never evicted.
Configurable Retransmit Modes
Retransmit behavior is controlled by flags on the retry endpoint:
| Flag | Default | Effect |
|---|---|---|
-beacon-flags-multicast | true | Retransmit frame to original multicast shard group |
-beacon-flags-unicast | false | Retransmit frame unicast to NACK source |
-suppress-miss | false | Do not send MISS response on cache miss |
-suppress-ack | false | Do not send ACK response on cache hit |
Deployment profiles:
- On-fabric (default):
-beacon-flags-multicast=true— all listeners on the fabric receive the retransmit simultaneously. - Edge / unicast:
-beacon-flags-unicast=true -beacon-flags-multicast=false— for listeners not on the multicast fabric, and wherever repair must be confirmed rather than trusted (see Repair confirmation above). A multicast retransmit is also delivered to every listener in the group, including those that observed no loss. - High-volume:
-suppress-ack=true— reduces per-frame ACK traffic; MISS responses are preserved for escalation correctness.
Flood Prevention
| Mechanism | Component | Effect |
|---|---|---|
| Cache TTL (60 s) | Retry endpoint | Frames expire naturally; bounds retransmit window |
Tracker.Fill() | Listener | Multicast repair cancels all pending NACKs for a gap |
| Jitter hold-off | Listener | Randomised delay before first NACK suppresses correlated duplicates |
| Exponential backoff | Listener | Reduces NACK rate on persistent or repeated gaps |
MaxRetries + GapTTL | Listener | Gap entries evicted after retry exhaustion or absolute deadline |
| THROTTLED hint | Retry endpoint | Tells throttled listeners to hold (vs. timeout + escalate), cutting NACK load on honest congestion; IP flood tier stays silent |
Examples
ADVERT Datagram (56 bytes)
A site-scope endpoint at fd20::24, port 9300, Tier 0, Preference 128,
60-second interval, multicast retransmit enabled:
E3E1F3E8 // Network Magic
02BF // Protocol Version
20 // MsgType = ADVERT
05 // Scope = site-local
FD200000000000000000000000000024 // NACKAddr = fd20::24
2454 // NACKPort = 9300
00 // Tier = 0
80 // Preference = 128
003C // BeaconInterval = 60 s
0010 // Flags = MulticastRetransmit
A1B2C3D4 // InstanceID (CRC32c of hostname)
00000000 // Reserved
00000000000000000000000000000000 // Reserved (16 bytes)
NACK Datagram (64 bytes)
Single-frame retrieval: missing frame for flow HashKey=0xA1B2C3D400000001,
SeqNum=1235:
E3E1F3E8 // Network Magic
02BF // Protocol Version
10 // MsgType = NACK
00 // Flags (reserved)
A1B2C3D400000001 // HashKey (XXH64 of sender+group+subtree)
00000000000004D3 // StartSeq = 1235
00000000000004D3 // EndSeq = 1235 (single frame)
baadf498a00ca5a44d1c4d9d103b49017f53cd8cb2a70a9c67fc884ecdd622b5 // SubtreeID
MISS Response (16 bytes)
E3E1F3E8 // Network Magic
02BF // Protocol Version
11 // MsgType = MISS
00 // Flags
0000000000000000 // SeqNum = 0 (cache miss)
ACK Response (16 bytes)
Frame found and retransmitted via multicast:
E3E1F3E8 // Network Magic
02BF // Protocol Version
12 // MsgType = ACK
01 // Flags = multicast_sent
00000000000004D3 // SeqNum of retransmitted frame (1235)
References
- BRC-12: Raw Transaction Format — Payload format for transaction data within BRC-124 frames
- BRC-124: Multicast Transaction Frame Format — Data-plane frame
format; defines
HashKey,SeqNum, andSubtree IDstamped by the proxy - BRC-129: Multicast Group Address Assignments — SSM/ASM addressing, scopes, and
(S,G)source discovery
Constants Reference
| Name | Value | Hex | Description |
|---|---|---|---|
MagicBSV | 3823236072 | 0xE3E1F3E8 | BSV mainnet P2P magic |
ProtoVer | 703 | 0x02BF | Protocol version |
MsgTypeNACK | 16 | 0x10 | NACK request |
MsgTypeMISS | 17 | 0x11 | MISS response |
MsgTypeACK | 18 | 0x12 | ACK response |
MsgTypeADVERT | 32 | 0x20 | Endpoint advertisement beacon |
ScopeSite | 5 | 0x05 | Site-local beacon scope |
ScopeOrg | 8 | 0x08 | Organisation beacon scope |
ScopeGlobal | 14 | 0x0E | Global beacon scope |
NACKSize | 64 | 0x40 | NACK datagram size in bytes |
FlagProxied | 1 | 0x01 | NACK flag: proxied, one-hop downstream |
FlagMulticastSent | 1 | 0x01 | ACK flag: multicast retransmit sent |
FlagUnicastSent | 2 | 0x02 | ACK flag: unicast retransmit sent |
FlagHasParent | 2 | 0x02 | ADVERT flag: upstream endpoint set |
FlagDraining | 4 | 0x04 | ADVERT flag: endpoint is draining |
FlagUnicastRetransmit | 8 | 0x08 | ADVERT flag: unicast retransmit |
FlagMcastRetransmit | 16 | 0x10 | ADVERT flag: multicast retransmit |
GroupBeacon | 65533 | 0xFFFD | Control-plane beacon group index |
DefaultNACKPort | 9300 | 0x2454 | Default NACK/ADVERT UDP port |
DefaultBeaconInterval | 60 | — | Default ADVERT send interval (s) |
DefaultCacheTTL | 60 | — | Default frame cache TTL (s) |
TierStaticSeed | 255 | 0xFF | Tier assigned to static seed endpoints |