solana_accounts_db/tiered_storage/
owners.rs

1use {
2    crate::tiered_storage::{
3        file::TieredWritableFile, footer::TieredStorageFooter, mmap_utils::get_pod,
4        TieredStorageResult,
5    },
6    indexmap::set::IndexSet,
7    memmap2::Mmap,
8    solana_pubkey::Pubkey,
9};
10
11/// The offset to an owner entry in the owners block.
12/// This is used to obtain the address of the account owner.
13///
14/// Note that as its internal type is u32, it means the maximum number of
15/// unique owners in one TieredStorageFile is 2^32.
16#[derive(Clone, Copy, Debug, Eq, PartialEq, PartialOrd)]
17pub struct OwnerOffset(pub u32);
18
19/// Owner block holds a set of unique addresses of account owners,
20/// and an account meta has a owner_offset field for accessing
21/// it's owner address.
22#[repr(u16)]
23#[derive(
24    Clone,
25    Copy,
26    Debug,
27    Default,
28    Eq,
29    Hash,
30    PartialEq,
31    num_enum::IntoPrimitive,
32    num_enum::TryFromPrimitive,
33)]
34pub enum OwnersBlockFormat {
35    /// This format persists OwnerBlock as a consecutive bytes of pubkeys
36    /// without any meta-data.  For each account meta, it has a owner_offset
37    /// field to access its owner's address in the OwnersBlock.
38    #[default]
39    AddressesOnly = 0,
40}
41
42impl OwnersBlockFormat {
43    /// Persists the provided owners' addresses into the specified file.
44    pub fn write_owners_block(
45        &self,
46        file: &mut TieredWritableFile,
47        owners_table: &OwnersTable,
48    ) -> TieredStorageResult<usize> {
49        match self {
50            Self::AddressesOnly => {
51                let mut bytes_written = 0;
52                for address in &owners_table.owners_set {
53                    bytes_written += file.write_pod(address)?;
54                }
55
56                Ok(bytes_written)
57            }
58        }
59    }
60
61    /// Returns the owner address associated with the specified owner_offset
62    /// and footer inside the input mmap.
63    pub fn get_owner_address<'a>(
64        &self,
65        mmap: &'a Mmap,
66        footer: &TieredStorageFooter,
67        owner_offset: OwnerOffset,
68    ) -> TieredStorageResult<&'a Pubkey> {
69        match self {
70            Self::AddressesOnly => {
71                let offset = footer.owners_block_offset as usize
72                    + (std::mem::size_of::<Pubkey>() * owner_offset.0 as usize);
73                let (pubkey, _) = get_pod::<Pubkey>(mmap, offset)?;
74
75                Ok(pubkey)
76            }
77        }
78    }
79}
80
81/// The in-memory representation of owners block for write.
82/// It manages a set of unique addresses of account owners.
83#[derive(Debug, Default)]
84pub struct OwnersTable {
85    owners_set: IndexSet<Pubkey>,
86}
87
88/// OwnersBlock is persisted as a consecutive bytes of pubkeys without any
89/// meta-data.  For each account meta, it has a owner_offset field to
90/// access its owner's address in the OwnersBlock.
91impl OwnersTable {
92    /// Add the specified pubkey as the owner into the OwnersWriterTable
93    /// if the specified pubkey has not existed in the OwnersWriterTable
94    /// yet.  In any case, the function returns its OwnerOffset.
95    pub fn insert(&mut self, pubkey: &Pubkey) -> OwnerOffset {
96        let (offset, _existed) = self.owners_set.insert_full(*pubkey);
97
98        OwnerOffset(offset as u32)
99    }
100
101    /// Returns the number of unique owner addresses in the table.
102    pub fn len(&self) -> usize {
103        self.owners_set.len()
104    }
105
106    /// Returns true if the OwnersTable is empty
107    pub fn is_empty(&self) -> bool {
108        self.len() == 0
109    }
110}
111
112#[cfg(test)]
113mod tests {
114    use {
115        super::*, crate::tiered_storage::file::TieredWritableFile, memmap2::MmapOptions,
116        std::fs::OpenOptions, tempfile::TempDir,
117    };
118
119    #[test]
120    fn test_owners_block() {
121        // Generate a new temp path that is guaranteed to NOT already have a file.
122        let temp_dir = TempDir::new().unwrap();
123        let path = temp_dir.path().join("test_owners_block");
124        const NUM_OWNERS: u32 = 10;
125
126        let addresses: Vec<_> = std::iter::repeat_with(Pubkey::new_unique)
127            .take(NUM_OWNERS as usize)
128            .collect();
129
130        let footer = TieredStorageFooter {
131            // Set owners_block_offset to 0 as we didn't write any account
132            // meta/data nor index block.
133            owners_block_offset: 0,
134            ..TieredStorageFooter::default()
135        };
136
137        {
138            let mut file = TieredWritableFile::new(&path).unwrap();
139
140            let mut owners_table = OwnersTable::default();
141            addresses.iter().for_each(|owner_address| {
142                owners_table.insert(owner_address);
143            });
144            footer
145                .owners_block_format
146                .write_owners_block(&mut file, &owners_table)
147                .unwrap();
148
149            // while the test only focuses on account metas, writing a footer
150            // here is necessary to make it a valid tiered-storage file.
151            footer.write_footer_block(&mut file).unwrap();
152        }
153
154        let file = OpenOptions::new().read(true).open(path).unwrap();
155        let mmap = unsafe { MmapOptions::new().map(&file).unwrap() };
156
157        for (i, address) in addresses.iter().enumerate() {
158            assert_eq!(
159                footer
160                    .owners_block_format
161                    .get_owner_address(&mmap, &footer, OwnerOffset(i as u32))
162                    .unwrap(),
163                address
164            );
165        }
166    }
167
168    #[test]
169    fn test_owners_table() {
170        let mut owners_table = OwnersTable::default();
171        const NUM_OWNERS: usize = 99;
172
173        let addresses: Vec<_> = std::iter::repeat_with(Pubkey::new_unique)
174            .take(NUM_OWNERS)
175            .collect();
176
177        // as we insert sequentially, we expect each entry has same OwnerOffset
178        // as its index inside the Vector.
179        for (i, address) in addresses.iter().enumerate() {
180            assert_eq!(owners_table.insert(address), OwnerOffset(i as u32));
181        }
182
183        let cloned_addresses = addresses.clone();
184
185        // insert again and expect the same OwnerOffset
186        for (i, address) in cloned_addresses.iter().enumerate() {
187            assert_eq!(owners_table.insert(address), OwnerOffset(i as u32));
188        }
189
190        // make sure the size of the resulting owner table is the same
191        // as the input
192        assert_eq!(owners_table.owners_set.len(), addresses.len());
193    }
194}