Solana shred anatomy reference
Identify every logical region needed to parse, authenticate, recover, and reassemble a shred.
Before you start
- A captured UDP datagram no larger than 1,228 bytes
- A pinned Agave release
Read a shred as four regions
A raw datagram contains one shred. Its four logical regions are the common header, the type-specific header, erasure-coded material, and Merkle material determined by the variant. Serialized ledger bytes belong to data shreds. Reed-Solomon parity over that data material belongs to coding shreds. Both types participate in one FEC set.
Datagram length never exceeds 1,228 bytes. That limit does not reveal useful data capacity. Merkle proof size and retransmitter-signature presence change the space available to a data shred. Ask the parser for data() or capacity for the parsed variant.
Common header
The current Agave wire implementation serializes an 83-byte common header. These byte ranges are verified against its public wire readers and serialization tests:
Treat the signature as opaque until the variant parser defines the signed message. In current Merkle forms, the reconstructed Merkle root is what the leader signs, not an arbitrary fixed slice selected by application code.
The two shred types use separate index spaces. A shred's identity is (slot, index, shred_type). Without type, equal numeric indexes from one data and one coding shred would collide.
Data header
The current data header follows the common header and ends at byte 88:
The low six flag bits hold a saturated reference-tick value. 0x40 is DATA_COMPLETE_SHRED. 0xC0 represents LAST_SHRED_IN_SLOT and also implies data complete. Use the bitflag implementation from the pinned release rather than accepting arbitrary bits.
The size field is not the UDP datagram length. It identifies how much of the data shred contains headers plus ledger data. Padding and Merkle material can follow. A typed parser validates the declared size against the parsed variant and returns only the ledger data slice.
Coding header
The current coding header ends at byte 89:
A coding shred's erasure-shard position is k + position. Its numeric shred index is not that local array position. Coding shreds in one set must agree on k, m, the first coding index derived as index - position, version, slot, FEC-set index, proof form, root, and leader signature.
Current Agave usually targets 32 data shreds plus 32 coding shreds in each FEC block. A final block may be shorter. Derive k and m from its sanitized coding header instead of allocating every set under a permanent 32 by 32 assumption.
Merkle regions
The variant byte's low four bits encode proof entry count. High bits select data or coding and whether a retransmitter signature is present. Layout after the erasure-coded region depends on those values. Current Agave computes capacity and offsets from the parsed variant, hashes a leaf with the protocol prefix, walks the proof using the erasure-shard index, and obtains the leader-signed root.
Do not copy a proof offset from a packet capture. The exact proof start depends on whether the shred is data or coding, proof entry count, payload size, and retransmitter-signature variant. The stable mechanism is: parse variant, sanitize the typed shred, and ask it for its Merkle root or proof-related accessors. Obtain the scheduled leader assigned to that slot, then use the corresponding key for verification.
Chained Merkle data commits an FEC set to the preceding set's root. Retransmitter-signed variants carry an additional signature at the end while leaving the leader signature intact. The service forwards raw shreds without substituting its own signature for the leader's.
Parse with checked slices
This small parser is suitable for header inspection. It is not a full validator:
rust
fn le_u32(bytes: &[u8], range: std::ops::Range<usize>) -> Option<u32> { Some(u32::from_le_bytes(bytes.get(range)?.try_into().ok()?))}fn le_u64(bytes: &[u8], range: std::ops::Range<usize>) -> Option<u64> { Some(u64::from_le_bytes(bytes.get(range)?.try_into().ok()?))} fn common(bytes: &[u8]) -> Option<(u8, u64, u32, u32)> { let variant = *bytes.get(64)?; let slot = le_u64(bytes, 65..73)?; let index = le_u32(bytes, 73..77)?; let fec = le_u32(bytes, 79..83)?; Some((variant, slot, index, fec))} fn main() { if common(&[0u8; 82]).is_some() { std::process::exit(1); }}Use these ranges only in tests, capture tools, and the minimal receiver tied to the documented format. Production code should construct solana_ledger::shred::Shred from serialized bytes and call sanitize. Keep raw bytes because recovery and duplicate-conflict evidence may need the canonical packet.
Record the parser revision beside every stored fixture. A byte map without its release identity becomes misleading after a format transition.
Parameters
When it goes wrong
Invalid payload size: N
Cause. The packet is too short or does not match the capacity required by its variant.
Fix. Reject it and retain a sampled packet plus the pinned Agave revision for diagnosis.
Invalid shred variant
Cause. The variant byte is unknown to the parser or a legacy form is disabled in that release.
Fix. Use the Agave revision matching the network; do not add a guessed variant mask.
Invalid data size: size=N, payload=M
Cause. The data header's declared size is outside the legal region for that variant.
Fix. Reject the shred before exposing data bytes.
Questions
- Is a shred always 1,228 bytes?
- No. A service datagram may reach 1,228 bytes, while smaller lengths also occur. Parsed shred size and useful data capacity depend on the variant and Merkle material. Allocate for the maximum, use the received datagram length, and let the matching parser validate it.
- Can the data size field be used as the UDP length?
- No. The data size marks the end of common header, data header, and ledger data inside a data shred. Padding, chained-root data, Merkle proof entries, or a retransmitter signature can occupy other regions. The receive length and declared data size serve different purposes.
- Why is shred type part of the identity?
- Data indexes advance independently of coding indexes. Both types can share a slot and numeric index without representing the same packet. Deduplicate with an identity key ordered as shred type, slot, then index. FEC grouping instead combines FEC-set index with its slot.