1use crate::{
65 offchain::storage::{MutateStorageError, StorageRetrievalError, StorageValueRef},
66 traits::BlockNumberProvider,
67};
68use codec::{Codec, Decode, Encode};
69use core::fmt;
70use sp_core::offchain::{Duration, Timestamp};
71use sp_io::offchain;
72
73const STORAGE_LOCK_DEFAULT_EXPIRY_DURATION: Duration = Duration::from_millis(20_000);
75
76const STORAGE_LOCK_DEFAULT_EXPIRY_BLOCKS: u32 = 4;
78
79const STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MIN: Duration = Duration::from_millis(10);
81const STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX: Duration = Duration::from_millis(100);
82
83pub trait Lockable: Sized {
88 type Deadline: Sized + Codec + Clone;
90
91 fn deadline(&self) -> Self::Deadline;
94
95 fn has_expired(deadline: &Self::Deadline) -> bool;
98
99 fn snooze(_deadline: &Self::Deadline) {
104 sp_io::offchain::sleep_until(
105 offchain::timestamp().add(STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX),
106 );
107 }
108}
109
110#[derive(Encode, Decode)]
112pub struct Time {
113 expiration_duration: Duration,
116}
117
118impl Default for Time {
119 fn default() -> Self {
120 Self { expiration_duration: STORAGE_LOCK_DEFAULT_EXPIRY_DURATION }
121 }
122}
123
124impl Lockable for Time {
125 type Deadline = Timestamp;
126
127 fn deadline(&self) -> Self::Deadline {
128 offchain::timestamp().add(self.expiration_duration)
129 }
130
131 fn has_expired(deadline: &Self::Deadline) -> bool {
132 offchain::timestamp() > *deadline
133 }
134
135 fn snooze(deadline: &Self::Deadline) {
136 let now = offchain::timestamp();
137 let remainder: Duration = now.diff(deadline);
138 let snooze = remainder.clamp(
141 STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MIN,
142 STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX,
143 );
144 sp_io::offchain::sleep_until(now.add(snooze));
145 }
146}
147
148#[derive(Encode, Decode, Eq, PartialEq)]
150pub struct BlockAndTimeDeadline<B: BlockNumberProvider> {
151 pub block_number: <B as BlockNumberProvider>::BlockNumber,
153 pub timestamp: Timestamp,
155}
156
157impl<B: BlockNumberProvider> Clone for BlockAndTimeDeadline<B> {
158 fn clone(&self) -> Self {
159 Self { block_number: self.block_number, timestamp: self.timestamp }
160 }
161}
162
163impl<B: BlockNumberProvider> Default for BlockAndTimeDeadline<B> {
164 fn default() -> Self {
166 Self {
167 block_number: B::current_block_number() + STORAGE_LOCK_DEFAULT_EXPIRY_BLOCKS.into(),
168 timestamp: offchain::timestamp().add(STORAGE_LOCK_DEFAULT_EXPIRY_DURATION),
169 }
170 }
171}
172
173impl<B: BlockNumberProvider> fmt::Debug for BlockAndTimeDeadline<B>
174where
175 <B as BlockNumberProvider>::BlockNumber: fmt::Debug,
176{
177 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
178 f.debug_struct("BlockAndTimeDeadline")
179 .field("block_number", &self.block_number)
180 .field("timestamp", &self.timestamp)
181 .finish()
182 }
183}
184
185pub struct BlockAndTime<B: BlockNumberProvider> {
190 expiration_block_number_offset: u32,
193 expiration_duration: Duration,
196
197 _phantom: core::marker::PhantomData<B>,
198}
199
200impl<B: BlockNumberProvider> Default for BlockAndTime<B> {
201 fn default() -> Self {
202 Self {
203 expiration_block_number_offset: STORAGE_LOCK_DEFAULT_EXPIRY_BLOCKS,
204 expiration_duration: STORAGE_LOCK_DEFAULT_EXPIRY_DURATION,
205 _phantom: core::marker::PhantomData::<B>,
206 }
207 }
208}
209
210impl<B: BlockNumberProvider> Clone for BlockAndTime<B> {
212 fn clone(&self) -> Self {
213 Self {
214 expiration_block_number_offset: self.expiration_block_number_offset,
215 expiration_duration: self.expiration_duration,
216 _phantom: core::marker::PhantomData::<B>,
217 }
218 }
219}
220
221impl<B: BlockNumberProvider> Lockable for BlockAndTime<B> {
222 type Deadline = BlockAndTimeDeadline<B>;
223
224 fn deadline(&self) -> Self::Deadline {
225 let block_number = <B as BlockNumberProvider>::current_block_number() +
226 self.expiration_block_number_offset.into();
227 BlockAndTimeDeadline {
228 timestamp: offchain::timestamp().add(self.expiration_duration),
229 block_number,
230 }
231 }
232
233 fn has_expired(deadline: &Self::Deadline) -> bool {
234 offchain::timestamp() > deadline.timestamp &&
235 <B as BlockNumberProvider>::current_block_number() > deadline.block_number
236 }
237
238 fn snooze(deadline: &Self::Deadline) {
239 let now = offchain::timestamp();
240 let remainder: Duration = now.diff(&(deadline.timestamp));
241 let snooze = remainder.clamp(
242 STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MIN,
243 STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX,
244 );
245 sp_io::offchain::sleep_until(now.add(snooze));
246 }
247}
248
249pub struct StorageLock<'a, L = Time> {
255 value_ref: StorageValueRef<'a>,
257 lockable: L,
258}
259
260impl<'a, L: Lockable + Default> StorageLock<'a, L> {
261 pub fn new(key: &'a [u8]) -> Self {
263 Self::with_lockable(key, Default::default())
264 }
265}
266
267impl<'a, L: Lockable> StorageLock<'a, L> {
268 pub fn with_lockable(key: &'a [u8], lockable: L) -> Self {
270 Self { value_ref: StorageValueRef::<'a>::persistent(key), lockable }
271 }
272
273 fn extend_active_lock(&mut self) -> Result<<L as Lockable>::Deadline, ()> {
275 let res = self.value_ref.mutate(
276 |s: Result<Option<L::Deadline>, StorageRetrievalError>| -> Result<<L as Lockable>::Deadline, ()> {
277 match s {
278 Ok(Some(deadline)) if !<L as Lockable>::has_expired(&deadline) =>
280 Ok(self.lockable.deadline()),
281 _ => Err(()),
283 }
284 });
285 match res {
286 Ok(deadline) => Ok(deadline),
287 Err(MutateStorageError::ConcurrentModification(_)) => Err(()),
288 Err(MutateStorageError::ValueFunctionFailed(e)) => Err(e),
289 }
290 }
291
292 fn try_lock_inner(
294 &mut self,
295 new_deadline: L::Deadline,
296 ) -> Result<(), <L as Lockable>::Deadline> {
297 let res = self.value_ref.mutate(
298 |s: Result<Option<L::Deadline>, StorageRetrievalError>|
299 -> Result<<L as Lockable>::Deadline, <L as Lockable>::Deadline> {
300 match s {
301 Ok(None) => Ok(new_deadline),
303 Err(_) => Ok(new_deadline),
305 Ok(Some(deadline)) if <L as Lockable>::has_expired(&deadline) =>
307 Ok(new_deadline),
308 Ok(Some(deadline)) => Err(deadline),
310 }
311 },
312 );
313 match res {
314 Ok(_) => Ok(()),
315 Err(MutateStorageError::ConcurrentModification(deadline)) => Err(deadline),
316 Err(MutateStorageError::ValueFunctionFailed(e)) => Err(e),
317 }
318 }
319
320 pub fn try_lock(&mut self) -> Result<StorageLockGuard<'a, '_, L>, <L as Lockable>::Deadline> {
326 self.try_lock_inner(self.lockable.deadline())?;
327 Ok(StorageLockGuard::<'a, '_> { lock: Some(self) })
328 }
329
330 pub fn lock(&mut self) -> StorageLockGuard<'a, '_, L> {
337 while let Err(deadline) = self.try_lock_inner(self.lockable.deadline()) {
338 L::snooze(&deadline);
339 }
340 StorageLockGuard::<'a, '_, L> { lock: Some(self) }
341 }
342
343 fn unlock(&mut self) {
345 self.value_ref.clear();
346 }
347}
348
349pub struct StorageLockGuard<'a, 'b, L: Lockable> {
351 lock: Option<&'b mut StorageLock<'a, L>>,
352}
353
354impl<'a, 'b, L: Lockable> StorageLockGuard<'a, 'b, L> {
355 pub fn forget(mut self) {
363 let _ = self.lock.take();
364 }
365
366 pub fn extend_lock(&mut self) -> Result<<L as Lockable>::Deadline, ()> {
372 if let Some(ref mut lock) = self.lock {
373 lock.extend_active_lock()
374 } else {
375 Err(())
376 }
377 }
378}
379
380impl<'a, 'b, L: Lockable> Drop for StorageLockGuard<'a, 'b, L> {
381 fn drop(&mut self) {
382 if let Some(lock) = self.lock.take() {
383 lock.unlock();
384 }
385 }
386}
387
388impl<'a> StorageLock<'a, Time> {
389 pub fn with_deadline(key: &'a [u8], expiration_duration: Duration) -> Self {
392 Self {
393 value_ref: StorageValueRef::<'a>::persistent(key),
394 lockable: Time { expiration_duration },
395 }
396 }
397}
398
399impl<'a, B> StorageLock<'a, BlockAndTime<B>>
400where
401 B: BlockNumberProvider,
402{
403 pub fn with_block_and_time_deadline(
406 key: &'a [u8],
407 expiration_block_number_offset: u32,
408 expiration_duration: Duration,
409 ) -> Self {
410 Self {
411 value_ref: StorageValueRef::<'a>::persistent(key),
412 lockable: BlockAndTime::<B> {
413 expiration_block_number_offset,
414 expiration_duration,
415 _phantom: core::marker::PhantomData,
416 },
417 }
418 }
419
420 pub fn with_block_deadline(key: &'a [u8], expiration_block_number_offset: u32) -> Self {
423 Self {
424 value_ref: StorageValueRef::<'a>::persistent(key),
425 lockable: BlockAndTime::<B> {
426 expiration_block_number_offset,
427 expiration_duration: STORAGE_LOCK_DEFAULT_EXPIRY_DURATION,
428 _phantom: core::marker::PhantomData,
429 },
430 }
431 }
432}
433
434#[cfg(test)]
435mod tests {
436 use super::*;
437 use sp_core::offchain::{testing, OffchainDbExt, OffchainWorkerExt};
438 use sp_io::TestExternalities;
439
440 const VAL_1: u32 = 0u32;
441 const VAL_2: u32 = 0xFFFF_FFFFu32;
442
443 #[test]
444 fn storage_lock_write_unlock_lock_read_unlock() {
445 let (offchain, state) = testing::TestOffchainExt::new();
446 let mut t = TestExternalities::default();
447 t.register_extension(OffchainDbExt::new(offchain.clone()));
448 t.register_extension(OffchainWorkerExt::new(offchain));
449
450 t.execute_with(|| {
451 let mut lock = StorageLock::<'_, Time>::new(b"lock_1");
452
453 let val = StorageValueRef::persistent(b"protected_value");
454
455 {
456 let _guard = lock.lock();
457
458 val.set(&VAL_1);
459
460 assert_eq!(val.get::<u32>(), Ok(Some(VAL_1)));
461 }
462
463 {
464 let _guard = lock.lock();
465 val.set(&VAL_2);
466
467 assert_eq!(val.get::<u32>(), Ok(Some(VAL_2)));
468 }
469 });
470 assert_eq!(state.read().persistent_storage.get(b"lock_1"), None);
472 }
473
474 #[test]
475 fn storage_lock_and_forget() {
476 let (offchain, state) = testing::TestOffchainExt::new();
477 let mut t = TestExternalities::default();
478 t.register_extension(OffchainDbExt::new(offchain.clone()));
479 t.register_extension(OffchainWorkerExt::new(offchain));
480
481 t.execute_with(|| {
482 let mut lock = StorageLock::<'_, Time>::new(b"lock_2");
483
484 let val = StorageValueRef::persistent(b"protected_value");
485
486 let guard = lock.lock();
487
488 val.set(&VAL_1);
489
490 assert_eq!(val.get::<u32>(), Ok(Some(VAL_1)));
491
492 guard.forget();
493 });
494 let opt = state.read().persistent_storage.get(b"lock_2");
496 assert!(opt.is_some());
497 }
498
499 #[test]
500 fn storage_lock_and_let_expire_and_lock_again() {
501 let (offchain, state) = testing::TestOffchainExt::new();
502 let mut t = TestExternalities::default();
503 t.register_extension(OffchainDbExt::new(offchain.clone()));
504 t.register_extension(OffchainWorkerExt::new(offchain));
505
506 t.execute_with(|| {
507 let sleep_until = offchain::timestamp().add(Duration::from_millis(500));
508 let lock_expiration = Duration::from_millis(200);
509
510 let mut lock = StorageLock::<'_, Time>::with_deadline(b"lock_3", lock_expiration);
511
512 {
513 let guard = lock.lock();
514 guard.forget();
515 }
516
517 offchain::sleep_until(sleep_until);
519
520 let mut lock = StorageLock::<'_, Time>::new(b"lock_3");
521 let res = lock.try_lock();
522 assert!(res.is_ok());
523 let guard = res.unwrap();
524 guard.forget();
525 });
526
527 let opt = state.read().persistent_storage.get(b"lock_3");
529 assert!(opt.is_some());
530 }
531
532 #[test]
533 fn extend_active_lock() {
534 let (offchain, state) = testing::TestOffchainExt::new();
535 let mut t = TestExternalities::default();
536 t.register_extension(OffchainDbExt::new(offchain.clone()));
537 t.register_extension(OffchainWorkerExt::new(offchain));
538
539 t.execute_with(|| {
540 let lock_expiration = Duration::from_millis(300);
541
542 let mut lock = StorageLock::<'_, Time>::with_deadline(b"lock_4", lock_expiration);
543 let mut guard = lock.lock();
544
545 offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(200)));
547
548 assert_eq!(guard.extend_lock().is_ok(), true);
550
551 offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(200)));
553
554 let mut lock = StorageLock::<'_, Time>::with_deadline(b"lock_4", lock_expiration);
556 let res = lock.try_lock();
557 assert_eq!(res.is_ok(), false);
558
559 offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(200)));
561
562 assert_eq!(guard.extend_lock().is_ok(), false);
564 guard.forget();
565
566 let mut lock = StorageLock::<'_, Time>::with_deadline(b"lock_4", lock_expiration);
568 let res = lock.try_lock();
569 assert!(res.is_ok());
570 let guard = res.unwrap();
571
572 guard.forget();
573 });
574
575 let opt = state.read().persistent_storage.get(b"lock_4");
577 assert_eq!(opt.unwrap(), vec![132_u8, 3u8, 0, 0, 0, 0, 0, 0]); }
579}