1use crate::offchain::storage::StorageValueRef;
65use crate::traits::AtLeast32BitUnsigned;
66use codec::{Codec, Decode, Encode};
67use tet_core::offchain::{Duration, Timestamp};
68use tet_io::offchain;
69
70const STORAGE_LOCK_DEFAULT_EXPIRY_DURATION: Duration = Duration::from_millis(20_000);
72
73const STORAGE_LOCK_DEFAULT_EXPIRY_BLOCKS: u32 = 4;
75
76const STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MIN: Duration = Duration::from_millis(100);
78const STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX: Duration = Duration::from_millis(10);
79
80pub trait Lockable: Sized {
85 type Deadline: Sized + Codec + Clone;
87
88 fn deadline(&self) -> Self::Deadline;
91
92 fn has_expired(deadline: &Self::Deadline) -> bool;
95
96 fn snooze(_deadline: &Self::Deadline) {
101 tet_io::offchain::sleep_until(
102 offchain::timestamp().add(STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX),
103 );
104 }
105}
106
107#[derive(Encode, Decode)]
109pub struct Time {
110 expiration_duration: Duration,
113}
114
115impl Default for Time {
116 fn default() -> Self {
117 Self {
118 expiration_duration: STORAGE_LOCK_DEFAULT_EXPIRY_DURATION,
119 }
120 }
121}
122
123impl Lockable for Time {
124 type Deadline = Timestamp;
125
126 fn deadline(&self) -> Self::Deadline {
127 offchain::timestamp().add(self.expiration_duration)
128 }
129
130 fn has_expired(deadline: &Self::Deadline) -> bool {
131 offchain::timestamp() > *deadline
132 }
133
134 fn snooze(deadline: &Self::Deadline) {
135 let now = offchain::timestamp();
136 let remainder: Duration = now.diff(&deadline);
137 use core::cmp::{max, min};
140 let snooze = max(
141 min(remainder, STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX),
142 STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MIN,
143 );
144 tet_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 {
160 block_number: self.block_number.clone(),
161 timestamp: self.timestamp.clone(),
162 }
163 }
164}
165
166impl<B: BlockNumberProvider> Default for BlockAndTimeDeadline<B> {
167 fn default() -> Self {
169 Self {
170 block_number: B::current_block_number() + STORAGE_LOCK_DEFAULT_EXPIRY_BLOCKS.into(),
171 timestamp: offchain::timestamp().add(STORAGE_LOCK_DEFAULT_EXPIRY_DURATION),
172 }
173 }
174}
175
176pub struct BlockAndTime<B: BlockNumberProvider> {
181 expiration_block_number_offset: u32,
184 expiration_duration: Duration,
187
188 _phantom: core::marker::PhantomData<B>,
189}
190
191impl<B: BlockNumberProvider> Default for BlockAndTime<B> {
192 fn default() -> Self {
193 Self {
194 expiration_block_number_offset: STORAGE_LOCK_DEFAULT_EXPIRY_BLOCKS,
195 expiration_duration: STORAGE_LOCK_DEFAULT_EXPIRY_DURATION,
196 _phantom: core::marker::PhantomData::<B>,
197 }
198 }
199}
200
201impl<B: BlockNumberProvider> Clone for BlockAndTime<B> {
203 fn clone(&self) -> Self {
204 Self {
205 expiration_block_number_offset: self.expiration_block_number_offset.clone(),
206 expiration_duration: self.expiration_duration,
207 _phantom: core::marker::PhantomData::<B>,
208 }
209 }
210}
211
212impl<B: BlockNumberProvider> Lockable for BlockAndTime<B> {
213 type Deadline = BlockAndTimeDeadline<B>;
214
215 fn deadline(&self) -> Self::Deadline {
216 let block_number = <B as BlockNumberProvider>::current_block_number()
217 + self.expiration_block_number_offset.into();
218 BlockAndTimeDeadline {
219 timestamp: offchain::timestamp().add(self.expiration_duration),
220 block_number,
221 }
222 }
223
224 fn has_expired(deadline: &Self::Deadline) -> bool {
225 offchain::timestamp() > deadline.timestamp
226 && <B as BlockNumberProvider>::current_block_number() > deadline.block_number
227 }
228
229 fn snooze(deadline: &Self::Deadline) {
230 let now = offchain::timestamp();
231 let remainder: Duration = now.diff(&(deadline.timestamp));
232 use core::cmp::{max, min};
233 let snooze = max(
234 min(remainder, STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MAX),
235 STORAGE_LOCK_PER_CHECK_ITERATION_SNOOZE_MIN,
236 );
237 tet_io::offchain::sleep_until(now.add(snooze));
238 }
239}
240
241pub struct StorageLock<'a, L = Time> {
247 value_ref: StorageValueRef<'a>,
249 lockable: L,
250}
251
252impl<'a, L: Lockable + Default> StorageLock<'a, L> {
253 pub fn new(key: &'a [u8]) -> Self {
255 Self::with_lockable(key, Default::default())
256 }
257}
258
259impl<'a, L: Lockable> StorageLock<'a, L> {
260 pub fn with_lockable(key: &'a [u8], lockable: L) -> Self {
262 Self {
263 value_ref: StorageValueRef::<'a>::persistent(key),
264 lockable,
265 }
266 }
267
268 fn extend_active_lock(&mut self) -> Result<<L as Lockable>::Deadline, ()> {
270 let res = self.value_ref.mutate(|s: Option<Option<L::Deadline>>| -> Result<<L as Lockable>::Deadline, ()> {
271 match s {
272 Some(Some(deadline)) if !<L as Lockable>::has_expired(&deadline) =>
274 Ok(self.lockable.deadline()),
275 _ => Err(()),
277 }
278 });
279 match res {
280 Ok(Ok(deadline)) => Ok(deadline),
281 Ok(Err(_)) => Err(()),
282 Err(e) => Err(e),
283 }
284 }
285
286 fn try_lock_inner(
288 &mut self,
289 new_deadline: L::Deadline,
290 ) -> Result<(), <L as Lockable>::Deadline> {
291 let res = self.value_ref.mutate(
292 |s: Option<Option<L::Deadline>>|
293 -> Result<<L as Lockable>::Deadline, <L as Lockable>::Deadline> {
294 match s {
295 None => Ok(new_deadline),
297 Some(None) => Ok(new_deadline),
299 Some(Some(deadline)) if <L as Lockable>::has_expired(&deadline) =>
301 Ok(new_deadline),
302 Some(Some(deadline)) => Err(deadline),
304 }
305 },
306 );
307 match res {
308 Ok(Ok(_)) => Ok(()),
309 Ok(Err(deadline)) => Err(deadline),
310 Err(e) => Err(e),
311 }
312 }
313
314 pub fn try_lock(&mut self) -> Result<StorageLockGuard<'a, '_, L>, <L as Lockable>::Deadline> {
320 self.try_lock_inner(self.lockable.deadline())?;
321 Ok(StorageLockGuard::<'a, '_> { lock: Some(self) })
322 }
323
324 pub fn lock(&mut self) -> StorageLockGuard<'a, '_, L> {
331 while let Err(deadline) = self.try_lock_inner(self.lockable.deadline()) {
332 L::snooze(&deadline);
333 }
334 StorageLockGuard::<'a, '_, L> { lock: Some(self) }
335 }
336
337 fn unlock(&mut self) {
339 self.value_ref.clear();
340 }
341}
342
343pub struct StorageLockGuard<'a, 'b, L: Lockable> {
345 lock: Option<&'b mut StorageLock<'a, L>>,
346}
347
348impl<'a, 'b, L: Lockable> StorageLockGuard<'a, 'b, L> {
349 pub fn forget(mut self) {
357 let _ = self.lock.take();
358 }
359
360 pub fn extend_lock(&mut self) -> Result<<L as Lockable>::Deadline, ()> {
366 if let Some(ref mut lock) = self.lock {
367 lock.extend_active_lock()
368 } else {
369 Err(())
370 }
371 }
372}
373
374impl<'a, 'b, L: Lockable> Drop for StorageLockGuard<'a, 'b, L> {
375 fn drop(&mut self) {
376 if let Some(lock) = self.lock.take() {
377 lock.unlock();
378 }
379 }
380}
381
382impl<'a> StorageLock<'a, Time> {
383 pub fn with_deadline(key: &'a [u8], expiration_duration: Duration) -> Self {
386 Self {
387 value_ref: StorageValueRef::<'a>::persistent(key),
388 lockable: Time {
389 expiration_duration: expiration_duration,
390 },
391 }
392 }
393}
394
395impl<'a, B> StorageLock<'a, BlockAndTime<B>>
396where
397 B: BlockNumberProvider,
398{
399 pub fn with_block_and_time_deadline(
402 key: &'a [u8],
403 expiration_block_number_offset: u32,
404 expiration_duration: Duration,
405 ) -> Self {
406 Self {
407 value_ref: StorageValueRef::<'a>::persistent(key),
408 lockable: BlockAndTime::<B> {
409 expiration_block_number_offset,
410 expiration_duration,
411 _phantom: core::marker::PhantomData,
412 },
413 }
414 }
415
416 pub fn with_block_deadline(key: &'a [u8], expiration_block_number_offset: u32) -> Self {
419 Self {
420 value_ref: StorageValueRef::<'a>::persistent(key),
421 lockable: BlockAndTime::<B> {
422 expiration_block_number_offset,
423 expiration_duration: STORAGE_LOCK_DEFAULT_EXPIRY_DURATION,
424 _phantom: core::marker::PhantomData,
425 },
426 }
427 }
428}
429
430pub trait BlockNumberProvider {
433 type BlockNumber: Codec + Clone + Ord + Eq + AtLeast32BitUnsigned;
435 fn current_block_number() -> Self::BlockNumber;
451}
452
453#[cfg(test)]
454mod tests {
455 use super::*;
456 use tet_core::offchain::{testing, OffchainExt};
457 use tet_io::TestExternalities;
458
459 const VAL_1: u32 = 0u32;
460 const VAL_2: u32 = 0xFFFF_FFFFu32;
461
462 #[test]
463 fn storage_lock_write_unlock_lock_read_unlock() {
464 let (offchain, state) = testing::TestOffchainExt::new();
465 let mut t = TestExternalities::default();
466 t.register_extension(OffchainExt::new(offchain));
467
468 t.execute_with(|| {
469 let mut lock = StorageLock::<'_, Time>::new(b"lock_1");
470
471 let val = StorageValueRef::persistent(b"protected_value");
472
473 {
474 let _guard = lock.lock();
475
476 val.set(&VAL_1);
477
478 assert_eq!(val.get::<u32>(), Some(Some(VAL_1)));
479 }
480
481 {
482 let _guard = lock.lock();
483 val.set(&VAL_2);
484
485 assert_eq!(val.get::<u32>(), Some(Some(VAL_2)));
486 }
487 });
488 assert_eq!(state.read().persistent_storage.get(b"lock_1"), None);
490 }
491
492 #[test]
493 fn storage_lock_and_forget() {
494 let (offchain, state) = testing::TestOffchainExt::new();
495 let mut t = TestExternalities::default();
496 t.register_extension(OffchainExt::new(offchain));
497
498 t.execute_with(|| {
499 let mut lock = StorageLock::<'_, Time>::new(b"lock_2");
500
501 let val = StorageValueRef::persistent(b"protected_value");
502
503 let guard = lock.lock();
504
505 val.set(&VAL_1);
506
507 assert_eq!(val.get::<u32>(), Some(Some(VAL_1)));
508
509 guard.forget();
510 });
511 let opt = state.read().persistent_storage.get(b"lock_2");
513 assert!(opt.is_some());
514 }
515
516 #[test]
517 fn storage_lock_and_let_expire_and_lock_again() {
518 let (offchain, state) = testing::TestOffchainExt::new();
519 let mut t = TestExternalities::default();
520 t.register_extension(OffchainExt::new(offchain));
521
522 t.execute_with(|| {
523 let sleep_until = offchain::timestamp().add(Duration::from_millis(500));
524 let lock_expiration = Duration::from_millis(200);
525
526 let mut lock = StorageLock::<'_, Time>::with_deadline(b"lock_3", lock_expiration);
527
528 {
529 let guard = lock.lock();
530 guard.forget();
531 }
532
533 offchain::sleep_until(sleep_until);
535
536 let mut lock = StorageLock::<'_, Time>::new(b"lock_3");
537 let res = lock.try_lock();
538 assert!(res.is_ok());
539 let guard = res.unwrap();
540 guard.forget();
541 });
542
543 let opt = state.read().persistent_storage.get(b"lock_3");
545 assert!(opt.is_some());
546 }
547
548 #[test]
549 fn extend_active_lock() {
550 let (offchain, state) = testing::TestOffchainExt::new();
551 let mut t = TestExternalities::default();
552 t.register_extension(OffchainExt::new(offchain));
553
554 t.execute_with(|| {
555 let lock_expiration = Duration::from_millis(300);
556
557 let mut lock = StorageLock::<'_, Time>::with_deadline(b"lock_4", lock_expiration);
558 let mut guard = lock.lock();
559
560 offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(200)));
562
563 assert_eq!(guard.extend_lock().is_ok(), true);
565
566 offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(200)));
568
569 let mut lock = StorageLock::<'_, Time>::with_deadline(b"lock_4", lock_expiration);
571 let res = lock.try_lock();
572 assert_eq!(res.is_ok(), false);
573
574 offchain::sleep_until(offchain::timestamp().add(Duration::from_millis(200)));
576
577 assert_eq!(guard.extend_lock().is_ok(), false);
579 guard.forget();
580
581 let mut lock = StorageLock::<'_, Time>::with_deadline(b"lock_4", lock_expiration);
583 let res = lock.try_lock();
584 assert!(res.is_ok());
585 let guard = res.unwrap();
586
587 guard.forget();
588 });
589
590 let opt = state.read().persistent_storage.get(b"lock_4");
592 assert_eq!(opt.unwrap(), vec![132_u8, 3u8, 0, 0, 0, 0, 0, 0]); }
594}