Skip to content
Docs

Run a minimal viable receiver

Confirm end-to-end delivery by printing common shred headers from live datagrams.

Before you start

  • Rust installed
  • A verified public UDP destination
  • Firewall access for 64.130.40.90

Compile and run the receiver

Save the program below as receiver.rs. It uses only the Rust standard library, binds the address passed on the command line, rejects packets from every source IP except 64.130.40.90, validates the common header length, classifies known legacy and Merkle variant bytes, and prints the slot, index, version, FEC-set index, datagram length, and source.

rust

use std::{env, io::{self, Write}, net::{Ipv4Addr, SocketAddr, UdpSocket}};

const SOURCE: Ipv4Addr = Ipv4Addr::new(64, 130, 40, 90);
const MAX_DATAGRAM: usize = 1228;
const COMMON_HEADER: usize = 83;

fn u16le(b: &[u8]) -> u16 {
    u16::from_le_bytes(b.try_into().expect("two bytes"))
}

fn u32le(b: &[u8]) -> u32 {
    u32::from_le_bytes(b.try_into().expect("four bytes"))
}

fn u64le(b: &[u8]) -> u64 {
    u64::from_le_bytes(b.try_into().expect("eight bytes"))
}

fn shred_kind(variant: u8) -> Option<&'static str> {
    match variant {
        0xa5 => Some("legacy-data"),
        0x5a => Some("legacy-code"),
        v => match v & 0xf0 {
            0x80 | 0x90 | 0xb0 => Some("merkle-data"),
            0x40 | 0x60 | 0x70 => Some("merkle-code"),
            _ => None,
        },
    }
}

fn report(mut line: String, error: bool) -> io::Result<()> {
    line.push('
');
    if error { io::stderr().lock().write_all(line.as_bytes()) }
    else { io::stdout().lock().write_all(line.as_bytes()) }
}

fn main() -> io::Result<()> {
    let bind = env::args().nth(1).unwrap_or_else(|| "0.0.0.0:8001".into());
    let socket = UdpSocket::bind(&bind)?;
    report("listening on ".to_owned() + &bind + ", accepting source " + &SOURCE.to_string(), true)?;
    let mut packet = [0u8; MAX_DATAGRAM];
    loop {
        let (len, peer) = socket.recv_from(&mut packet)?;
        let allowed = match peer {
            SocketAddr::V4(v4) => *v4.ip() == SOURCE,
            SocketAddr::V6(_) => false,
        };
        if allowed == false { continue; }
        if len < COMMON_HEADER {
            report("short datagram: len=".to_owned() + &len.to_string(), true)?;
            continue;
        }
        let variant = packet[64];
        let Some(kind) = shred_kind(variant) else {
            report("unknown variant byte=".to_owned() + &variant.to_string(), true)?;
            continue;
        };
        let slot = u64le(&packet[65..73]);
        let index = u32le(&packet[73..77]);
        let version = u16le(&packet[77..79]);
        let fec = u32le(&packet[79..83]);
        let line = "slot=".to_owned() + &slot.to_string()
            + " type=" + kind + " index=" + &index.to_string()
            + " fec=" + &fec.to_string() + " version=" + &version.to_string()
            + " len=" + &len.to_string() + " source=" + &peer.to_string();
        report(line, false)?;
    }
}

The program is under 100 lines. Compile and start it with:

bash

rustc -O receiver.rs
./receiver 0.0.0.0:8001

Replace port 8001 with the verified destination port. If the receiver sits behind static NAT, bind the private local address or 0.0.0.0, not the public translated address. Permit inbound UDP from 64.130.40.90/32 through every firewall layer.

Understand what the program proves

Printed rows prove that UDP datagrams from the service reached the process and that their first 83 bytes match a recognized common-header layout. The common header contains a 64-byte signature followed by variant, slot, index, shred version, and FEC-set index. All integer fields shown are little-endian in the currently documented layout.

The program does not validate the leader signature, Merkle proof, data or coding header, declared data size, parent relationship, or FEC configuration. It does not detect kernel receive-buffer overflow because the standard library does not expose SO_RXQ_OVFL ancillary data. It also performs synchronous text output inside the receive loop, which can cause loss when stdout blocks.

Use it for commissioning, not production. Stop it after confirming live output and move to a buffered receiver with SO_RCVBUF, overflow reporting, batching, bounded queues, metrics, and the Agave parser.

Check the output

Slots should generally move forward, but UDP can reorder packets and forks can expose competing proposals. Data and coding shreds use independent index sequences. Many shreds share one fec value because that value identifies the erasure set. Duplicate lines are possible and must not be treated as two different shards.

Datagram lengths must be no greater than 1,228 bytes. The mean observed packet is 1,216 bytes. A shorter packet is not automatically invalid because format variants determine their allowed size, so a production receiver should ask the matching Agave parser to sanitize it.

If output shows only unknown variant, capture the variant values and compare them with the Agave release used by the network. Do not add a guessed mask. Pin and test the parser from the matching release.

Diagnose no output

Run sudo tcpdump -ni any udp port 8001 and src host 64.130.40.90. If packets appear there but the program prints nothing, confirm the bind address, port, process, and IP source. If no packets appear, inspect the cloud security group, host firewall, NAT rule, and verification status. Use ss -lunp 'sport = :8001' to confirm the socket exists.

Text printing becomes the bottleneck quickly. Redirecting output to a slow terminal or log collector can fill the socket queue. For a short test, sample one row per several hundred packets if needed. A production implementation should increment counters on the receive thread and send parsed metadata through a bounded channel.

Move to the protocol implementation

The next receiver must construct a typed shred with the matching Agave ledger crate, sanitize it, resolve the scheduled leader for its slot, verify the leader signature over the format-defined signed data, group it by (slot, fec_set_index), recover missing data where possible, order data indices, deshred at completion boundaries, and deserialize entries using the codec used by that Agave release.

Keep one short output sample with the deployment record. It should include both data and coding rows, several FEC-set values, the observed version, and datagram lengths. Do not retain a continuous terminal log. The sample proves commissioning state, while counters and bounded captures provide production evidence without blocking receipt.

Parameters

NameTypeDefaultNotes
bindSocketAddrV40.0.0.0:8001First command-line argument and verified destination port.
sourceIpv4Addr64.130.40.90Only source IP admitted by the program.
max_datagramusize1228Maximum documented UDP payload accepted without truncation.

When it goes wrong

bind: Address already in use

Cause. Another process already owns the selected UDP port.

Fix. Find it with ss -lunp, then stop it or use the verified port assigned to this receiver.

short datagram: len=N

Cause. A source-address-matching datagram does not contain the 83-byte common header.

Fix. Capture it, verify the sender path, and reject it before reading header fields.

unknown variant: 0xNN

Cause. The datagram uses an unsupported format or is not a valid shred.

Fix. Compare with the network's pinned Agave release and use its typed parser rather than adding a guessed value.

Questions

Does this receiver verify that shreds are genuine?
No. It filters the source IP and prints recognized common-header fields, but source addresses can be spoofed on some paths and headers can be malformed. A production receiver must resolve the slot leader and verify the format-defined Ed25519 signature and Merkle proof before trusting payload bytes.
Why does the example parse fixed common-header offsets?
The program is a dependency-free commissioning tool for the documented layout. Production code should use the parser from the Agave release matching the network, because variants and serialization behavior change. The example rejects unknown variants instead of extending the format from an assumption.
Why can printing every packet cause drops?
Terminal and logging output can block for much longer than the roughly 179-microsecond mean packet interval. The example keeps printing because visible output is its commissioning purpose. Production code must remove formatting from the receive loop and publish counters or sampled records asynchronously.