Skip to content

Commit

Permalink
Trim extra shred bytes in blockstore (solana-labs#16602)
Browse files Browse the repository at this point in the history
Strip the zero-padding off of data shreds before insertion into blockstore

Co-authored-by: Stephen Akridge <[email protected]>
Co-authored-by: Nathan Hawkins <[email protected]>
  • Loading branch information
3 people authored Apr 27, 2021
1 parent 283f587 commit bc31378
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 35 deletions.
3 changes: 1 addition & 2 deletions core/src/replay_stage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2944,7 +2944,6 @@ pub(crate) mod tests {
fn test_dead_fork_entry_deserialize_failure() {
// Insert entry that causes deserialization failure
let res = check_dead_fork(|_, _| {
let payload_len = SIZE_OF_DATA_SHRED_PAYLOAD;
let gibberish = [0xa5u8; PACKET_DATA_SIZE];
let mut data_header = DataShredHeader::default();
data_header.flags |= DATA_COMPLETE_SHRED;
Expand All @@ -2957,7 +2956,7 @@ pub(crate) mod tests {
);
bincode::serialize_into(
&mut shred.payload[SIZE_OF_COMMON_SHRED_HEADER + SIZE_OF_DATA_SHRED_HEADER..],
&gibberish[..payload_len],
&gibberish[..SIZE_OF_DATA_SHRED_PAYLOAD],
)
.unwrap();
vec![shred]
Expand Down
22 changes: 4 additions & 18 deletions core/src/serve_repair.rs
Original file line number Diff line number Diff line change
Expand Up @@ -611,9 +611,7 @@ mod tests {
use solana_ledger::{
blockstore::make_many_slot_entries,
blockstore_processor::fill_blockstore_slot_with_ticks,
shred::{
max_ticks_per_n_shreds, CodingShredHeader, DataShredHeader, Shred, ShredCommonHeader,
},
shred::{max_ticks_per_n_shreds, Shred},
};
use solana_perf::packet::Packet;
use solana_sdk::{hash::Hash, pubkey::Pubkey, timing::timestamp};
Expand Down Expand Up @@ -726,23 +724,10 @@ mod tests {
nonce,
);
assert!(rv.is_none());
let common_header = ShredCommonHeader {
slot,
index: 1,
..ShredCommonHeader::default()
};
let data_header = DataShredHeader {
parent_offset: 1,
..DataShredHeader::default()
};
let shred_info = Shred::new_empty_from_header(
common_header,
data_header,
CodingShredHeader::default(),
);
let shred = Shred::new_from_data(slot, 1, 1, None, false, false, 0, 2, 0);

blockstore
.insert_shreds(vec![shred_info], None, false)
.insert_shreds(vec![shred], None, false)
.expect("Expect successful ledger write");

let index = 1;
Expand Down Expand Up @@ -954,6 +939,7 @@ mod tests {
assert_eq!(shreds[0].slot(), 1);
assert_eq!(shreds[0].index(), 0);
shreds[0].payload.push(10);
shreds[0].data_header.size = shreds[0].payload.len() as u16;
blockstore
.insert_shreds(shreds, None, false)
.expect("Expect successful ledger write");
Expand Down
55 changes: 50 additions & 5 deletions ledger/src/blockstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,7 @@ impl Blockstore {
leader_schedule: Option<&Arc<LeaderScheduleCache>>,
is_recovered: bool,
) -> bool {
use crate::shred::SHRED_PAYLOAD_SIZE;
let shred_index = u64::from(shred.index());
let slot = shred.slot();
let last_in_slot = if shred.last_in_slot() {
Expand All @@ -1290,6 +1291,13 @@ impl Blockstore {
false
};

if shred.data_header.size == 0 {
return false;
}
if shred.payload.len() > SHRED_PAYLOAD_SIZE {
return false;
}

// Check that we do not receive shred_index >= than the last_index
// for the slot
let last_index = slot_meta.last_index;
Expand Down Expand Up @@ -1410,7 +1418,12 @@ impl Blockstore {

// Commit step: commit all changes to the mutable structures at once, or none at all.
// We don't want only a subset of these changes going through.
write_batch.put_bytes::<cf::ShredData>((slot, index), &shred.payload)?;
write_batch.put_bytes::<cf::ShredData>(
(slot, index),
// Payload will be padded out to SHRED_PAYLOAD_SIZE
// But only need to store the bytes within data_header.size
&shred.payload[..shred.data_header.size as usize],
)?;
data_index.set_present(index, true);
let newly_completed_data_sets = update_slot_meta(
last_in_slot,
Expand Down Expand Up @@ -1438,7 +1451,16 @@ impl Blockstore {
}

pub fn get_data_shred(&self, slot: Slot, index: u64) -> Result<Option<Vec<u8>>> {
self.data_shred_cf.get_bytes((slot, index))
use crate::shred::SHRED_PAYLOAD_SIZE;
self.data_shred_cf.get_bytes((slot, index)).map(|data| {
data.map(|mut d| {
// Only data_header.size bytes stored in the blockstore so
// pad the payload out to SHRED_PAYLOAD_SIZE so that the
// erasure recovery works properly.
d.resize(cmp::max(d.len(), SHRED_PAYLOAD_SIZE), 0);
d
})
})
}

pub fn get_data_shreds_for_slot(
Expand Down Expand Up @@ -2805,7 +2827,7 @@ impl Blockstore {
&self,
slot: u64,
index: u32,
new_shred: &[u8],
new_shred_raw: &[u8],
is_data: bool,
) -> Option<Vec<u8>> {
let res = if is_data {
Expand All @@ -2816,8 +2838,14 @@ impl Blockstore {
.expect("fetch from DuplicateSlots column family failed")
};

let mut payload = new_shred_raw.to_vec();
payload.resize(
std::cmp::max(new_shred_raw.len(), crate::shred::SHRED_PAYLOAD_SIZE),
0,
);
let new_shred = Shred::new_from_serialized_shred(payload).unwrap();
res.map(|existing_shred| {
if existing_shred != new_shred {
if existing_shred != new_shred.payload {
Some(existing_shred)
} else {
None
Expand Down Expand Up @@ -5270,6 +5298,23 @@ pub mod tests {
.insert_shreds(shreds[0..5].to_vec(), None, false)
.unwrap();

let slot_meta = blockstore.meta(0).unwrap().unwrap();
// Corrupt shred by making it too large
let mut shred5 = shreds[5].clone();
shred5.payload.push(10);
shred5.data_header.size = shred5.payload.len() as u16;
assert_eq!(
blockstore.should_insert_data_shred(
&shred5,
&slot_meta,
&HashMap::new(),
&last_root,
None,
false
),
false
);

// Trying to insert another "is_last" shred with index < the received index should fail
// skip over shred 7
blockstore
Expand Down Expand Up @@ -7722,7 +7767,7 @@ pub mod tests {
&duplicate_shred.payload,
duplicate_shred.is_data()
),
Some(shred.payload.clone())
Some(shred.payload.to_vec())
);
assert!(blockstore
.is_shred_duplicate(
Expand Down
16 changes: 6 additions & 10 deletions ledger/src/shred.rs
Original file line number Diff line number Diff line change
Expand Up @@ -216,11 +216,12 @@ impl Shred {
where
T: Deserialize<'de>,
{
let end = std::cmp::min(*index + size, buf.len());
let ret = bincode::options()
.with_limit(PACKET_DATA_SIZE as u64)
.with_fixint_encoding()
.allow_trailing_bytes()
.deserialize(&buf[*index..*index + size])?;
.deserialize(&buf[*index..end])?;
*index += size;
Ok(ret)
}
Expand Down Expand Up @@ -318,15 +319,10 @@ impl Shred {
Self::deserialize_obj(&mut start, SIZE_OF_COMMON_SHRED_HEADER, &payload)?;

let slot = common_header.slot;
let expected_data_size = SHRED_PAYLOAD_SIZE;
// Safe because any payload from the network must have passed through
// window service, which implies payload wll be of size
// PACKET_DATA_SIZE, and `expected_data_size` <= PACKET_DATA_SIZE.
//
// On the other hand, if this function is called locally, the payload size should match
// the `expected_data_size`.
assert!(payload.len() >= expected_data_size);
payload.truncate(expected_data_size);
// Shreds should be padded out to SHRED_PAYLOAD_SIZE
// so that erasure generation/recovery works correctly
// But only the data_header.size is stored in blockstore.
payload.resize(SHRED_PAYLOAD_SIZE, 0);
let shred = if common_header.shred_type == ShredType(CODING_SHRED) {
let coding_header: CodingShredHeader =
Self::deserialize_obj(&mut start, SIZE_OF_CODING_SHRED_HEADER, &payload)?;
Expand Down

0 comments on commit bc31378

Please sign in to comment.