📥 Приём декодированных транзакций

Decoded Shred Stream доставляется по UDP в режиме push на IP и порт, которые вы настраиваете на странице деталей стрима — без соединения, без handshake, без повторной передачи. Каждая датаграмма занимает не более 1 408 байт и несёт одну целую транзакцию: бинарный заголовок из 16 байт, слот и транзакцию в стандартном wire-формате Solana.


📦 Заголовок конверта (16 байт)

Каждая датаграмма начинается с одного и того же фиксированного заголовка. Все целые числа — в формате little-endian.

СмещениеРазмерПолеЗначение / семантика
02magic0x5AE7 (u16 LE) — любое другое значение ⇒ отбросить датаграмму
21version2 — отбрасывайте любую неизвестную версию
31msg_type2 = DecodedTx (1 = зарезервировано)
41flagsЗарезервировано, 0
51frag_indexИндекс фрагмента, с нуля — всегда 0 для декодированных транзакций
61frag_countЧисло фрагментов — всегда 1 для декодированных транзакций (никогда не фрагментируются)
71_pad0
88sequ64 LE, монотонно возрастает в рамках потока продукта

🧾 Полезная нагрузка (msg_type = 2)

Сразу после заголовка:

ПолеРазмерСемантика
slot8u64 LE — слот, к которому относится транзакция
transactionостаток датаграммыСтандартный wire-формат Solana (сериализованный через bincode VersionedTransaction)

Байты транзакции — ровно то, что ожидает любой SDK Solana: передайте их вашему существующему парсеру без изменений. Декодированные транзакции никогда не фрагментируются: одна датаграмма — всегда одна целая транзакция. Vote-транзакции исключены по умолчанию.


📉 Обнаружение потерь через seq

UDP не выполняет повторную передачу. Каждая датаграмма несёт seq, который увеличивается ровно на 1 в пределах вашего стрима. Пропуск в seq означает потерю соответствующего числа датаграмм:

text
получен seq = 1042, предыдущий = 1039 → потеряно 2 датаграммы

Отслеживайте последний увиденный seq и оповещайте о пропусках — устойчивые потери обычно означают слишком маленький приёмный буфер (см. ниже) или узкое место на пути.


🔧 Размер буфера

Поток идёт всплесками — загруженный слот может доставить много транзакций подряд. Дайте сокету щедрый приёмный буфер (SO_RCVBUF, несколько мегабайт), чтобы короткие всплески поглощались, а не отбрасывались ядром, и опустошайте сокет из выделенного быстрого цикла.


💻 Минимальный декодер (Python)

python
import socket, struct
sock = 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)) # порт, который вы настроили в дашборде
expected = None
while True:
dgram = sock.recv(2048)
if len(dgram) < 24:
continue
magic, version, msg_type = struct.unpack_from("<HBB", dgram, 0)
if magic != 0x5AE7 or version != 2:
continue # отбрасываем неизвестные magic/version
if msg_type != 2:
continue # не датаграмма DecodedTx
(seq,) = struct.unpack_from("<Q", dgram, 8)
if expected is not None and seq != expected:
print(f"пропуск: потеряно {seq - expected} датаграмм")
expected = seq + 1
(slot,) = struct.unpack_from("<Q", dgram, 16)
tx_bytes = dgram[24:] # стандартный wire-формат — передайте любому парсеру транзакций Solana

🦀 Официальные клиенты и модуль codec

Писать приведённый выше декодер самостоятельно не обязательно: официальные клиенты decoded-shredstream (Rust, Go, JavaScript/TypeScript, Python) привязывают порт, разбирают конверт, обнаруживают пропуски seq и передают вам каждую транзакцию вместе с её слотом и подписями — по UDP и по gRPC, с одним и тем же API.

rust
// cargo add decoded-shredstream tokio --features tokio/macros,tokio/rt-multi-thread
use 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(())
}

Rust-крейт также предоставляет фрейминг в виде модуля codec (константы FRAME_MAGIC = 0x5AE7, FRAME_VERSION = 2, FRAME_HEADER_LEN = 16, MAX_DATAGRAM = 1408, parse_header, StreamDecoder), чтобы вы могли декодировать датаграммы, прочитанные из сокета, которым управляете сами — Go-клиент предоставляет то же самое через ParseHeader / StreamDecoder:

rust
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());
}
}
}

Установка: npm install decoded-shredstream · pip install decoded-shredstream · cargo add decoded-shredstream · go get github.com/shredstream/decoded-shredstream-go.


➡️ Следующие шаги

  • Decoded Shred Stream — позиционирование, задержка и режимы доставки.
  • Доставка по gRPC — те же транзакции по упорядоченному соединению с серверной фильтрацией по аккаунтам.
Приём декодированных транзакций — Docs | ShredStream.com