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
16pub 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 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 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 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 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 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 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
182fn check_prealloc_limit<T>(count: usize) -> Result<(), usize> {
186 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 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 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 let entry_count = MAX_DATA_SHREDS_SIZE / size_of::<Entry>() + 1;
437 assert!(entry_count < BlockComponent::MAX_ENTRIES);
438
439 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 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 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 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}