|
16 | 16 | shredder::{self, ReedSolomonCache},
|
17 | 17 | },
|
18 | 18 | assert_matches::debug_assert_matches,
|
19 |
| - itertools::Itertools, |
| 19 | + itertools::{Either, Itertools}, |
20 | 20 | rayon::{prelude::*, ThreadPool},
|
21 | 21 | reed_solomon_erasure::Error::{InvalidIndex, TooFewParityShards, TooFewShards},
|
22 | 22 | solana_perf::packet::deserialize_from_with_limit,
|
@@ -875,21 +875,30 @@ pub(super) fn make_shreds_from_data(
|
875 | 875 | }
|
876 | 876 | data = rest;
|
877 | 877 | }
|
878 |
| - if !data.is_empty() { |
| 878 | + // If shreds.is_empty() then the data argument was empty. In that case we |
| 879 | + // want to generate one data shred with empty data. |
| 880 | + if !data.is_empty() || shreds.is_empty() { |
879 | 881 | // Find the Merkle proof_size and data_buffer_size
|
880 | 882 | // which can embed the remaining data.
|
881 | 883 | let (proof_size, data_buffer_size) = (1u8..32)
|
882 | 884 | .find_map(|proof_size| {
|
883 | 885 | let data_buffer_size = ShredData::capacity(proof_size).ok()?;
|
884 | 886 | let num_data_shreds = (data.len() + data_buffer_size - 1) / data_buffer_size;
|
| 887 | + let num_data_shreds = num_data_shreds.max(1); |
885 | 888 | let erasure_batch_size = shredder::get_erasure_batch_size(num_data_shreds);
|
886 | 889 | (proof_size == get_proof_size(erasure_batch_size))
|
887 | 890 | .then_some((proof_size, data_buffer_size))
|
888 | 891 | })
|
889 | 892 | .ok_or(Error::UnknownProofSize)?;
|
890 | 893 | common_header.shred_variant = ShredVariant::MerkleData(proof_size);
|
891 | 894 | common_header.fec_set_index = common_header.index;
|
892 |
| - for shred in data.chunks(data_buffer_size) { |
| 895 | + let chunks = if data.is_empty() { |
| 896 | + // Generate one data shred with empty data. |
| 897 | + Either::Left(std::iter::once(data)) |
| 898 | + } else { |
| 899 | + Either::Right(data.chunks(data_buffer_size)) |
| 900 | + }; |
| 901 | + for shred in chunks { |
893 | 902 | let shred = new_shred_data(common_header, data_header, shred);
|
894 | 903 | shreds.push(shred);
|
895 | 904 | common_header.index += 1;
|
@@ -1274,12 +1283,8 @@ mod test {
|
1274 | 1283 | assert_eq!(shreds.iter().map(Shred::signature).dedup().count(), 1);
|
1275 | 1284 | for size in num_data_shreds..num_shreds {
|
1276 | 1285 | let mut shreds = shreds.clone();
|
1277 |
| - let mut removed_shreds = Vec::new(); |
1278 |
| - while shreds.len() > size { |
1279 |
| - let index = rng.gen_range(0, shreds.len()); |
1280 |
| - removed_shreds.push(shreds.swap_remove(index)); |
1281 |
| - } |
1282 | 1286 | shreds.shuffle(rng);
|
| 1287 | + let mut removed_shreds = shreds.split_off(size); |
1283 | 1288 | // Should at least contain one coding shred.
|
1284 | 1289 | if shreds.iter().all(|shred| {
|
1285 | 1290 | matches!(
|
@@ -1337,9 +1342,9 @@ mod test {
|
1337 | 1342 | #[test_case(46800)]
|
1338 | 1343 | fn test_make_shreds_from_data(data_size: usize) {
|
1339 | 1344 | let mut rng = rand::thread_rng();
|
1340 |
| - let data_size = data_size.saturating_sub(16).max(1); |
| 1345 | + let data_size = data_size.saturating_sub(16); |
1341 | 1346 | let reed_solomon_cache = ReedSolomonCache::default();
|
1342 |
| - for data_size in (data_size..data_size + 32).step_by(3) { |
| 1347 | + for data_size in data_size..data_size + 32 { |
1343 | 1348 | run_make_shreds_from_data(&mut rng, data_size, &reed_solomon_cache);
|
1344 | 1349 | }
|
1345 | 1350 | }
|
@@ -1392,7 +1397,7 @@ mod test {
|
1392 | 1397 | Shred::ShredData(shred) => Some(shred),
|
1393 | 1398 | })
|
1394 | 1399 | .collect();
|
1395 |
| - // Assert that the input data can be recovered from data sherds. |
| 1400 | + // Assert that the input data can be recovered from data shreds. |
1396 | 1401 | assert_eq!(
|
1397 | 1402 | data,
|
1398 | 1403 | data_shreds
|
|
0 commit comments