📥 Dekodierte Transaktionen empfangen
Ein Decoded Shred Stream wird per UDP an die IP und den Port gepusht, die Sie auf der Stream-Detailseite konfigurieren — keine Verbindung, kein Handshake, keine erneute Übertragung. Jedes Datagramm ist höchstens 1.408 Bytes groß und trägt genau eine vollständige Transaktion: einen binären 16-Byte-Header, den Slot und die Transaktion im Standard-Wire-Format von Solana.
📦 Der Umschlag-Header (16 Bytes)
Jedes Datagramm beginnt mit demselben festen Header. Alle Ganzzahlen sind Little-Endian.
| Offset | Größe | Feld | Wert / Semantik |
|---|---|---|---|
| 0 | 2 | magic | 0x5AE7 (u16 LE) — alles andere ⇒ Datagramm verwerfen |
| 2 | 1 | version | 2 — jede unbekannte Version verwerfen |
| 3 | 1 | msg_type | 2 = DecodedTx (1 = reserviert) |
| 4 | 1 | flags | Reserviert, 0 |
| 5 | 1 | frag_index | Fragment-Index, 0-basiert — bei dekodierten Transaktionen immer 0 |
| 6 | 1 | frag_count | Fragment-Anzahl — bei dekodierten Transaktionen immer 1 (nie fragmentiert) |
| 7 | 1 | _pad | 0 |
| 8 | 8 | seq | u64 LE, monoton steigend pro Produkt-Stream |
đź§ľ Der Payload (msg_type = 2)
Direkt nach dem Header:
| Feld | Größe | Semantik |
|---|---|---|
slot | 8 | u64 LE — der Slot, zu dem die Transaktion gehört |
transaction | Rest des Datagramms | Standard-Wire-Format von Solana (bincode-serialisierte VersionedTransaction) |
Die Transaktionsbytes sind exakt das, was jedes Solana-SDK erwartet — geben Sie sie unverändert an Ihren bestehenden Parser. Dekodierte Transaktionen werden nie fragmentiert: ein Datagramm ist immer eine ganze Transaktion. Vote-Transaktionen sind standardmäßig ausgeschlossen.
📉 Verluste mit seq erkennen
UDP überträgt nicht erneut. Jedes Datagramm trägt eine seq, die innerhalb Ihres Streams um genau 1 steigt. Eine Lücke in seq bedeutet, dass entsprechend viele Datagramme verloren gingen:
seq empfangen = 1042, vorherige = 1039 → 2 Datagramme verloren
Merken Sie sich die zuletzt gesehene seq und alarmieren Sie bei Lücken — anhaltender Verlust bedeutet in der Regel, dass Ihr Empfangspuffer zu klein ist (siehe unten) oder ein Engpass auf dem Netzwerkpfad vorliegt.
🔧 Puffergröße
Der Stream ist bursty — ein aktiver Slot kann viele Transaktionen direkt hintereinander liefern. Geben Sie dem Socket einen großzügigen Empfangspuffer (SO_RCVBUF, mehrere Megabyte), damit kurze Bursts absorbiert statt vom Kernel verworfen werden, und leeren Sie den Socket aus einer dedizierten, schnellen Schleife.
đź’» Minimaler Decoder (Python)
import socket, structsock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)sock.setsockopt(socket.SOL_SOCKET, socket.SO_RCVBUF, 8 * 1024 * 1024)sock.bind(("0.0.0.0", 5000)) # der Port, den Sie im Dashboard konfiguriert habenexpected = Nonewhile True:dgram = sock.recv(2048)if len(dgram) < 24:continuemagic, version, msg_type = struct.unpack_from("<HBB", dgram, 0)if magic != 0x5AE7 or version != 2:continue # unbekannte magic/version verwerfenif msg_type != 2:continue # kein DecodedTx-Datagramm(seq,) = struct.unpack_from("<Q", dgram, 8)if expected is not None and seq != expected:print(f"gap: {seq - expected} datagram(s) lost")expected = seq + 1(slot,) = struct.unpack_from("<Q", dgram, 16)tx_bytes = dgram[24:] # Standard-Wire-Format — an jeden Solana-Tx-Parser übergeben
🦀 Offizielle Clients und das codec-Modul
Sie müssen den obigen Decoder nicht selbst schreiben: Die offiziellen decoded-shredstream-Clients (Rust, Go, JavaScript/TypeScript, Python) binden den Port, parsen den Umschlag, erkennen seq-Lücken und übergeben Ihnen jede Transaktion mit ihrem Slot und ihren Signaturen — über UDP und gRPC, mit derselben API.
// cargo add decoded-shredstream tokio --features tokio/macros,tokio/rt-multi-threaduse decoded_shredstream::{UdpClient, UdpConfig};#[tokio::main]async fn main() -> std::io::Result<()> {let mut client = UdpClient::bind(UdpConfig { port: 8002, ..Default::default() })?;while let Some(update) = client.next_update().await {println!("slot={} sig={} {}B", update.slot(), update.signature(), update.bytes().len());}Ok(())}
Das Rust-Crate stellt das Framing außerdem als codec-Modul bereit (Konstanten FRAME_MAGIC = 0x5AE7, FRAME_VERSION = 2, FRAME_HEADER_LEN = 16, MAX_DATAGRAM = 1408, parse_header, StreamDecoder), sodass Sie Datagramme dekodieren können, die Sie von einem selbst verwalteten Socket lesen — der Go-Client bietet dasselbe über ParseHeader / StreamDecoder:
use std::net::UdpSocket;use decoded_shredstream::codec::{Push, StreamDecoder, MAX_DATAGRAM};fn decode_loop(socket: &UdpSocket) -> std::io::Result<()> {let mut decoder = StreamDecoder::new();let mut buf = [0u8; MAX_DATAGRAM + 64];loop {let n = socket.recv(&mut buf)?;if let Push::Update(update) = decoder.push(&buf[..n]) {println!("slot={} sig={}", update.slot(), update.signature());}}}
Installation: npm install decoded-shredstream · pip install decoded-shredstream · cargo add decoded-shredstream · go get github.com/shredstream/decoded-shredstream-go.
➡️ Nächste Schritte
- Decoded Shred Stream — Einordnung, Latenz und Auslieferungsmodi.
- gRPC-Zustellung — dieselben Transaktionen über einen geordneten Stream mit serverseitiger Kontenfilterung.