Skip to main content

solana_entry/
block_component_parser.rs

1use {
2    crate::{
3        block_component::{BlockComponent, ParsedBlockComponent, VersionedBlockMarker},
4        entry::{Entry, EntryView, MAX_DATA_SHREDS_SIZE},
5    },
6    agave_transaction_view::{
7        result::TransactionViewError, transaction_view::UnsanitizedTransactionView,
8    },
9    bytes::Bytes,
10    solana_hash::Hash,
11    solana_transaction::versioned::VersionedTransaction,
12    std::mem::size_of,
13    wincode::{SchemaRead, config::DefaultConfig, io::Reader},
14};
15
16/// Parses a [`ParsedBlockComponent`] from `bytes`, using the same wire format
17/// as [`BlockComponent`].
18pub fn parse<B: Into<Bytes>>(bytes: B) -> Result<ParsedBlockComponent, ParseError> {
19    let bytes: Bytes = bytes.into();
20
21    let mut header: &[u8] = bytes.as_ref();
22    let entry_count =
23        <u64 as SchemaRead<DefaultConfig>>::get(header.by_ref()).map_err(ParseError::EntryCount)?;
24
25    // entry count of zero encodes a block marker
26    let entry_count_encodes_block_marker = entry_count == 0;
27    if entry_count_encodes_block_marker {
28        let remaining = &bytes[BlockComponent::ENTRY_COUNT_SIZE..];
29        let marker = wincode::deserialize::<VersionedBlockMarker>(remaining)
30            .map_err(ParseError::BlockMarker)?;
31        return Ok(ParsedBlockComponent::BlockMarker(marker));
32    }
33
34    let entry_count = usize::try_from(entry_count)
35        .map_err(|_| ParseError::EntryCountOverflow { count: entry_count })?;
36    if entry_count >= BlockComponent::MAX_ENTRIES {
37        return Err(ParseError::TooManyEntries {
38            count: entry_count,
39            max: BlockComponent::MAX_ENTRIES,
40        });
41    }
42
43    // Since the bytes are wincode serialization of [`BlockComponent::EntryBatch`] which
44    // contains regular [`Entry`]s, bound by Entry's size to match wincode's prealloc check
45    // behavior for deserialization.
46    check_prealloc_limit::<Entry>(entry_count).map_err(|needed| {
47        ParseError::EntryCountPreallocLimit {
48            count: entry_count,
49            needed,
50            limit: MAX_DATA_SHREDS_SIZE,
51        }
52    })?;
53
54    // Safe to pre-allocate: entry_count already passed the preallocation-size check above.
55    let mut entry_views = Vec::with_capacity(entry_count);
56    let mut offset = BlockComponent::ENTRY_COUNT_SIZE;
57
58    for entry_index in 0..entry_count {
59        let mut cursor: &[u8] = &bytes[offset..];
60
61        let num_hashes =
62            <u64 as SchemaRead<DefaultConfig>>::get(cursor.by_ref()).map_err(|source| {
63                ParseError::EntryHeader {
64                    entry_index,
65                    field: "num_hashes",
66                    source,
67                }
68            })?;
69        let hash = <Hash as SchemaRead<DefaultConfig>>::get(cursor.by_ref()).map_err(|source| {
70            ParseError::EntryHeader {
71                entry_index,
72                field: "hash",
73                source,
74            }
75        })?;
76        let tx_count =
77            <u64 as SchemaRead<DefaultConfig>>::get(cursor.by_ref()).map_err(|source| {
78                ParseError::EntryHeader {
79                    entry_index,
80                    field: "tx_count",
81                    source,
82                }
83            })?;
84
85        offset = bytes.len() - cursor.len();
86
87        let tx_count =
88            usize::try_from(tx_count).map_err(|_| ParseError::TransactionCountOverflow {
89                entry_index,
90                count: tx_count,
91            })?;
92        // Since the bytes are wincode serialization of [`BlockComponent::EntryBatch`] whose
93        // entries contain regular [`VersionedTransaction`]s, bound by VersionedTransaction's
94        // size to match wincode's prealloc check behavior for deserialization.
95        check_prealloc_limit::<VersionedTransaction>(tx_count).map_err(|needed| {
96            ParseError::TransactionCountPreallocLimit {
97                entry_index,
98                count: tx_count,
99                needed,
100                limit: MAX_DATA_SHREDS_SIZE,
101            }
102        })?;
103
104        // Safe to pre-allocate: tx_count already passed the preallocation-size check above.
105        let mut transactions = Vec::with_capacity(tx_count);
106
107        for tx_index in 0..tx_count {
108            let remaining_bytes = bytes.slice(offset..);
109            let (view, consumed_len) =
110                UnsanitizedTransactionView::try_new_unsanitized_from_prefix(remaining_bytes)
111                    .map_err(|error| ParseError::Transaction {
112                        entry_index,
113                        tx_index,
114                        error,
115                    })?;
116            offset += consumed_len;
117            transactions.push(view);
118        }
119
120        entry_views.push(EntryView {
121            num_hashes,
122            hash,
123            transactions,
124        });
125    }
126
127    // Trailing bytes after the last entry are intentionally ignored, matching
128    // legacy `wincode::deserialize` behavior.
129    Ok(ParsedBlockComponent::EntryBatch(entry_views))
130}
131
132#[derive(Debug, thiserror::Error)]
133pub enum ParseError {
134    #[error("failed to read entry count: {0}")]
135    EntryCount(wincode::ReadError),
136
137    #[error("entry count {count} does not fit in usize")]
138    EntryCountOverflow { count: u64 },
139
140    #[error("entry count {count} exceeds max {max}")]
141    TooManyEntries { count: usize, max: usize },
142
143    #[error("entry count {count} would need {needed} bytes, exceeding preallocation limit {limit}")]
144    EntryCountPreallocLimit {
145        count: usize,
146        needed: usize,
147        limit: usize,
148    },
149
150    #[error("failed to read {field} for entry {entry_index}: {source}")]
151    EntryHeader {
152        entry_index: usize,
153        field: &'static str,
154        source: wincode::ReadError,
155    },
156
157    #[error("transaction count {count} for entry {entry_index} does not fit in usize")]
158    TransactionCountOverflow { entry_index: usize, count: u64 },
159
160    #[error(
161        "transaction count {count} for entry {entry_index} would need {needed} bytes, exceeding \
162         preallocation limit {limit}"
163    )]
164    TransactionCountPreallocLimit {
165        entry_index: usize,
166        count: usize,
167        needed: usize,
168        limit: usize,
169    },
170
171    #[error("failed to parse transaction {tx_index} of entry {entry_index}: {error:?}")]
172    Transaction {
173        entry_index: usize,
174        tx_index: usize,
175        error: TransactionViewError,
176    },
177
178    #[error("failed to deserialize block marker: {0}")]
179    BlockMarker(wincode::ReadError),
180}
181
182/// Checks that pre-allocating `count` elements of `T` would stay within
183/// [`MAX_DATA_SHREDS_SIZE`] bytes (`count * size_of::<T>()`), returning the bytes needed as
184/// the error otherwise.
185fn check_prealloc_limit<T>(count: usize) -> Result<(), usize> {
186    // max(1) to count zero sized types
187    let size_of_t = size_of::<T>().max(1);
188    let needed = count.saturating_mul(size_of_t);
189
190    if needed > MAX_DATA_SHREDS_SIZE {
191        Err(needed)
192    } else {
193        Ok(())
194    }
195}
196
197#[cfg(test)]
198mod tests {
199    use {
200        super::*,
201        crate::{
202            block_component::{
203                BlockFooterV1, BlockHeaderV1, GenesisCertBlockMarker, UpdateParentV1,
204            },
205            entry::Entry,
206        },
207        solana_bls_signatures::Keypair as BlsKeypair,
208        solana_keypair::Keypair,
209        solana_pubkey::Pubkey,
210        solana_transaction::versioned::VersionedTransaction,
211        std::{assert_matches, iter::repeat_n},
212        test_case::test_case,
213    };
214
215    fn tick_entries(n: usize) -> Vec<Entry> {
216        repeat_n(Entry::default(), n).collect()
217    }
218
219    fn entry_with_transactions(num_hashes: u64, transactions: Vec<VersionedTransaction>) -> Entry {
220        Entry {
221            num_hashes,
222            hash: Hash::new_unique(),
223            transactions,
224        }
225    }
226
227    fn transfer_transaction(amount: u64) -> VersionedTransaction {
228        solana_system_transaction::transfer(
229            &Keypair::new(),
230            &Pubkey::new_unique(),
231            amount,
232            Hash::default(),
233        )
234        .into()
235    }
236
237    fn sample_footer() -> BlockFooterV1 {
238        BlockFooterV1 {
239            bank_hash: Hash::new_unique(),
240            block_producer_time_nanos: 1234567890,
241            block_user_agent: b"test-agent".to_vec(),
242            block_final_cert: None,
243            skip_reward_cert: None,
244            notar_reward_cert: None,
245        }
246    }
247
248    /// Serializes `entries` as a `BlockComponent` entry batch and parses it back.
249    fn parse_entry_batch(entries: Vec<Entry>) -> Vec<EntryView<Bytes>> {
250        let component = BlockComponent::new_entry_batch(entries).unwrap();
251        let bytes = wincode::serialize(&component).unwrap();
252        let ParsedBlockComponent::EntryBatch(views) = parse(bytes).unwrap() else {
253            panic!("expected EntryBatch");
254        };
255        views
256    }
257
258    #[test]
259    fn parsed_tick_entry_view_has_same_num_hashes_and_hash_as_source_entry() {
260        let source_tick_entry = Entry {
261            num_hashes: 7,
262            hash: Hash::new_unique(),
263            transactions: vec![],
264        };
265
266        let parsed_entry_views = parse_entry_batch(vec![source_tick_entry.clone()]);
267
268        assert_eq!(parsed_entry_views.len(), 1);
269        assert_eq!(
270            parsed_entry_views[0].num_hashes,
271            source_tick_entry.num_hashes
272        );
273        assert_eq!(parsed_entry_views[0].hash, source_tick_entry.hash);
274        assert!(parsed_entry_views[0].transactions.is_empty());
275    }
276
277    #[test]
278    fn parsed_entry_views_preserve_source_entry_order_across_multiple_entries() {
279        let source_entries_with_distinct_num_hashes: Vec<Entry> = (0..5)
280            .map(|num_hashes| Entry {
281                num_hashes,
282                hash: Hash::new_unique(),
283                transactions: vec![],
284            })
285            .collect();
286
287        let parsed_entry_views = parse_entry_batch(source_entries_with_distinct_num_hashes.clone());
288
289        assert_eq!(
290            parsed_entry_views.len(),
291            source_entries_with_distinct_num_hashes.len()
292        );
293        for (parsed_entry_view, source_entry) in parsed_entry_views
294            .iter()
295            .zip(source_entries_with_distinct_num_hashes.iter())
296        {
297            assert_eq!(parsed_entry_view.num_hashes, source_entry.num_hashes);
298            assert_eq!(parsed_entry_view.hash, source_entry.hash);
299        }
300    }
301
302    #[test]
303    fn parsed_transaction_view_exposes_same_signature_as_source_transaction() {
304        let source_transaction = transfer_transaction(1);
305        let source_entry = entry_with_transactions(1, vec![source_transaction.clone()]);
306
307        let parsed_entry_views = parse_entry_batch(vec![source_entry]);
308
309        assert_eq!(parsed_entry_views.len(), 1);
310        assert_eq!(parsed_entry_views[0].transactions.len(), 1);
311        assert_eq!(
312            parsed_entry_views[0].transactions[0].signatures(),
313            source_transaction.signatures.as_slice()
314        );
315    }
316
317    #[test]
318    fn parsed_transaction_views_preserve_source_transaction_order_within_entry() {
319        let first_source_transaction = transfer_transaction(1);
320        let second_source_transaction = transfer_transaction(2);
321        let source_entry = entry_with_transactions(
322            1,
323            vec![
324                first_source_transaction.clone(),
325                second_source_transaction.clone(),
326            ],
327        );
328
329        let parsed_entry_views = parse_entry_batch(vec![source_entry]);
330
331        let parsed_transaction_views = &parsed_entry_views[0].transactions;
332        assert_eq!(parsed_transaction_views.len(), 2);
333        assert_eq!(
334            parsed_transaction_views[0].signatures(),
335            first_source_transaction.signatures.as_slice()
336        );
337        assert_eq!(
338            parsed_transaction_views[1].signatures(),
339            second_source_transaction.signatures.as_slice()
340        );
341    }
342
343    #[test_case(VersionedBlockMarker::from_block_footer(sample_footer()); "block_footer")]
344    #[test_case(
345        VersionedBlockMarker::from_block_header(BlockHeaderV1 {
346            parent_slot: 42,
347            parent_block_id: Hash::new_unique(),
348        });
349        "block_header"
350    )]
351    #[test_case(
352        VersionedBlockMarker::from_update_parent(UpdateParentV1 {
353            new_parent_slot: 43,
354            new_parent_block_id: Hash::new_unique(),
355        });
356        "update_parent"
357    )]
358    #[test_case(
359        VersionedBlockMarker::from_genesis_cert_block_marker(GenesisCertBlockMarker {
360            slot: 44,
361            block_id: Hash::new_unique(),
362            bls_signature: BlsKeypair::new().sign(b"genesis").into(),
363            bitmap: vec![1, 2, 3],
364        });
365        "genesis_certificate"
366    )]
367    fn parsed_block_marker_view_matches_source_marker_for_each_variant(
368        source_marker: VersionedBlockMarker,
369    ) {
370        let component = BlockComponent::new_block_marker(source_marker.clone());
371        let bytes = wincode::serialize(&component).unwrap();
372
373        let view = parse(bytes).unwrap();
374
375        let ParsedBlockComponent::BlockMarker(parsed_marker) = view else {
376            panic!("expected BlockMarker");
377        };
378        assert_eq!(parsed_marker, source_marker);
379    }
380
381    #[test]
382    fn parse_rejects_all_zero_empty_entry_batch_payload() {
383        let empty_entry_batch_payload = Bytes::from(BlockComponent::EMPTY_ENTRY_BATCH.to_vec());
384
385        let parse_result = parse(empty_entry_batch_payload);
386
387        assert!(parse_result.is_err());
388    }
389
390    #[test]
391    fn parse_rejects_entry_count_header_claiming_max_entries_with_no_further_bytes() {
392        let mut header_claiming_max_entries =
393            (BlockComponent::MAX_ENTRIES as u64).to_le_bytes().to_vec();
394        header_claiming_max_entries.resize(BlockComponent::ENTRY_COUNT_SIZE, 0);
395
396        let parse_result = parse(header_claiming_max_entries);
397
398        assert_matches!(parse_result, Err(ParseError::TooManyEntries { .. }));
399    }
400
401    #[test]
402    fn parse_ignores_extra_trailing_byte_appended_after_valid_entry_batch() {
403        let source_entries = tick_entries(1);
404        let component = BlockComponent::new_entry_batch(source_entries.clone()).unwrap();
405        let mut entry_batch_bytes_with_trailing_byte = wincode::serialize(&component).unwrap();
406        entry_batch_bytes_with_trailing_byte.push(0);
407
408        let parse_result = parse(entry_batch_bytes_with_trailing_byte);
409
410        // trailing bytes are ignored matching legacy `wincode::deserialize`
411        let Ok(ParsedBlockComponent::EntryBatch(views)) = parse_result else {
412            panic!("expected EntryBatch");
413        };
414        assert_eq!(views.len(), source_entries.len());
415    }
416
417    #[test]
418    fn parse_ignores_extra_trailing_byte_appended_after_valid_block_marker() {
419        let marker = VersionedBlockMarker::from_block_footer(sample_footer());
420        let component = BlockComponent::new_block_marker(marker.clone());
421        let mut block_marker_bytes_with_trailing_byte = wincode::serialize(&component).unwrap();
422        block_marker_bytes_with_trailing_byte.push(0);
423
424        let parse_result = parse(block_marker_bytes_with_trailing_byte);
425
426        let Ok(ParsedBlockComponent::BlockMarker(parsed_marker)) = parse_result else {
427            panic!("expected BlockMarker");
428        };
429        assert_eq!(parsed_marker, marker);
430    }
431
432    #[test]
433    fn parse_rejects_entry_count_over_prealloc_limit() {
434        // One past the largest entry_count that fits the preallocation-size guard, but
435        // still well under MAX_ENTRIES, so only the prealloc check can reject it.
436        let entry_count = MAX_DATA_SHREDS_SIZE / size_of::<Entry>() + 1;
437        assert!(entry_count < BlockComponent::MAX_ENTRIES);
438
439        // Just the entry_count header; no entry data needs to follow it.
440        let header = (entry_count as u64).to_le_bytes().to_vec();
441
442        let owned_result = wincode::deserialize::<BlockComponent>(&header);
443        let view_result = parse(header);
444
445        assert_matches!(
446            owned_result,
447            Err(wincode::ReadError::PreallocationSizeLimit { .. })
448        );
449        assert_matches!(view_result, Err(ParseError::EntryCountPreallocLimit { .. }));
450    }
451
452    #[test]
453    fn parse_accepts_entry_count_at_prealloc_limit() {
454        // The largest entry_count that still fits the preallocation-size guard. No
455        // entry data follows it, so both decoders still error out - just not because
456        // of this guard, which is the only thing this test checks.
457        let entry_count = MAX_DATA_SHREDS_SIZE / size_of::<Entry>();
458        let header = (entry_count as u64).to_le_bytes().to_vec();
459
460        let owned_result = wincode::deserialize::<BlockComponent>(&header);
461        let view_result = parse(header);
462
463        assert!(!matches!(
464            owned_result,
465            Err(wincode::ReadError::PreallocationSizeLimit { .. })
466        ));
467        assert!(!matches!(
468            view_result,
469            Err(ParseError::EntryCountPreallocLimit { .. })
470        ));
471    }
472
473    #[test]
474    fn parse_rejects_transaction_count_over_prealloc_limit() {
475        // A real single-entry, zero-transaction batch, with its tx_count field (the
476        // last 8 bytes, since no transactions follow it) patched to one past the
477        // largest tx_count that fits the preallocation-size guard.
478        let component = BlockComponent::new_entry_batch(vec![Entry::default()]).unwrap();
479        let mut bytes = wincode::serialize(&component).unwrap();
480
481        let tx_count = MAX_DATA_SHREDS_SIZE / size_of::<VersionedTransaction>() + 1;
482        let tx_count_offset = bytes.len() - size_of::<u64>();
483        bytes[tx_count_offset..].copy_from_slice(&(tx_count as u64).to_le_bytes());
484
485        let owned_result = wincode::deserialize::<BlockComponent>(&bytes);
486        let view_result = parse(bytes);
487
488        assert_matches!(
489            owned_result,
490            Err(wincode::ReadError::PreallocationSizeLimit { .. })
491        );
492        assert_matches!(
493            view_result,
494            Err(ParseError::TransactionCountPreallocLimit { .. })
495        );
496    }
497
498    #[test]
499    fn parse_accepts_transaction_count_at_prealloc_limit() {
500        // The largest tx_count that still fits the preallocation-size guard, patched
501        // into a real zero-transaction entry. No transaction data follows it, so both
502        // decoders still error out - just not because of this guard.
503        let component = BlockComponent::new_entry_batch(vec![Entry::default()]).unwrap();
504        let mut bytes = wincode::serialize(&component).unwrap();
505
506        let tx_count = MAX_DATA_SHREDS_SIZE / size_of::<VersionedTransaction>();
507        let tx_count_offset = bytes.len() - size_of::<u64>();
508        bytes[tx_count_offset..].copy_from_slice(&(tx_count as u64).to_le_bytes());
509
510        let owned_result = wincode::deserialize::<BlockComponent>(&bytes);
511        let view_result = parse(bytes);
512
513        assert!(!matches!(
514            owned_result,
515            Err(wincode::ReadError::PreallocationSizeLimit { .. })
516        ));
517        assert!(!matches!(
518            view_result,
519            Err(ParseError::TransactionCountPreallocLimit { .. })
520        ));
521    }
522
523    const ENTRY_PREALLOC_LIMIT: usize = MAX_DATA_SHREDS_SIZE / size_of::<Entry>();
524
525    #[test_case(0; "zero_count")]
526    #[test_case(1; "one_count")]
527    #[test_case(ENTRY_PREALLOC_LIMIT; "at_limit")]
528    fn check_prealloc_limit_for_entry_accepts_within_limit(count: usize) {
529        assert_matches!(check_prealloc_limit::<Entry>(count), Ok(()));
530    }
531
532    #[test_case(ENTRY_PREALLOC_LIMIT + 1; "over_limit")]
533    #[test_case(usize::MAX; "count_overflows_and_saturates")]
534    fn check_prealloc_limit_for_entry_rejects_count_over_limit(count: usize) {
535        assert_eq!(
536            check_prealloc_limit::<Entry>(count),
537            Err(count.saturating_mul(size_of::<Entry>()))
538        );
539    }
540
541    #[test]
542    fn check_prealloc_limit_treats_zero_sized_types_as_one_byte_each() {
543        struct ZeroSized;
544        assert_eq!(size_of::<ZeroSized>(), 0);
545
546        let count = MAX_DATA_SHREDS_SIZE + 1;
547
548        assert_eq!(check_prealloc_limit::<ZeroSized>(count), Err(count));
549    }
550}