Skip to content

Commit 4f947a0

Browse files
authored
(de-)serializes shred headers through a Cursor (solana-labs#24876)
Current serialize/de-serialize code for shreds manually tracks offsets into the payload; This can instead be done with std::io::Cursor. https://github.com/solana-labs/solana/blob/e812430e2/ledger/src/shred.rs#L232-L258
1 parent 74b586a commit 4f947a0

File tree

1 file changed

+76
-101
lines changed

1 file changed

+76
-101
lines changed

ledger/src/shred.rs

+76-101
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,6 @@ pub use crate::{
5555
};
5656
use {
5757
crate::blockstore::MAX_DATA_SHREDS_PER_SLOT,
58-
bincode::config::Options,
5958
bitflags::bitflags,
6059
num_enum::{IntoPrimitive, TryFromPrimitive},
6160
serde::{Deserialize, Serialize},
@@ -68,7 +67,7 @@ use {
6867
pubkey::Pubkey,
6968
signature::{Keypair, Signature, Signer},
7069
},
71-
std::{fmt::Debug, mem::size_of, ops::RangeInclusive},
70+
std::{fmt::Debug, io::Cursor, mem::size_of, ops::RangeInclusive},
7271
thiserror::Error,
7372
};
7473

@@ -229,34 +228,6 @@ impl ErasureSetId {
229228
}
230229

231230
impl Shred {
232-
fn deserialize_obj<'de, T>(index: &mut usize, size: usize, buf: &'de [u8]) -> bincode::Result<T>
233-
where
234-
T: Deserialize<'de>,
235-
{
236-
let end = std::cmp::min(*index + size, buf.len());
237-
let ret = bincode::options()
238-
.with_limit(PACKET_DATA_SIZE as u64)
239-
.with_fixint_encoding()
240-
.allow_trailing_bytes()
241-
.deserialize(&buf[*index..end])?;
242-
*index += size;
243-
Ok(ret)
244-
}
245-
246-
fn serialize_obj_into<'de, T>(
247-
index: &mut usize,
248-
size: usize,
249-
buf: &'de mut [u8],
250-
obj: &T,
251-
) -> bincode::Result<()>
252-
where
253-
T: Serialize,
254-
{
255-
bincode::serialize_into(&mut buf[*index..*index + size], obj)?;
256-
*index += size;
257-
Ok(())
258-
}
259-
260231
pub fn copy_to_packet(&self, packet: &mut Packet) {
261232
let len = self.payload.len();
262233
packet.data[..len].copy_from_slice(&self.payload[..]);
@@ -306,24 +277,13 @@ impl Shred {
306277
data_header.flags |= ShredFlags::LAST_SHRED_IN_SLOT
307278
}
308279

309-
let mut start = 0;
310-
Self::serialize_obj_into(
311-
&mut start,
312-
SIZE_OF_COMMON_SHRED_HEADER,
313-
&mut payload,
314-
&common_header,
315-
)
316-
.expect("Failed to write common header into shred buffer");
317-
318-
Self::serialize_obj_into(
319-
&mut start,
320-
SIZE_OF_DATA_SHRED_HEADER,
321-
&mut payload,
322-
&data_header,
323-
)
324-
.expect("Failed to write data header into shred buffer");
280+
let mut cursor = Cursor::new(&mut payload[..]);
281+
bincode::serialize_into(&mut cursor, &common_header).unwrap();
282+
bincode::serialize_into(&mut cursor, &data_header).unwrap();
325283
// TODO: Need to check if data is too large!
326-
payload[start..start + data.len()].copy_from_slice(data);
284+
let offset = cursor.position() as usize;
285+
debug_assert_eq!(offset, SHRED_DATA_OFFSET);
286+
payload[offset..offset + data.len()].copy_from_slice(data);
327287
Self {
328288
common_header,
329289
data_header,
@@ -333,23 +293,19 @@ impl Shred {
333293
}
334294

335295
pub fn new_from_serialized_shred(mut payload: Vec<u8>) -> Result<Self, Error> {
336-
let mut start = 0;
337-
let common_header: ShredCommonHeader =
338-
Self::deserialize_obj(&mut start, SIZE_OF_COMMON_SHRED_HEADER, &payload)?;
339-
340-
// Shreds should be padded out to SHRED_PAYLOAD_SIZE
341-
// so that erasure generation/recovery works correctly
342-
// But only the data_header.size is stored in blockstore.
343-
payload.resize(SHRED_PAYLOAD_SIZE, 0);
296+
let mut cursor = Cursor::new(&payload[..]);
297+
let common_header: ShredCommonHeader = bincode::deserialize_from(&mut cursor)?;
344298
let (data_header, coding_header) = match common_header.shred_type {
345299
ShredType::Code => {
346-
let coding_header: CodingShredHeader =
347-
Self::deserialize_obj(&mut start, SIZE_OF_CODING_SHRED_HEADER, &payload)?;
300+
let coding_header = bincode::deserialize_from(&mut cursor)?;
301+
// see: https://github.com/solana-labs/solana/pull/10109
302+
payload.truncate(SHRED_PAYLOAD_SIZE);
348303
(DataShredHeader::default(), coding_header)
349304
}
350305
ShredType::Data => {
351-
let data_header: DataShredHeader =
352-
Self::deserialize_obj(&mut start, SIZE_OF_DATA_SHRED_HEADER, &payload)?;
306+
let data_header = bincode::deserialize_from(&mut cursor)?;
307+
// see: https://github.com/solana-labs/solana/pull/16602
308+
payload.resize(SHRED_PAYLOAD_SIZE, 0u8);
353309
(data_header, CodingShredHeader::default())
354310
}
355311
};
@@ -386,24 +342,14 @@ impl Shred {
386342
position,
387343
};
388344
let mut payload = vec![0; SHRED_PAYLOAD_SIZE];
389-
let mut start = 0;
390-
Self::serialize_obj_into(
391-
&mut start,
392-
SIZE_OF_COMMON_SHRED_HEADER,
393-
&mut payload,
394-
&common_header,
395-
)
396-
.expect("Failed to write header into shred buffer");
397-
Self::serialize_obj_into(
398-
&mut start,
399-
SIZE_OF_CODING_SHRED_HEADER,
400-
&mut payload,
401-
&coding_header,
402-
)
403-
.expect("Failed to write coding header into shred buffer");
345+
let mut cursor = Cursor::new(&mut payload[..]);
346+
bincode::serialize_into(&mut cursor, &common_header).unwrap();
347+
bincode::serialize_into(&mut cursor, &coding_header).unwrap();
404348
// Tests may have an empty parity_shard.
405349
if !parity_shard.is_empty() {
406-
payload[SIZE_OF_CODING_SHRED_HEADERS..].copy_from_slice(parity_shard);
350+
let offset = cursor.position() as usize;
351+
debug_assert_eq!(offset, SIZE_OF_CODING_SHRED_HEADERS);
352+
payload[offset..].copy_from_slice(parity_shard);
407353
}
408354
Shred {
409355
common_header,
@@ -491,9 +437,14 @@ impl Shred {
491437
},
492438
};
493439
match shred_type {
494-
ShredType::Code => Ok(shred),
440+
ShredType::Code => {
441+
if shred.len() != SHRED_PAYLOAD_SIZE {
442+
return Err(Error::InvalidPayloadSize(shred.len()));
443+
}
444+
Ok(shred)
445+
}
495446
ShredType::Data => {
496-
if shred.len() > SHRED_PAYLOAD_SIZE {
447+
if !(SHRED_DATA_OFFSET..SHRED_PAYLOAD_SIZE).contains(&shred.len()) {
497448
return Err(Error::InvalidPayloadSize(shred.len()));
498449
}
499450
shred.resize(SHRED_PAYLOAD_SIZE, 0u8);
@@ -627,24 +578,12 @@ impl Shred {
627578

628579
pub fn set_index(&mut self, index: u32) {
629580
self.common_header.index = index;
630-
Self::serialize_obj_into(
631-
&mut 0,
632-
SIZE_OF_COMMON_SHRED_HEADER,
633-
&mut self.payload,
634-
&self.common_header,
635-
)
636-
.unwrap();
581+
bincode::serialize_into(&mut self.payload[..], &self.common_header).unwrap();
637582
}
638583

639584
pub fn set_slot(&mut self, slot: Slot) {
640585
self.common_header.slot = slot;
641-
Self::serialize_obj_into(
642-
&mut 0,
643-
SIZE_OF_COMMON_SHRED_HEADER,
644-
&mut self.payload,
645-
&self.common_header,
646-
)
647-
.unwrap();
586+
bincode::serialize_into(&mut self.payload[..], &self.common_header).unwrap();
648587
}
649588

650589
pub fn signature(&self) -> Signature {
@@ -695,6 +634,8 @@ impl Shred {
695634
if self.is_data() {
696635
self.data_header.flags |= ShredFlags::LAST_SHRED_IN_SLOT
697636
}
637+
let buffer = &mut self.payload[SIZE_OF_COMMON_SHRED_HEADER..];
638+
bincode::serialize_into(buffer, &self.data_header).unwrap();
698639
}
699640

700641
#[cfg(test)]
@@ -704,17 +645,9 @@ impl Shred {
704645
.flags
705646
.remove(ShredFlags::DATA_COMPLETE_SHRED);
706647
}
707-
708648
// Data header starts after the shred common header
709-
let mut start = SIZE_OF_COMMON_SHRED_HEADER;
710-
let size_of_data_shred_header = SIZE_OF_DATA_SHRED_HEADER;
711-
Self::serialize_obj_into(
712-
&mut start,
713-
size_of_data_shred_header,
714-
&mut self.payload,
715-
&self.data_header,
716-
)
717-
.expect("Failed to write data header into shred buffer");
649+
let buffer = &mut self.payload[SIZE_OF_COMMON_SHRED_HEADER..];
650+
bincode::serialize_into(buffer, &self.data_header).unwrap();
718651
}
719652

720653
pub fn data_complete(&self) -> bool {
@@ -1276,6 +1209,48 @@ mod tests {
12761209
);
12771210
}
12781211

1212+
#[test]
1213+
fn test_serde_compat_shred_data_empty() {
1214+
const SEED: &str = "E3M5hm8yAEB7iPhQxFypAkLqxNeZCTuGBDMa8Jdrghoo";
1215+
const PAYLOAD: &str = "nRNFVBEsV9FEM5KfmsCXJsgELRSkCV55drTavdy5aZPnsp\
1216+
B8WvsgY99ZuNHDnwkrqe6Lx7ARVmercwugR5HwDcLA9ivKMypk9PNucDPLs67TXWy6k9R\
1217+
ozKmy";
1218+
let mut rng = {
1219+
let seed = <[u8; 32]>::try_from(bs58_decode(SEED)).unwrap();
1220+
ChaChaRng::from_seed(seed)
1221+
};
1222+
let keypair = Keypair::generate(&mut rng);
1223+
let mut shred = Shred::new_from_data(
1224+
142076266, // slot
1225+
21443, // index
1226+
51279, // parent_offset
1227+
&[], // data
1228+
true, // is_last_data
1229+
false, // is_last_in_slot
1230+
49, // reference_tick
1231+
59445, // version
1232+
21414, // fec_set_index
1233+
);
1234+
shred.sign(&keypair);
1235+
assert!(shred.verify(&keypair.pubkey()));
1236+
assert_matches!(shred.sanitize(), Ok(()));
1237+
let payload = bs58_decode(PAYLOAD);
1238+
let mut packet = Packet::default();
1239+
packet.data[..payload.len()].copy_from_slice(&payload);
1240+
packet.meta.size = payload.len();
1241+
assert_eq!(shred.bytes_to_store(), payload);
1242+
assert_eq!(shred, Shred::new_from_serialized_shred(payload).unwrap());
1243+
assert_eq!(
1244+
shred.reference_tick(),
1245+
Shred::reference_tick_from_data(&packet.data)
1246+
);
1247+
assert_eq!(Shred::get_slot_from_packet(&packet), Some(shred.slot()));
1248+
assert_eq!(
1249+
get_shred_slot_index_type(&packet, &mut ShredFetchStats::default()),
1250+
Some((shred.slot(), shred.index(), shred.shred_type()))
1251+
);
1252+
}
1253+
12791254
#[test]
12801255
fn test_serde_compat_shred_code() {
12811256
const SEED: &str = "4jfjh3UZVyaEgvyG9oQmNyFY9yHDmbeH9eUhnBKkrcrN";

0 commit comments

Comments
 (0)