solana_accounts_db/
account_locks.rs1#[cfg(feature = "dev-context-only-utils")]
2use qualifier_attr::qualifiers;
3use {
4 ahash::{AHashMap, AHashSet},
5 solana_pubkey::Pubkey,
6 solana_sdk::{
7 message::AccountKeys,
8 transaction::{TransactionError, MAX_TX_ACCOUNT_LOCKS},
9 },
10 std::{cell::RefCell, collections::hash_map},
11};
12
13#[derive(Debug, Default)]
14pub struct AccountLocks {
15 write_locks: AHashSet<Pubkey>,
16 readonly_locks: AHashMap<Pubkey, u64>,
17}
18
19impl AccountLocks {
20 pub fn try_lock_accounts<'a>(
25 &mut self,
26 keys: impl Iterator<Item = (&'a Pubkey, bool)> + Clone,
27 ) -> Result<(), TransactionError> {
28 for (key, writable) in keys.clone() {
29 if writable {
30 if !self.can_write_lock(key) {
31 return Err(TransactionError::AccountInUse);
32 }
33 } else if !self.can_read_lock(key) {
34 return Err(TransactionError::AccountInUse);
35 }
36 }
37
38 for (key, writable) in keys {
39 if writable {
40 self.lock_write(key);
41 } else {
42 self.lock_readonly(key);
43 }
44 }
45
46 Ok(())
47 }
48
49 pub fn unlock_accounts<'a>(&mut self, keys: impl Iterator<Item = (&'a Pubkey, bool)>) {
54 for (k, writable) in keys {
55 if writable {
56 self.unlock_write(k);
57 } else {
58 self.unlock_readonly(k);
59 }
60 }
61 }
62
63 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
64 fn is_locked_readonly(&self, key: &Pubkey) -> bool {
65 self.readonly_locks.get(key).is_some_and(|count| *count > 0)
66 }
67
68 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
69 fn is_locked_write(&self, key: &Pubkey) -> bool {
70 self.write_locks.contains(key)
71 }
72
73 fn can_read_lock(&self, key: &Pubkey) -> bool {
74 !self.is_locked_write(key)
76 }
77
78 fn can_write_lock(&self, key: &Pubkey) -> bool {
79 !self.is_locked_readonly(key) && !self.is_locked_write(key)
81 }
82
83 fn lock_readonly(&mut self, key: &Pubkey) {
84 *self.readonly_locks.entry(*key).or_default() += 1;
85 }
86
87 fn lock_write(&mut self, key: &Pubkey) {
88 self.write_locks.insert(*key);
89 }
90
91 fn unlock_readonly(&mut self, key: &Pubkey) {
92 if let hash_map::Entry::Occupied(mut occupied_entry) = self.readonly_locks.entry(*key) {
93 let count = occupied_entry.get_mut();
94 *count -= 1;
95 if *count == 0 {
96 occupied_entry.remove_entry();
97 }
98 } else {
99 debug_assert!(
100 false,
101 "Attempted to remove a read-lock for a key that wasn't read-locked"
102 );
103 }
104 }
105
106 fn unlock_write(&mut self, key: &Pubkey) {
107 let removed = self.write_locks.remove(key);
108 debug_assert!(
109 removed,
110 "Attempted to remove a write-lock for a key that wasn't write-locked"
111 );
112 }
113}
114
115pub fn validate_account_locks(
117 account_keys: AccountKeys,
118 tx_account_lock_limit: usize,
119) -> Result<(), TransactionError> {
120 if account_keys.len() > tx_account_lock_limit {
121 Err(TransactionError::TooManyAccountLocks)
122 } else if has_duplicates(account_keys) {
123 Err(TransactionError::AccountLoadedTwice)
124 } else {
125 Ok(())
126 }
127}
128
129thread_local! {
130 static HAS_DUPLICATES_SET: RefCell<AHashSet<Pubkey>> = RefCell::new(AHashSet::with_capacity(MAX_TX_ACCOUNT_LOCKS));
131}
132
133fn has_duplicates(account_keys: AccountKeys) -> bool {
135 const USE_ACCOUNT_LOCK_SET_SIZE: usize = 32;
139 if account_keys.len() >= USE_ACCOUNT_LOCK_SET_SIZE {
140 HAS_DUPLICATES_SET.with_borrow_mut(|set| {
141 let has_duplicates = account_keys.iter().any(|key| !set.insert(*key));
142 set.clear();
143 has_duplicates
144 })
145 } else {
146 for (idx, key) in account_keys.iter().enumerate() {
147 for jdx in idx + 1..account_keys.len() {
148 if key == &account_keys[jdx] {
149 return true;
150 }
151 }
152 }
153 false
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use {super::*, solana_sdk::message::v0::LoadedAddresses};
160
161 #[test]
162 fn test_account_locks() {
163 let mut account_locks = AccountLocks::default();
164
165 let key1 = Pubkey::new_unique();
166 let key2 = Pubkey::new_unique();
167
168 let result = account_locks.try_lock_accounts([(&key1, true), (&key2, false)].into_iter());
170 assert!(result.is_ok());
171
172 let result = account_locks.try_lock_accounts([(&key1, true)].into_iter());
174 assert_eq!(result, Err(TransactionError::AccountInUse));
175
176 let result = account_locks.try_lock_accounts([(&key2, true)].into_iter());
178 assert_eq!(result, Err(TransactionError::AccountInUse));
179
180 let result = account_locks.try_lock_accounts([(&key1, false)].into_iter());
182 assert_eq!(result, Err(TransactionError::AccountInUse));
183
184 let result = account_locks.try_lock_accounts([(&key2, false)].into_iter());
186 assert!(result.is_ok());
187
188 account_locks.unlock_accounts([(&key1, true), (&key2, false)].into_iter());
190
191 assert!(!account_locks.is_locked_write(&key1));
193 assert!(account_locks.is_locked_readonly(&key2));
194
195 account_locks.unlock_accounts([(&key2, false)].into_iter());
197 assert!(!account_locks.is_locked_readonly(&key2));
198 }
199
200 #[test]
201 fn test_validate_account_locks_valid_no_dynamic() {
202 let static_keys = &[Pubkey::new_unique(), Pubkey::new_unique()];
203 let account_keys = AccountKeys::new(static_keys, None);
204 assert!(validate_account_locks(account_keys, MAX_TX_ACCOUNT_LOCKS).is_ok());
205 }
206
207 #[test]
208 fn test_validate_account_locks_too_many_no_dynamic() {
209 let static_keys = &[Pubkey::new_unique(), Pubkey::new_unique()];
210 let account_keys = AccountKeys::new(static_keys, None);
211 assert_eq!(
212 validate_account_locks(account_keys, 1),
213 Err(TransactionError::TooManyAccountLocks)
214 );
215 }
216
217 #[test]
218 fn test_validate_account_locks_duplicate_no_dynamic() {
219 let duplicate_key = Pubkey::new_unique();
220 let static_keys = &[duplicate_key, Pubkey::new_unique(), duplicate_key];
221 let account_keys = AccountKeys::new(static_keys, None);
222 assert_eq!(
223 validate_account_locks(account_keys, MAX_TX_ACCOUNT_LOCKS),
224 Err(TransactionError::AccountLoadedTwice)
225 );
226 }
227
228 #[test]
229 fn test_validate_account_locks_valid_dynamic() {
230 let static_keys = &[Pubkey::new_unique(), Pubkey::new_unique()];
231 let dynamic_keys = LoadedAddresses {
232 writable: vec![Pubkey::new_unique()],
233 readonly: vec![Pubkey::new_unique()],
234 };
235 let account_keys = AccountKeys::new(static_keys, Some(&dynamic_keys));
236 assert!(validate_account_locks(account_keys, MAX_TX_ACCOUNT_LOCKS).is_ok());
237 }
238
239 #[test]
240 fn test_validate_account_locks_too_many_dynamic() {
241 let static_keys = &[Pubkey::new_unique()];
242 let dynamic_keys = LoadedAddresses {
243 writable: vec![Pubkey::new_unique()],
244 readonly: vec![Pubkey::new_unique()],
245 };
246 let account_keys = AccountKeys::new(static_keys, Some(&dynamic_keys));
247 assert_eq!(
248 validate_account_locks(account_keys, 2),
249 Err(TransactionError::TooManyAccountLocks)
250 );
251 }
252
253 #[test]
254 fn test_validate_account_locks_duplicate_dynamic() {
255 let duplicate_key = Pubkey::new_unique();
256 let static_keys = &[duplicate_key];
257 let dynamic_keys = LoadedAddresses {
258 writable: vec![Pubkey::new_unique()],
259 readonly: vec![duplicate_key],
260 };
261 let account_keys = AccountKeys::new(static_keys, Some(&dynamic_keys));
262 assert_eq!(
263 validate_account_locks(account_keys, MAX_TX_ACCOUNT_LOCKS),
264 Err(TransactionError::AccountLoadedTwice)
265 );
266 }
267
268 #[test]
269 fn test_has_duplicates_small() {
270 let mut keys = (0..16).map(|_| Pubkey::new_unique()).collect::<Vec<_>>();
271 let account_keys = AccountKeys::new(&keys, None);
272 assert!(!has_duplicates(account_keys));
273
274 keys[14] = keys[3]; let account_keys = AccountKeys::new(&keys, None);
276 assert!(has_duplicates(account_keys));
277 }
278
279 #[test]
280 fn test_has_duplicates_large() {
281 let mut keys = (0..64).map(|_| Pubkey::new_unique()).collect::<Vec<_>>();
282 let account_keys = AccountKeys::new(&keys, None);
283 assert!(!has_duplicates(account_keys));
284
285 keys[47] = keys[3]; let account_keys = AccountKeys::new(&keys, None);
287 assert!(has_duplicates(account_keys));
288 }
289}