Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions entry/src/entry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ use {
sync::{Arc, Once, OnceLock},
time::Instant,
},
wincode::{containers::Pod, SchemaRead, SchemaWrite},
wincode::{
containers::{Pod, Vec as WincodeVec},
len::BincodeLen,
SchemaRead, SchemaWrite,
},
};

pub type EntrySender = Sender<Vec<Entry>>;
Expand Down Expand Up @@ -78,6 +82,10 @@ pub struct Api<'a> {
Symbol<'a, unsafe extern "C" fn(hashes: *mut u8, num_hashes: *const u64)>,
}

const MAX_DATA_SHREDS_PER_SLOT: usize = 32_768;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The duplication here not ideal but I guess we have to in order to avoid circular dependency. Alex P brought it up a while ago but maybe time to break out agave-shred crate from solana-ledger. No action needed here tho

pub const MAX_DATA_SHREDS_SIZE: usize = MAX_DATA_SHREDS_PER_SLOT * solana_packet::PACKET_DATA_SIZE;
pub type MaxDataShredsLen = BincodeLen<MAX_DATA_SHREDS_SIZE>;

/// Each Entry contains three pieces of data. The `num_hashes` field is the number
/// of hashes performed since the previous entry. The `hash` field is the result
/// of hashing `hash` from the previous entry `num_hashes` times. The `transactions`
Expand Down Expand Up @@ -117,7 +125,7 @@ pub struct Entry {
/// An unordered list of transactions that were observed before the Entry ID was
/// generated. They may have been observed before a previous Entry ID but were
/// pushed back into this list to ensure deterministic interpretation of the ledger.
#[wincode(with = "Vec<crate::wincode::VersionedTransaction>")]
#[wincode(with = "WincodeVec<crate::wincode::VersionedTransaction, MaxDataShredsLen>")]
pub transactions: Vec<VersionedTransaction>,
}

Expand Down
80 changes: 79 additions & 1 deletion entry/src/wincode.rs
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ impl<'de> SchemaRead<'de> for VersionedMsg {
#[cfg(test)]
mod tests {
use {
crate::entry::Entry,
crate::entry::{Entry, MAX_DATA_SHREDS_SIZE},
proptest::prelude::*,
solana_address::{Address, ADDRESS_BYTES},
solana_hash::{Hash, HASH_BYTES},
Expand Down Expand Up @@ -361,4 +361,82 @@ mod tests {
prop_assert_eq!(entries, deserialized);
}
}

#[test]
fn entry_deserialize_rejects_excessive_prealloc() {
let message = LegacyMessage {
header: MessageHeader {
num_required_signatures: 0,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
account_keys: vec![],
recent_blockhash: Hash::new_from_array([0u8; HASH_BYTES]),
instructions: vec![],
};
let transaction = VersionedTransaction {
signatures: vec![],
message: VersionedMessage::Legacy(message),
};
let entry = Entry {
num_hashes: 0,
hash: Hash::new_from_array([0u8; HASH_BYTES]),
transactions: vec![transaction],
};

let mut data = wincode::serialize(&entry).unwrap();
let over_limit: usize = MAX_DATA_SHREDS_SIZE / size_of::<VersionedTransaction>() + 1;
let len_offset = 8 + HASH_BYTES;
// Fudge the length of the vec to be over the limit to trigger the preallocation
// size limit error.
data[len_offset..len_offset + 8].copy_from_slice(&over_limit.to_le_bytes());

let needed_bytes = over_limit * size_of::<VersionedTransaction>();
let err = Entry::deserialize(&data).unwrap_err();
assert!(matches!(
err,
wincode::error::ReadError::PreallocationSizeLimit {
limit: MAX_DATA_SHREDS_SIZE,
needed,
} if needed == needed_bytes,
));
}

#[test]
fn entry_deserialize_accepts_prealloc_at_limit() {
let message = LegacyMessage {
header: MessageHeader {
num_required_signatures: 0,
num_readonly_signed_accounts: 0,
num_readonly_unsigned_accounts: 0,
},
account_keys: vec![],
recent_blockhash: Hash::new_from_array([0u8; HASH_BYTES]),
instructions: vec![],
};
let transaction = VersionedTransaction {
signatures: vec![],
message: VersionedMessage::Legacy(message),
};
let entry = Entry {
num_hashes: 0,
hash: Hash::new_from_array([0u8; HASH_BYTES]),
transactions: vec![transaction],
};

let mut data = wincode::serialize(&entry).unwrap();
let at_limit: usize = MAX_DATA_SHREDS_SIZE / size_of::<VersionedTransaction>();
let len_offset = 8 + HASH_BYTES;
// Fudge the length of the vec to be at the limit.
data[len_offset..len_offset + 8].copy_from_slice(&at_limit.to_le_bytes());

let err = Entry::deserialize(&data).unwrap_err();
assert!(!matches!(
err,
wincode::error::ReadError::PreallocationSizeLimit {
limit: _,
needed: _,
}
));
}
}
5 changes: 3 additions & 2 deletions ledger/src/blockstore.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ use {
solana_account::ReadableAccount,
solana_address_lookup_table_interface::state::AddressLookupTable,
solana_clock::{Slot, UnixTimestamp, DEFAULT_TICKS_PER_SECOND},
solana_entry::entry::{create_ticks, Entry},
solana_entry::entry::{create_ticks, Entry, MaxDataShredsLen},
solana_genesis_config::{GenesisConfig, DEFAULT_GENESIS_ARCHIVE, DEFAULT_GENESIS_FILE},
solana_hash::Hash,
solana_keypair::Keypair,
Expand Down Expand Up @@ -82,6 +82,7 @@ use {
tar,
tempfile::{Builder, TempDir},
thiserror::Error,
wincode::{containers::Vec as WincodeVec, Deserialize as _},
};

pub mod blockstore_purge;
Expand Down Expand Up @@ -3703,7 +3704,7 @@ impl Blockstore {
)))
})
.and_then(|payload| {
wincode::deserialize::<Vec<Entry>>(&payload).map_err(|e| {
<WincodeVec<Entry, MaxDataShredsLen>>::deserialize(&payload).map_err(|e| {
BlockstoreError::InvalidShredData(Box::new(bincode::ErrorKind::Custom(
format!("could not reconstruct entries: {e:?}"),
)))
Expand Down
Loading