solana_accounts_db/
account_locks.rs1#[cfg(feature = "dev-context-only-utils")]
2use qualifier_attr::qualifiers;
3use {
4 ahash::{AHashMap, AHashSet},
5 solana_sdk::{
6 message::AccountKeys,
7 pubkey::Pubkey,
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
66 .get(key)
67 .map_or(false, |count| *count > 0)
68 }
69
70 #[cfg_attr(feature = "dev-context-only-utils", qualifiers(pub))]
71 fn is_locked_write(&self, key: &Pubkey) -> bool {
72 self.write_locks.contains(key)
73 }
74
75 fn can_read_lock(&self, key: &Pubkey) -> bool {
76 !self.is_locked_write(key)
78 }
79
80 fn can_write_lock(&self, key: &Pubkey) -> bool {
81 !self.is_locked_readonly(key) && !self.is_locked_write(key)
83 }
84
85 fn lock_readonly(&mut self, key: &Pubkey) {
86 *self.readonly_locks.entry(*key).or_default() += 1;
87 }
88
89 fn lock_write(&mut self, key: &Pubkey) {
90 self.write_locks.insert(*key);
91 }
92
93 fn unlock_readonly(&mut self, key: &Pubkey) {
94 if let hash_map::Entry::Occupied(mut occupied_entry) = self.readonly_locks.entry(*key) {
95 let count = occupied_entry.get_mut();
96 *count -= 1;
97 if *count == 0 {
98 occupied_entry.remove_entry();
99 }
100 } else {
101 debug_assert!(
102 false,
103 "Attempted to remove a read-lock for a key that wasn't read-locked"
104 );
105 }
106 }
107
108 fn unlock_write(&mut self, key: &Pubkey) {
109 let removed = self.write_locks.remove(key);
110 debug_assert!(
111 removed,
112 "Attempted to remove a write-lock for a key that wasn't write-locked"
113 );
114 }
115}
116
117pub fn validate_account_locks(
119 account_keys: AccountKeys,
120 tx_account_lock_limit: usize,
121) -> Result<(), TransactionError> {
122 if account_keys.len() > tx_account_lock_limit {
123 Err(TransactionError::TooManyAccountLocks)
124 } else if has_duplicates(account_keys) {
125 Err(TransactionError::AccountLoadedTwice)
126 } else {
127 Ok(())
128 }
129}
130
131thread_local! {
132 static HAS_DUPLICATES_SET: RefCell<AHashSet<Pubkey>> = RefCell::new(AHashSet::with_capacity(MAX_TX_ACCOUNT_LOCKS));
133}
134
135fn has_duplicates(account_keys: AccountKeys) -> bool {
137 const USE_ACCOUNT_LOCK_SET_SIZE: usize = 32;
141 if account_keys.len() >= USE_ACCOUNT_LOCK_SET_SIZE {
142 HAS_DUPLICATES_SET.with_borrow_mut(|set| {
143 let has_duplicates = account_keys.iter().any(|key| !set.insert(*key));
144 set.clear();
145 has_duplicates
146 })
147 } else {
148 for (idx, key) in account_keys.iter().enumerate() {
149 for jdx in idx + 1..account_keys.len() {
150 if key == &account_keys[jdx] {
151 return true;
152 }
153 }
154 }
155 false
156 }
157}
158
159#[cfg(test)]
160mod tests {
161 use {super::*, solana_sdk::message::v0::LoadedAddresses};
162
163 #[test]
164 fn test_account_locks() {
165 let mut account_locks = AccountLocks::default();
166
167 let key1 = Pubkey::new_unique();
168 let key2 = Pubkey::new_unique();
169
170 let result = account_locks.try_lock_accounts([(&key1, true), (&key2, false)].into_iter());
172 assert!(result.is_ok());
173
174 let result = account_locks.try_lock_accounts([(&key1, true)].into_iter());
176 assert_eq!(result, Err(TransactionError::AccountInUse));
177
178 let result = account_locks.try_lock_accounts([(&key2, true)].into_iter());
180 assert_eq!(result, Err(TransactionError::AccountInUse));
181
182 let result = account_locks.try_lock_accounts([(&key1, false)].into_iter());
184 assert_eq!(result, Err(TransactionError::AccountInUse));
185
186 let result = account_locks.try_lock_accounts([(&key2, false)].into_iter());
188 assert!(result.is_ok());
189
190 account_locks.unlock_accounts([(&key1, true), (&key2, false)].into_iter());
192
193 assert!(!account_locks.is_locked_write(&key1));
195 assert!(account_locks.is_locked_readonly(&key2));
196
197 account_locks.unlock_accounts([(&key2, false)].into_iter());
199 assert!(!account_locks.is_locked_readonly(&key2));
200 }
201
202 #[test]
203 fn test_validate_account_locks_valid_no_dynamic() {
204 let static_keys = &[Pubkey::new_unique(), Pubkey::new_unique()];
205 let account_keys = AccountKeys::new(static_keys, None);
206 assert!(validate_account_locks(account_keys, MAX_TX_ACCOUNT_LOCKS).is_ok());
207 }
208
209 #[test]
210 fn test_validate_account_locks_too_many_no_dynamic() {
211 let static_keys = &[Pubkey::new_unique(), Pubkey::new_unique()];
212 let account_keys = AccountKeys::new(static_keys, None);
213 assert_eq!(
214 validate_account_locks(account_keys, 1),
215 Err(TransactionError::TooManyAccountLocks)
216 );
217 }
218
219 #[test]
220 fn test_validate_account_locks_duplicate_no_dynamic() {
221 let duplicate_key = Pubkey::new_unique();
222 let static_keys = &[duplicate_key, Pubkey::new_unique(), duplicate_key];
223 let account_keys = AccountKeys::new(static_keys, None);
224 assert_eq!(
225 validate_account_locks(account_keys, MAX_TX_ACCOUNT_LOCKS),
226 Err(TransactionError::AccountLoadedTwice)
227 );
228 }
229
230 #[test]
231 fn test_validate_account_locks_valid_dynamic() {
232 let static_keys = &[Pubkey::new_unique(), Pubkey::new_unique()];
233 let dynamic_keys = LoadedAddresses {
234 writable: vec![Pubkey::new_unique()],
235 readonly: vec![Pubkey::new_unique()],
236 };
237 let account_keys = AccountKeys::new(static_keys, Some(&dynamic_keys));
238 assert!(validate_account_locks(account_keys, MAX_TX_ACCOUNT_LOCKS).is_ok());
239 }
240
241 #[test]
242 fn test_validate_account_locks_too_many_dynamic() {
243 let static_keys = &[Pubkey::new_unique()];
244 let dynamic_keys = LoadedAddresses {
245 writable: vec![Pubkey::new_unique()],
246 readonly: vec![Pubkey::new_unique()],
247 };
248 let account_keys = AccountKeys::new(static_keys, Some(&dynamic_keys));
249 assert_eq!(
250 validate_account_locks(account_keys, 2),
251 Err(TransactionError::TooManyAccountLocks)
252 );
253 }
254
255 #[test]
256 fn test_validate_account_locks_duplicate_dynamic() {
257 let duplicate_key = Pubkey::new_unique();
258 let static_keys = &[duplicate_key];
259 let dynamic_keys = LoadedAddresses {
260 writable: vec![Pubkey::new_unique()],
261 readonly: vec![duplicate_key],
262 };
263 let account_keys = AccountKeys::new(static_keys, Some(&dynamic_keys));
264 assert_eq!(
265 validate_account_locks(account_keys, MAX_TX_ACCOUNT_LOCKS),
266 Err(TransactionError::AccountLoadedTwice)
267 );
268 }
269
270 #[test]
271 fn test_has_duplicates_small() {
272 let mut keys = (0..16).map(|_| Pubkey::new_unique()).collect::<Vec<_>>();
273 let account_keys = AccountKeys::new(&keys, None);
274 assert!(!has_duplicates(account_keys));
275
276 keys[14] = keys[3]; let account_keys = AccountKeys::new(&keys, None);
278 assert!(has_duplicates(account_keys));
279 }
280
281 #[test]
282 fn test_has_duplicates_large() {
283 let mut keys = (0..64).map(|_| Pubkey::new_unique()).collect::<Vec<_>>();
284 let account_keys = AccountKeys::new(&keys, None);
285 assert!(!has_duplicates(account_keys));
286
287 keys[47] = keys[3]; let account_keys = AccountKeys::new(&keys, None);
289 assert!(has_duplicates(account_keys));
290 }
291}