solana_accounts_db/tiered_storage/
index.rs1use {
2 crate::tiered_storage::{
3 file::TieredWritableFile, footer::TieredStorageFooter, mmap_utils::get_pod,
4 TieredStorageResult,
5 },
6 bytemuck::{Pod, Zeroable},
7 memmap2::Mmap,
8 solana_pubkey::Pubkey,
9};
10
11#[derive(Debug)]
13pub struct AccountIndexWriterEntry<Offset: AccountOffset> {
14 pub address: Pubkey,
16 pub offset: Offset,
18}
19
20pub trait AccountOffset: Clone + Copy + Pod + Zeroable {}
22
23#[repr(C)]
27#[derive(Clone, Copy, Debug, Eq, PartialEq, bytemuck_derive::Pod, bytemuck_derive::Zeroable)]
28pub struct IndexOffset(pub u32);
29
30const _: () = assert!(std::mem::size_of::<IndexOffset>() == 4);
32
33#[repr(u16)]
35#[derive(
36 Clone,
37 Copy,
38 Debug,
39 Default,
40 Eq,
41 Hash,
42 PartialEq,
43 num_enum::IntoPrimitive,
44 num_enum::TryFromPrimitive,
45)]
46pub enum IndexBlockFormat {
47 #[default]
51 AddressesThenOffsets = 0,
52}
53
54const _: () = assert!(std::mem::size_of::<IndexBlockFormat>() == 2);
56
57impl IndexBlockFormat {
58 pub fn write_index_block(
61 &self,
62 file: &mut TieredWritableFile,
63 index_entries: &[AccountIndexWriterEntry<impl AccountOffset>],
64 ) -> TieredStorageResult<usize> {
65 match self {
66 Self::AddressesThenOffsets => {
67 let mut bytes_written = 0;
68 for index_entry in index_entries {
69 bytes_written += file.write_pod(&index_entry.address)?;
70 }
71 for index_entry in index_entries {
72 bytes_written += file.write_pod(&index_entry.offset)?;
73 }
74 Ok(bytes_written)
75 }
76 }
77 }
78
79 pub fn get_account_address<'a>(
81 &self,
82 mmap: &'a Mmap,
83 footer: &TieredStorageFooter,
84 index_offset: IndexOffset,
85 ) -> TieredStorageResult<&'a Pubkey> {
86 let offset = match self {
87 Self::AddressesThenOffsets => {
88 debug_assert!(index_offset.0 < footer.account_entry_count);
89 footer.index_block_offset as usize
90 + std::mem::size_of::<Pubkey>() * (index_offset.0 as usize)
91 }
92 };
93
94 debug_assert!(
95 offset.saturating_add(std::mem::size_of::<Pubkey>())
96 <= footer.owners_block_offset as usize,
97 "reading IndexOffset ({}) would exceed index block boundary ({}).",
98 offset,
99 footer.owners_block_offset,
100 );
101
102 let (address, _) = get_pod::<Pubkey>(mmap, offset)?;
103 Ok(address)
104 }
105
106 pub fn get_account_offset<Offset: AccountOffset>(
108 &self,
109 mmap: &Mmap,
110 footer: &TieredStorageFooter,
111 index_offset: IndexOffset,
112 ) -> TieredStorageResult<Offset> {
113 let offset = match self {
114 Self::AddressesThenOffsets => {
115 debug_assert!(index_offset.0 < footer.account_entry_count);
116 footer.index_block_offset as usize
117 + std::mem::size_of::<Pubkey>() * footer.account_entry_count as usize
118 + std::mem::size_of::<Offset>() * index_offset.0 as usize
119 }
120 };
121
122 debug_assert!(
123 offset.saturating_add(std::mem::size_of::<Offset>())
124 <= footer.owners_block_offset as usize,
125 "reading IndexOffset ({}) would exceed index block boundary ({}).",
126 offset,
127 footer.owners_block_offset,
128 );
129
130 let (account_offset, _) = get_pod::<Offset>(mmap, offset)?;
131
132 Ok(*account_offset)
133 }
134
135 pub fn entry_size<Offset: AccountOffset>(&self) -> usize {
137 match self {
138 Self::AddressesThenOffsets => {
139 std::mem::size_of::<Pubkey>() + std::mem::size_of::<Offset>()
140 }
141 }
142 }
143}
144
145#[cfg(test)]
146mod tests {
147 use {
148 super::*,
149 crate::tiered_storage::{
150 file::TieredWritableFile,
151 hot::{HotAccountOffset, HOT_ACCOUNT_ALIGNMENT},
152 },
153 memmap2::MmapOptions,
154 rand::Rng,
155 std::fs::OpenOptions,
156 tempfile::TempDir,
157 };
158
159 #[test]
160 fn test_address_and_offset_indexer() {
161 const ENTRY_COUNT: usize = 100;
162 let mut footer = TieredStorageFooter {
163 account_entry_count: ENTRY_COUNT as u32,
164 ..TieredStorageFooter::default()
165 };
166 let temp_dir = TempDir::new().unwrap();
167 let path = temp_dir.path().join("test_address_and_offset_indexer");
168 let addresses: Vec<_> = std::iter::repeat_with(Pubkey::new_unique)
169 .take(ENTRY_COUNT)
170 .collect();
171 let mut rng = rand::thread_rng();
172 let index_entries: Vec<_> = addresses
173 .iter()
174 .map(|address| AccountIndexWriterEntry {
175 address: *address,
176 offset: HotAccountOffset::new(
177 rng.gen_range(0..u32::MAX) as usize * HOT_ACCOUNT_ALIGNMENT,
178 )
179 .unwrap(),
180 })
181 .collect();
182
183 {
184 let mut file = TieredWritableFile::new(&path).unwrap();
185 let indexer = IndexBlockFormat::AddressesThenOffsets;
186 let cursor = indexer
187 .write_index_block(&mut file, &index_entries)
188 .unwrap();
189 footer.owners_block_offset = cursor as u64;
190 }
191
192 let indexer = IndexBlockFormat::AddressesThenOffsets;
193 let file = OpenOptions::new()
194 .read(true)
195 .create(false)
196 .open(&path)
197 .unwrap();
198 let mmap = unsafe { MmapOptions::new().map(&file).unwrap() };
199 for (i, index_entry) in index_entries.iter().enumerate() {
200 let account_offset = indexer
201 .get_account_offset::<HotAccountOffset>(&mmap, &footer, IndexOffset(i as u32))
202 .unwrap();
203 assert_eq!(index_entry.offset, account_offset);
204 let address = indexer
205 .get_account_address(&mmap, &footer, IndexOffset(i as u32))
206 .unwrap();
207 assert_eq!(index_entry.address, *address);
208 }
209 }
210
211 #[test]
212 #[should_panic(expected = "index_offset.0 < footer.account_entry_count")]
213 fn test_get_account_address_out_of_bounds() {
214 let temp_dir = TempDir::new().unwrap();
215 let path = temp_dir
216 .path()
217 .join("test_get_account_address_out_of_bounds");
218
219 let footer = TieredStorageFooter {
220 account_entry_count: 100,
221 index_block_format: IndexBlockFormat::AddressesThenOffsets,
222 ..TieredStorageFooter::default()
223 };
224
225 {
226 let mut file = TieredWritableFile::new(&path).unwrap();
229 footer.write_footer_block(&mut file).unwrap();
230 }
231
232 let file = OpenOptions::new()
233 .read(true)
234 .create(false)
235 .open(&path)
236 .unwrap();
237 let mmap = unsafe { MmapOptions::new().map(&file).unwrap() };
238 footer
239 .index_block_format
240 .get_account_address(&mmap, &footer, IndexOffset(footer.account_entry_count))
241 .unwrap();
242 }
243
244 #[test]
245 #[should_panic(expected = "would exceed index block boundary")]
246 fn test_get_account_address_exceeds_index_block_boundary() {
247 let temp_dir = TempDir::new().unwrap();
248 let path = temp_dir
249 .path()
250 .join("test_get_account_address_exceeds_index_block_boundary");
251
252 let footer = TieredStorageFooter {
253 account_entry_count: 100,
254 index_block_format: IndexBlockFormat::AddressesThenOffsets,
255 index_block_offset: 1024,
256 owners_block_offset: 1024 + std::mem::size_of::<HotAccountOffset>() as u64,
258 ..TieredStorageFooter::default()
259 };
260
261 {
262 let mut file = TieredWritableFile::new(&path).unwrap();
265 footer.write_footer_block(&mut file).unwrap();
266 }
267
268 let file = OpenOptions::new()
269 .read(true)
270 .create(false)
271 .open(&path)
272 .unwrap();
273 let mmap = unsafe { MmapOptions::new().map(&file).unwrap() };
274 footer
277 .index_block_format
278 .get_account_address(&mmap, &footer, IndexOffset(2))
279 .unwrap();
280 }
281
282 #[test]
283 #[should_panic(expected = "index_offset.0 < footer.account_entry_count")]
284 fn test_get_account_offset_out_of_bounds() {
285 let temp_dir = TempDir::new().unwrap();
286 let path = temp_dir
287 .path()
288 .join("test_get_account_offset_out_of_bounds");
289
290 let footer = TieredStorageFooter {
291 account_entry_count: 100,
292 index_block_format: IndexBlockFormat::AddressesThenOffsets,
293 ..TieredStorageFooter::default()
294 };
295
296 {
297 let mut file = TieredWritableFile::new(&path).unwrap();
300 footer.write_footer_block(&mut file).unwrap();
301 }
302
303 let file = OpenOptions::new()
304 .read(true)
305 .create(false)
306 .open(&path)
307 .unwrap();
308 let mmap = unsafe { MmapOptions::new().map(&file).unwrap() };
309 footer
310 .index_block_format
311 .get_account_offset::<HotAccountOffset>(
312 &mmap,
313 &footer,
314 IndexOffset(footer.account_entry_count),
315 )
316 .unwrap();
317 }
318
319 #[test]
320 #[should_panic(expected = "would exceed index block boundary")]
321 fn test_get_account_offset_exceeds_index_block_boundary() {
322 let temp_dir = TempDir::new().unwrap();
323 let path = temp_dir
324 .path()
325 .join("test_get_account_offset_exceeds_index_block_boundary");
326
327 let footer = TieredStorageFooter {
328 account_entry_count: 100,
329 index_block_format: IndexBlockFormat::AddressesThenOffsets,
330 index_block_offset: 1024,
331 owners_block_offset: 1024 + std::mem::size_of::<HotAccountOffset>() as u64,
333 ..TieredStorageFooter::default()
334 };
335
336 {
337 let mut file = TieredWritableFile::new(&path).unwrap();
340 footer.write_footer_block(&mut file).unwrap();
341 }
342
343 let file = OpenOptions::new()
344 .read(true)
345 .create(false)
346 .open(&path)
347 .unwrap();
348 let mmap = unsafe { MmapOptions::new().map(&file).unwrap() };
349 footer
352 .index_block_format
353 .get_account_offset::<HotAccountOffset>(&mmap, &footer, IndexOffset(2))
354 .unwrap();
355 }
356}