📥 Recevoir les transactions décodées
Un Decoded Shred Stream est poussé en UDP vers l'IP et le port que vous configurez depuis la page de détail du stream — pas de connexion, pas de handshake, pas de retransmission. Chaque datagramme fait au plus 1 408 octets et porte une transaction complète : un header binaire de 16 octets, le slot, puis la transaction au format wire Solana standard.
📦 Le header de l'enveloppe (16 octets)
Chaque datagramme commence par le mĂŞme header fixe. Tous les entiers sont en little-endian.
| Offset | Taille | Champ | Valeur / sémantique |
|---|---|---|---|
| 0 | 2 | magic | 0x5AE7 (u16 LE) — toute autre valeur ⇒ rejeter le datagramme |
| 2 | 1 | version | 2 — rejeter toute version inconnue |
| 3 | 1 | msg_type | 2 = DecodedTx (1 = réservé) |
| 4 | 1 | flags | Réservé, 0 |
| 5 | 1 | frag_index | Index du fragment, base 0 — toujours 0 pour les transactions décodées |
| 6 | 1 | frag_count | Nombre de fragments — toujours 1 pour les transactions décodées (jamais fragmentées) |
| 7 | 1 | _pad | 0 |
| 8 | 8 | seq | u64 LE, strictement croissant par flux produit |
đź§ľ Le payload (msg_type = 2)
Immédiatement après le header :
| Champ | Taille | Sémantique |
|---|---|---|
slot | 8 | u64 LE — le slot auquel appartient la transaction |
transaction | reste du datagramme | Format wire Solana standard (VersionedTransaction sérialisée en bincode) |
Les octets de la transaction sont exactement ce qu'attend n'importe quel SDK Solana — donnez-les tels quels à votre parser existant. Les transactions décodées ne sont jamais fragmentées : un datagramme est toujours une transaction entière. Les transactions de vote sont exclues par défaut.
📉 Détecter la perte avec seq
L'UDP ne retransmet pas. Chaque datagramme porte un seq qui augmente d'exactement 1 au sein de votre stream. Un trou dans seq signifie autant de datagrammes perdus :
seq reçu = 1042, précédent = 1039 → 2 datagrammes perdus
Gardez trace du dernier seq observé et alertez sur les trous — une perte soutenue signifie généralement un buffer de réception trop petit (ci-dessous) ou un goulot d'étranglement sur le chemin.
đź”§ Dimensionner le buffer
Le flux est en rafales — un slot chargé peut livrer de nombreuses transactions coup sur coup. Donnez au socket un buffer de réception généreux (SO_RCVBUF, plusieurs mégaoctets) pour absorber les rafales courtes au lieu de les laisser jeter par le noyau, et videz le socket depuis une boucle dédiée et rapide.
💻 Décodeur minimal (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)) # le port configuré sur votre dashboardexpected = 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 # rejeter magic/version inconnusif msg_type != 2:continue # pas un datagramme DecodedTx(seq,) = struct.unpack_from("<Q", dgram, 8)if expected is not None and seq != expected:print(f"trou : {seq - expected} datagramme(s) perdu(s)")expected = seq + 1(slot,) = struct.unpack_from("<Q", dgram, 16)tx_bytes = dgram[24:] # format wire standard — pour n'importe quel parser Solana
🦀 Clients officiels et le module codec
Vous n'avez pas à écrire le décodeur ci-dessus : les clients officiels decoded-shredstream (Rust, Go, JavaScript/TypeScript, Python) ouvrent le port en écoute, parsent l'enveloppe, détectent les trous de seq et vous livrent chaque transaction avec son slot et ses signatures — en UDP et en gRPC, avec la même 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(())}
Le crate Rust expose aussi le framing sous la forme du module codec (constantes FRAME_MAGIC = 0x5AE7, FRAME_VERSION = 2, FRAME_HEADER_LEN = 16, MAX_DATAGRAM = 1408, parse_header, StreamDecoder), pour décoder des datagrammes lus sur un socket que vous gérez vous-même — le client Go expose la même chose via 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.
➡️ Prochaines étapes
- Decoded Shred Stream — positionnement, latence et modes de livraison.
- Livraison gRPC — les mêmes transactions sur un flux ordonné, avec filtrage des comptes côté serveur.