Skip to content

Commit 0e932c7

Browse files
authored
[TieredStorage] Refactor TieredStorage::new_readonly() code path (pyth-network#195)
#### Problem The TieredStorage::new_readonly() function currently has the following problems: * It opens the file without checking the magic number before checking and loading the footer. * It opens the file twice: first to load the footer, then open again by the reader. #### Summary of Changes This PR refactors TieredStorage::new_readonly() so that it first performs all checks inside the constructor of TieredReadableFile. The TieredReadableFile instance is then passed to the proper reader (currently HotStorageReader) when all checks are passed. #### Test Plan * Added a new test to check MagicNumberMismatch. * Existing tiered-storage tests
1 parent f41fb84 commit 0e932c7

File tree

5 files changed

+105
-49
lines changed

5 files changed

+105
-49
lines changed

accounts-db/src/tiered_storage.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,8 @@ mod tests {
170170
use {
171171
super::*,
172172
crate::account_storage::meta::StoredMetaWriteVersion,
173-
footer::{TieredStorageFooter, TieredStorageMagicNumber},
173+
file::TieredStorageMagicNumber,
174+
footer::TieredStorageFooter,
174175
hot::HOT_FORMAT,
175176
index::IndexOffset,
176177
solana_sdk::{

accounts-db/src/tiered_storage/file.rs

Lines changed: 75 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use {
2-
bytemuck::{AnyBitPattern, NoUninit},
2+
super::{error::TieredStorageError, TieredStorageResult},
3+
bytemuck::{AnyBitPattern, NoUninit, Pod, Zeroable},
34
std::{
45
fs::{File, OpenOptions},
56
io::{BufWriter, Read, Result as IoResult, Seek, SeekFrom, Write},
@@ -8,23 +9,37 @@ use {
89
},
910
};
1011

12+
/// The ending 8 bytes of a valid tiered account storage file.
13+
pub const FILE_MAGIC_NUMBER: u64 = u64::from_le_bytes(*b"AnzaTech");
14+
15+
#[derive(Debug, PartialEq, Eq, Clone, Copy, Pod, Zeroable)]
16+
#[repr(C)]
17+
pub struct TieredStorageMagicNumber(pub u64);
18+
19+
// Ensure there are no implicit padding bytes
20+
const _: () = assert!(std::mem::size_of::<TieredStorageMagicNumber>() == 8);
21+
22+
impl Default for TieredStorageMagicNumber {
23+
fn default() -> Self {
24+
Self(FILE_MAGIC_NUMBER)
25+
}
26+
}
27+
1128
#[derive(Debug)]
1229
pub struct TieredReadableFile(pub File);
1330

1431
impl TieredReadableFile {
15-
pub fn new(file_path: impl AsRef<Path>) -> Self {
16-
Self(
32+
pub fn new(file_path: impl AsRef<Path>) -> TieredStorageResult<Self> {
33+
let file = Self(
1734
OpenOptions::new()
1835
.read(true)
1936
.create(false)
20-
.open(&file_path)
21-
.unwrap_or_else(|err| {
22-
panic!(
23-
"[TieredStorageError] Unable to open {} as read-only: {err}",
24-
file_path.as_ref().display(),
25-
);
26-
}),
27-
)
37+
.open(&file_path)?,
38+
);
39+
40+
file.check_magic_number()?;
41+
42+
Ok(file)
2843
}
2944

3045
pub fn new_writable(file_path: impl AsRef<Path>) -> IoResult<Self> {
@@ -36,6 +51,19 @@ impl TieredReadableFile {
3651
))
3752
}
3853

54+
fn check_magic_number(&self) -> TieredStorageResult<()> {
55+
self.seek_from_end(-(std::mem::size_of::<TieredStorageMagicNumber>() as i64))?;
56+
let mut magic_number = TieredStorageMagicNumber::zeroed();
57+
self.read_pod(&mut magic_number)?;
58+
if magic_number != TieredStorageMagicNumber::default() {
59+
return Err(TieredStorageError::MagicNumberMismatch(
60+
TieredStorageMagicNumber::default().0,
61+
magic_number.0,
62+
));
63+
}
64+
Ok(())
65+
}
66+
3967
/// Reads a value of type `T` from the file.
4068
///
4169
/// Type T must be plain ol' data.
@@ -127,3 +155,39 @@ impl TieredWritableFile {
127155
Ok(bytes.len())
128156
}
129157
}
158+
159+
#[cfg(test)]
160+
mod tests {
161+
use {
162+
crate::tiered_storage::{
163+
error::TieredStorageError,
164+
file::{TieredReadableFile, TieredWritableFile, FILE_MAGIC_NUMBER},
165+
},
166+
std::path::Path,
167+
tempfile::TempDir,
168+
};
169+
170+
fn generate_test_file_with_number(path: impl AsRef<Path>, number: u64) {
171+
let mut file = TieredWritableFile::new(path).unwrap();
172+
file.write_pod(&number).unwrap();
173+
}
174+
175+
#[test]
176+
fn test_new() {
177+
let temp_dir = TempDir::new().unwrap();
178+
let path = temp_dir.path().join("test_new");
179+
generate_test_file_with_number(&path, FILE_MAGIC_NUMBER);
180+
assert!(TieredReadableFile::new(&path).is_ok());
181+
}
182+
183+
#[test]
184+
fn test_magic_number_mismatch() {
185+
let temp_dir = TempDir::new().unwrap();
186+
let path = temp_dir.path().join("test_magic_number_mismatch");
187+
generate_test_file_with_number(&path, !FILE_MAGIC_NUMBER);
188+
assert!(matches!(
189+
TieredReadableFile::new(&path),
190+
Err(TieredStorageError::MagicNumberMismatch(_, _))
191+
));
192+
}
193+
}

accounts-db/src/tiered_storage/footer.rs

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
use {
22
crate::tiered_storage::{
33
error::TieredStorageError,
4-
file::{TieredReadableFile, TieredWritableFile},
4+
file::{TieredReadableFile, TieredStorageMagicNumber, TieredWritableFile},
55
index::IndexBlockFormat,
66
mmap_utils::{get_pod, get_type},
77
owners::OwnersBlockFormat,
88
TieredStorageResult,
99
},
10-
bytemuck::{Pod, Zeroable},
10+
bytemuck::Zeroable,
1111
memmap2::Mmap,
1212
num_enum::TryFromPrimitiveError,
1313
solana_sdk::{hash::Hash, pubkey::Pubkey},
@@ -26,22 +26,6 @@ static_assertions::const_assert_eq!(mem::size_of::<TieredStorageFooter>(), 160);
2626
/// even when the footer's format changes.
2727
pub const FOOTER_TAIL_SIZE: usize = 24;
2828

29-
/// The ending 8 bytes of a valid tiered account storage file.
30-
pub const FOOTER_MAGIC_NUMBER: u64 = 0x502A2AB5; // SOLALABS -> SOLANA LABS
31-
32-
#[derive(Debug, PartialEq, Eq, Clone, Copy, Pod, Zeroable)]
33-
#[repr(C)]
34-
pub struct TieredStorageMagicNumber(pub u64);
35-
36-
// Ensure there are no implicit padding bytes
37-
const _: () = assert!(std::mem::size_of::<TieredStorageMagicNumber>() == 8);
38-
39-
impl Default for TieredStorageMagicNumber {
40-
fn default() -> Self {
41-
Self(FOOTER_MAGIC_NUMBER)
42-
}
43-
}
44-
4529
#[repr(u16)]
4630
#[derive(
4731
Clone,
@@ -133,7 +117,7 @@ pub struct TieredStorageFooter {
133117
/// The size of the footer including the magic number.
134118
pub footer_size: u64,
135119
// This field is persisted in the storage but not in this struct.
136-
// The number should match FOOTER_MAGIC_NUMBER.
120+
// The number should match FILE_MAGIC_NUMBER.
137121
// pub magic_number: u64,
138122
}
139123

@@ -186,7 +170,7 @@ impl Default for TieredStorageFooter {
186170

187171
impl TieredStorageFooter {
188172
pub fn new_from_path(path: impl AsRef<Path>) -> TieredStorageResult<Self> {
189-
let file = TieredReadableFile::new(path);
173+
let file = TieredReadableFile::new(path)?;
190174
Self::new_from_footer_block(&file)
191175
}
192176

accounts-db/src/tiered_storage/hot.rs

Lines changed: 20 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use {
77
accounts_hash::AccountHash,
88
tiered_storage::{
99
byte_block,
10-
file::TieredWritableFile,
10+
file::{TieredReadableFile, TieredWritableFile},
1111
footer::{AccountBlockFormat, AccountMetaFormat, TieredStorageFooter},
1212
index::{AccountIndexWriterEntry, AccountOffset, IndexBlockFormat, IndexOffset},
1313
meta::{AccountMetaFlags, AccountMetaOptionalFields, TieredAccountMeta},
@@ -24,7 +24,7 @@ use {
2424
account::ReadableAccount, pubkey::Pubkey, rent_collector::RENT_EXEMPT_RENT_EPOCH,
2525
stake_history::Epoch,
2626
},
27-
std::{borrow::Borrow, fs::OpenOptions, option::Option, path::Path},
27+
std::{borrow::Borrow, option::Option, path::Path},
2828
};
2929

3030
pub const HOT_FORMAT: TieredStorageFormat = TieredStorageFormat {
@@ -346,10 +346,8 @@ pub struct HotStorageReader {
346346
}
347347

348348
impl HotStorageReader {
349-
/// Constructs a HotStorageReader from the specified path.
350-
pub fn new_from_path(path: impl AsRef<Path>) -> TieredStorageResult<Self> {
351-
let file = OpenOptions::new().read(true).open(path)?;
352-
let mmap = unsafe { MmapOptions::new().map(&file)? };
349+
pub fn new(file: TieredReadableFile) -> TieredStorageResult<Self> {
350+
let mmap = unsafe { MmapOptions::new().map(&file.0)? };
353351
// Here we are copying the footer, as accessing any data in a
354352
// TieredStorage instance requires accessing its Footer.
355353
// This can help improve cache locality and reduce the overhead
@@ -899,7 +897,8 @@ pub mod tests {
899897
// Reopen the same storage, and expect the persisted footer is
900898
// the same as what we have written.
901899
{
902-
let hot_storage = HotStorageReader::new_from_path(&path).unwrap();
900+
let file = TieredReadableFile::new(&path).unwrap();
901+
let hot_storage = HotStorageReader::new(file).unwrap();
903902
assert_eq!(expected_footer, *hot_storage.footer());
904903
}
905904
}
@@ -945,7 +944,8 @@ pub mod tests {
945944
footer.write_footer_block(&mut file).unwrap();
946945
}
947946

948-
let hot_storage = HotStorageReader::new_from_path(&path).unwrap();
947+
let file = TieredReadableFile::new(&path).unwrap();
948+
let hot_storage = HotStorageReader::new(file).unwrap();
949949

950950
for (offset, expected_meta) in account_offsets.iter().zip(hot_account_metas.iter()) {
951951
let meta = hot_storage.get_account_meta_from_offset(*offset).unwrap();
@@ -975,7 +975,8 @@ pub mod tests {
975975
footer.write_footer_block(&mut file).unwrap();
976976
}
977977

978-
let hot_storage = HotStorageReader::new_from_path(&path).unwrap();
978+
let file = TieredReadableFile::new(&path).unwrap();
979+
let hot_storage = HotStorageReader::new(file).unwrap();
979980
let offset = HotAccountOffset::new(footer.index_block_offset as usize).unwrap();
980981
// Read from index_block_offset, which offset doesn't belong to
981982
// account blocks. Expect assert failure here
@@ -1026,7 +1027,8 @@ pub mod tests {
10261027
footer.write_footer_block(&mut file).unwrap();
10271028
}
10281029

1029-
let hot_storage = HotStorageReader::new_from_path(&path).unwrap();
1030+
let file = TieredReadableFile::new(&path).unwrap();
1031+
let hot_storage = HotStorageReader::new(file).unwrap();
10301032
for (i, index_writer_entry) in index_writer_entries.iter().enumerate() {
10311033
let account_offset = hot_storage
10321034
.get_account_offset(IndexOffset(i as u32))
@@ -1075,7 +1077,8 @@ pub mod tests {
10751077
footer.write_footer_block(&mut file).unwrap();
10761078
}
10771079

1078-
let hot_storage = HotStorageReader::new_from_path(&path).unwrap();
1080+
let file = TieredReadableFile::new(&path).unwrap();
1081+
let hot_storage = HotStorageReader::new(file).unwrap();
10791082
for (i, address) in addresses.iter().enumerate() {
10801083
assert_eq!(
10811084
hot_storage
@@ -1149,7 +1152,8 @@ pub mod tests {
11491152
footer.write_footer_block(&mut file).unwrap();
11501153
}
11511154

1152-
let hot_storage = HotStorageReader::new_from_path(&path).unwrap();
1155+
let file = TieredReadableFile::new(&path).unwrap();
1156+
let hot_storage = HotStorageReader::new(file).unwrap();
11531157

11541158
// First, verify whether we can find the expected owners.
11551159
let mut owner_candidates = owner_addresses.clone();
@@ -1281,7 +1285,8 @@ pub mod tests {
12811285
footer.write_footer_block(&mut file).unwrap();
12821286
}
12831287

1284-
let hot_storage = HotStorageReader::new_from_path(&path).unwrap();
1288+
let file = TieredReadableFile::new(&path).unwrap();
1289+
let hot_storage = HotStorageReader::new(file).unwrap();
12851290

12861291
for i in 0..NUM_ACCOUNTS {
12871292
let (stored_meta, next) = hot_storage
@@ -1362,10 +1367,10 @@ pub mod tests {
13621367
writer.write_accounts(&storable_accounts, 0).unwrap()
13631368
};
13641369

1365-
let hot_storage = HotStorageReader::new_from_path(&path).unwrap();
1370+
let file = TieredReadableFile::new(&path).unwrap();
1371+
let hot_storage = HotStorageReader::new(file).unwrap();
13661372

13671373
let num_accounts = account_data_sizes.len();
1368-
13691374
for i in 0..num_accounts {
13701375
let (stored_meta, next) = hot_storage
13711376
.get_account(IndexOffset(i as u32))

accounts-db/src/tiered_storage/readable.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use {
33
account_storage::meta::StoredAccountMeta,
44
accounts_file::MatchAccountOwnerError,
55
tiered_storage::{
6+
file::TieredReadableFile,
67
footer::{AccountMetaFormat, TieredStorageFooter},
78
hot::HotStorageReader,
89
index::IndexOffset,
@@ -22,9 +23,10 @@ pub enum TieredStorageReader {
2223
impl TieredStorageReader {
2324
/// Creates a reader for the specified tiered storage accounts file.
2425
pub fn new_from_path(path: impl AsRef<Path>) -> TieredStorageResult<Self> {
25-
let footer = TieredStorageFooter::new_from_path(&path)?;
26+
let file = TieredReadableFile::new(&path)?;
27+
let footer = TieredStorageFooter::new_from_footer_block(&file)?;
2628
match footer.account_meta_format {
27-
AccountMetaFormat::Hot => Ok(Self::Hot(HotStorageReader::new_from_path(path)?)),
29+
AccountMetaFormat::Hot => Ok(Self::Hot(HotStorageReader::new(file)?)),
2830
}
2931
}
3032

0 commit comments

Comments
 (0)