Reconstruct Solana entries
Convert authenticated ordered data shreds into typed entries at each completion boundary.
Before you start
- A consecutive data-shred range
- The final shred has DATA_COMPLETE_SHRED
- A pinned Agave codec
Deshred before deserializing
Take a consecutive ordered range of complete data-shred payloads ending at DATA_COMPLETE_SHRED. Pass the serialized full shreds to Shredder::deshred. It validates consecutive indices, rejects trailing shreds after a completion flag, extracts each variant-correct ledger-data slice, concatenates them, and requires the last member to be data complete.
Do not concatenate a fixed tail offset. Padding and Merkle material are not ledger bytes. Do not decode one shred at a time because serialized entries can cross packet and FEC boundaries.
rust
use solana_entry::{block_component::BlockComponent, entry::Entry};
use solana_ledger::shred::{Shred, Shredder};
fn decode_range(shreds: &[Shred]) -> Result<Vec<Entry>, String> {
if shreds.is_empty() { return Err("empty data range".into()); }
if shreds.iter().any(|s| s.is_data() == false) { return Err("coding shred in data range".into()); }
let payloads = shreds.iter().map(Shred::payload);
let bytes = Shredder::deshred(payloads).map_err(|e| e.to_string())?;
let component: BlockComponent = wincode::deserialize(&bytes)
.map_err(|e| e.to_string())?;
match component {
BlockComponent::EntryBatch(entries) => Ok(entries),
BlockComponent::BlockMarker(_) => Ok(Vec::new()),
}
}This example follows current Agave source that serializes BlockComponent with wincode. Entry batches contain Vec<Entry>; block markers carry protocol control data rather than transactions.
Pin the release codec
The Agave release, entry codec, and block-component activation point are not currently specified by the product facts. Recent Agave source uses wincode. Older Solana and Agave paths commonly deserialize a Vec<Entry> with bincode. The nonempty entry vector encoding is related, but do not rely on accidental compatibility.
Make the codec an implementation selected by trusted deployment configuration. Do not try codecs in an open-ended loop until one accepts attacker-controlled bytes. Pin the cluster release, cap input size and element counts, and replay captured fixtures on upgrade.
For a release whose completed range is directly a vector, the adapter is conceptually:
rust
use solana_entry::entry::Entry;
fn decode_legacy(bytes: &[u8]) -> Result<Vec<Entry>, bincode::Error> {
bincode::deserialize(bytes)
}Build either the current component decoder or the reviewed historical decoder into one binary only when transition support is required. Select it from a trusted feature or activation schedule, not from an error message.
Understand an entry
An Entry contains num_hashes, a 32-byte Proof of History hash, and an ordered vector of VersionedTransaction. An empty transaction vector is a tick entry. It is valid and advances the hash chain. Do not report an empty entry as a decode failure.
Entry order and transaction order inside each entry are meaningful. Preserve them with slot, data-range start and end indices, and receive timing. If downstream processing becomes parallel, attach ordinal positions before dispatch.
Entry deserialization alone does not verify Proof of History, transaction signatures, account state, instruction execution, or fork acceptance. The leader-authenticated shreds prove proposal provenance. Full ledger replay performs additional checks.
Bound deserialization
Cap the number of shreds and total deshredded bytes before allocation. The protocol release has maximum data shreds per slot; use its constant. Reject a completed range that exceeds the configured limit. Wincode schemas in current Agave include bounds for entry and transaction containers, but the receiver still needs outer queue and memory limits.
Do deserialization outside the receive and FEC ownership threads. A malformed range can be CPU-expensive. Count codec errors and sample only a digest plus range metadata.
Handle incomplete and extra bytes
Shredder::deshred requires a completion flag at the end. A missing data member should be recovered or cause range expiry before decode. Never pad the byte stream or trim until a decoder accepts it.
Use exact deserialization when the selected codec provides it, so trailing bytes are rejected. Current protocol helpers may intentionally pad an empty data result for backward compatibility, so follow the pinned implementation's interpretation for that release.
Test reconstruction
Create entries through the same Agave release, shred them, reorder members, remove recoverable members, restore them, order data, deshred, and compare decoded entries. Include transactions that cross shred boundaries, several entries in one shred, empty ticks, multiple completion ranges, and a range crossing FEC sets.
Corrupt declared data size, remove the final completion flag, insert a coding shred, skip an index, append a data shred after completion, truncate serialized bytes, and select the wrong codec. Require a specific failure without panic or unbounded allocation.
Export deshred range count, shreds per range, bytes per range, deshred duration, component type, entries, ticks, transactions, decode duration, and failure reason. Keep slots and indexes in sampled logs rather than labels.
Preserve boundaries downstream
Attach the slot, first and last data index, component ordinal, and earliest receive time to decoded output. Those fields let transaction consumers compare latency and let operators trace a decode failure back to one authenticated range. Do not merge adjacent decoded entry batches before assigning their source context.
If a release introduces another component type, handle it as a versioned protocol event. Do not cast unknown control data to an empty entry vector. Update the pinned enum, add fixtures for the new type, and decide whether the transaction path ignores, records, or acts on it.
Free the deshredded byte buffer after typed entries have moved to the next bounded stage. Retaining packet bytes, the concatenated range, and decoded objects at once multiplies memory during bursts. Profile ownership and copies with maximum-size valid ranges.
Parameters
When it goes wrong
deshred: Too few data shards present
Cause. Indices are not consecutive or the final member lacks a completion flag.
Fix. Return to ordering and FEC recovery; do not pad or skip the missing range.
entry component decode: unexpected end of input
Cause. The byte range is truncated, used the wrong boundary, or selected the wrong codec.
Fix. Verify consecutive flags and the pinned network release before retrying.
coding shred in data range
Cause. The parity path was merged into ordered ledger data.
Fix. Keep coding members inside FEC recovery and deshred data members only.
Questions
- Can one data shred be deserialized as entries?
- Not reliably. An entry or transaction can span multiple data shreds, and one shred can contain parts of several entries. Collect consecutive data indices through an authenticated data-complete boundary, use the variant-aware deshredder, then deserialize the resulting complete byte stream with the pinned release codec.
- Are empty entries decode errors?
- No. An entry with no transactions is a tick and advances the Proof of History chain. Current block-component formats can also carry marker components rather than entry batches. Count these cases explicitly instead of requiring every completed range to produce at least one transaction.
- Should the decoder try bincode after wincode fails?
- Do not use failure-driven codec guessing on live untrusted input. Select the codec from a trusted pinned network release or activation schedule. Test transition support with captured fixtures and bounded inputs. A fallback can turn malformed bytes into a different accepted structure.