solana_runtime/
storable_accounts.rs

1//! trait for abstracting underlying storage of pubkey and account pairs to be written
2use solana_sdk::{account::ReadableAccount, clock::Slot, pubkey::Pubkey};
3
4/// abstract access to pubkey, account, slot, target_slot of either:
5/// a. (slot, &[&Pubkey, &ReadableAccount])
6/// b. (slot, &[&Pubkey, &ReadableAccount, Slot]) (we will use this later)
7/// This trait avoids having to allocate redundant data when there is a duplicated slot parameter.
8/// All legacy callers do not have a unique slot per account to store.
9pub trait StorableAccounts<'a, T: ReadableAccount + Sync>: Sync {
10    /// pubkey at 'index'
11    fn pubkey(&self, index: usize) -> &Pubkey;
12    /// account at 'index'
13    fn account(&self, index: usize) -> &T;
14    // current slot for account at 'index'
15    fn slot(&self, index: usize) -> Slot;
16    /// slot that all accounts are to be written to
17    fn target_slot(&self) -> Slot;
18    /// true if no accounts to write
19    fn is_empty(&self) -> bool {
20        self.len() == 0
21    }
22    /// # accounts to write
23    fn len(&self) -> usize;
24    /// are there accounts from multiple slots
25    /// only used for an assert
26    fn contains_multiple_slots(&self) -> bool;
27}
28
29/// accounts that are moving from 'old_slot' to 'target_slot'
30/// since all accounts are from the same old slot, we don't need to create a slice with per-account slot
31/// but, we need slot(_) to return 'old_slot' for all accounts
32/// Created a struct instead of a tuple to make the code easier to read.
33pub struct StorableAccountsMovingSlots<'a, T: ReadableAccount + Sync> {
34    pub accounts: &'a [(&'a Pubkey, &'a T)],
35    /// accounts will be written to this slot
36    pub target_slot: Slot,
37    /// slot where accounts are currently stored
38    pub old_slot: Slot,
39}
40
41impl<'a, T: ReadableAccount + Sync> StorableAccounts<'a, T> for StorableAccountsMovingSlots<'a, T> {
42    fn pubkey(&self, index: usize) -> &Pubkey {
43        self.accounts[index].0
44    }
45    fn account(&self, index: usize) -> &T {
46        self.accounts[index].1
47    }
48    fn slot(&self, _index: usize) -> Slot {
49        // per-index slot is not unique per slot, but it is different than 'target_slot'
50        self.old_slot
51    }
52    fn target_slot(&self) -> Slot {
53        self.target_slot
54    }
55    fn len(&self) -> usize {
56        self.accounts.len()
57    }
58    fn contains_multiple_slots(&self) -> bool {
59        false
60    }
61}
62
63impl<'a, T: ReadableAccount + Sync> StorableAccounts<'a, T> for (Slot, &'a [(&'a Pubkey, &'a T)]) {
64    fn pubkey(&self, index: usize) -> &Pubkey {
65        self.1[index].0
66    }
67    fn account(&self, index: usize) -> &T {
68        self.1[index].1
69    }
70    fn slot(&self, _index: usize) -> Slot {
71        // per-index slot is not unique per slot when per-account slot is not included in the source data
72        self.target_slot()
73    }
74    fn target_slot(&self) -> Slot {
75        self.0
76    }
77    fn len(&self) -> usize {
78        self.1.len()
79    }
80    fn contains_multiple_slots(&self) -> bool {
81        false
82    }
83}
84
85/// this tuple contains slot info PER account
86impl<'a, T: ReadableAccount + Sync> StorableAccounts<'a, T>
87    for (Slot, &'a [(&'a Pubkey, &'a T, Slot)])
88{
89    fn pubkey(&self, index: usize) -> &Pubkey {
90        self.1[index].0
91    }
92    fn account(&self, index: usize) -> &T {
93        self.1[index].1
94    }
95    fn slot(&self, index: usize) -> Slot {
96        // note that this could be different than 'target_slot()' PER account
97        self.1[index].2
98    }
99    fn target_slot(&self) -> Slot {
100        self.0
101    }
102    fn len(&self) -> usize {
103        self.1.len()
104    }
105    fn contains_multiple_slots(&self) -> bool {
106        let len = self.len();
107        if len > 0 {
108            let slot = self.slot(0);
109            // true if any item has a different slot than the first item
110            (1..len).any(|i| slot != self.slot(i))
111        } else {
112            false
113        }
114    }
115}
116
117#[cfg(test)]
118pub mod tests {
119    use {
120        super::*,
121        solana_sdk::account::{AccountSharedData, WritableAccount},
122    };
123
124    fn compare<'a, T: ReadableAccount + Sync + PartialEq + std::fmt::Debug>(
125        a: &impl StorableAccounts<'a, T>,
126        b: &impl StorableAccounts<'a, T>,
127    ) {
128        assert_eq!(a.target_slot(), b.target_slot());
129        assert_eq!(a.len(), b.len());
130        assert_eq!(a.is_empty(), b.is_empty());
131        (0..a.len()).into_iter().for_each(|i| {
132            assert_eq!(a.pubkey(i), b.pubkey(i));
133            assert_eq!(a.account(i), b.account(i));
134        })
135    }
136
137    #[test]
138    fn test_contains_multiple_slots() {
139        let pk = Pubkey::from([1; 32]);
140        let account = AccountSharedData::create(1, Vec::default(), Pubkey::default(), false, 0);
141        let slot = 0;
142        let test3 = (
143            slot,
144            &vec![(&pk, &account, slot), (&pk, &account, slot)][..],
145        );
146        assert!(!(&test3).contains_multiple_slots());
147        let test3 = (
148            slot,
149            &vec![(&pk, &account, slot), (&pk, &account, slot + 1)][..],
150        );
151        assert!(test3.contains_multiple_slots());
152    }
153
154    #[test]
155    fn test_storable_accounts() {
156        let max_slots = 3_u64;
157        for target_slot in 0..max_slots {
158            for entries in 0..2 {
159                for starting_slot in 0..max_slots {
160                    let mut raw = Vec::new();
161                    for entry in 0..entries {
162                        let pk = Pubkey::from([entry; 32]);
163                        raw.push((
164                            pk,
165                            AccountSharedData::create(
166                                ((entry as u64) * starting_slot) as u64,
167                                Vec::default(),
168                                Pubkey::default(),
169                                false,
170                                0,
171                            ),
172                            starting_slot % max_slots,
173                        ));
174                    }
175                    let mut two = Vec::new();
176                    let mut three = Vec::new();
177                    raw.iter().for_each(|raw| {
178                        two.push((&raw.0, &raw.1)); // 2 item tuple
179                        three.push((&raw.0, &raw.1, raw.2)); // 3 item tuple, including slot
180                    });
181                    let test2 = (target_slot, &two[..]);
182                    let test3 = (target_slot, &three[..]);
183                    let old_slot = starting_slot;
184                    let test_moving_slots = StorableAccountsMovingSlots {
185                        accounts: &two[..],
186                        target_slot,
187                        old_slot,
188                    };
189                    compare(&test2, &test3);
190                    compare(&test2, &test_moving_slots);
191                    for (i, raw) in raw.iter().enumerate() {
192                        assert_eq!(raw.0, *test3.pubkey(i));
193                        assert_eq!(raw.1, *test3.account(i));
194                        assert_eq!(raw.2, test3.slot(i));
195                        assert_eq!(target_slot, test2.slot(i));
196                        assert_eq!(old_slot, test_moving_slots.slot(i));
197                    }
198                    assert_eq!(target_slot, test3.target_slot());
199                    assert!(!test2.contains_multiple_slots());
200                    assert!(!test_moving_slots.contains_multiple_slots());
201                    assert_eq!(test3.contains_multiple_slots(), entries > 1);
202                }
203            }
204        }
205    }
206}