Coverage Report

Created: 2022-11-10 19:56

/home/runner/work/creditcoin/creditcoin/pallets/creditcoin/src/ocw/nonce.rs
Line
Count
Source (jump to first uncovered line)
1
use crate::{Config, Pallet};
2
use alloc::vec::Vec;
3
use codec::Encode;
4
use sp_runtime::offchain::storage_lock::{StorageLock, Time};
5
use sp_runtime::offchain::Duration;
6
7
const SYNCED_NONCE: &[u8] = b"creditcoin/OCW/nonce/nonce/";
8
const SYNCED_NONCE_LOCK: &[u8] = b"creditcoin/OCW/nonce/lock/";
9
const LOCK_DEADLINE: u64 = 50_000;
10
11
18
pub(super) fn lock_key<Id: Encode>(id: &Id) -> Vec<u8> {
12
18
  id.using_encoded(|encoded_id| SYNCED_NONCE_LOCK.iter().chain(encoded_id).copied().collect())
13
18
}
14
15
21
pub fn nonce_key<Id: Encode>(id: &Id) -> Vec<u8> {
16
21
  id.using_encoded(|encoded_id| SYNCED_NONCE.iter().chain(encoded_id).copied().collect())
17
21
}
18
19
impl<T: Config> Pallet<T> {
20
17
  pub(super) fn nonce_lock_new(key: &[u8]) -> StorageLock<'_, Time> {
21
17
    StorageLock::<Time>::with_deadline(key, Duration::from_millis(LOCK_DEADLINE))
22
17
  }
23
}
24
25
#[cfg(test)]
26
mod tests {
27
  use super::*;
28
  use crate::helpers::HexToAddress;
29
  use crate::mock::{roll_to, roll_to_with_ocw, ExtBuilder, Origin, Test};
30
  use crate::ocw::errors::VerificationFailureCause as Cause;
31
  use crate::ocw::tasks::collect_coins::tests::mock_rpc_for_collect_coins;
32
  use crate::ocw::tasks::collect_coins::{testing_constants::CHAIN, tests::TX_HASH};
33
  use crate::tests::generate_address_with_proof;
34
  use crate::types::{Address, AddressId};
35
  use crate::Pallet as Creditcoin;
36
  use assert_matches::assert_matches;
37
  use codec::Decode;
38
  use core::sync::atomic::AtomicU64;
39
  use frame_support::assert_ok;
40
  use frame_system::Config as SystemConfig;
41
  use frame_system::Pallet as System;
42
  use sp_runtime::offchain::storage::StorageValueRef;
43
  use sp_runtime::offchain::testing::TestOffchainExt;
44
  use sp_runtime::traits::IdentifyAccount;
45
  use std::sync::atomic::Ordering;
46
  use std::sync::Arc;
47
48
1
  #[test]
49
1
  fn incremented_for_persisted_task() {
50
1
    let mut ext = ExtBuilder::default();
51
1
    let pkey = ext.generate_authority();
52
1
    let acct = <Test as SystemConfig>::AccountId::from(pkey.into_account().0);
53
1
    ext.build_offchain_and_execute_with_state(|state, _| {
54
1
      mock_rpc_for_collect_coins(&state);
55
1
56
1
      let (acc, addr, sign, _) = generate_address_with_proof("collector");
57
1
      assert_ok!(Creditcoin::<Test>::register_address(
58
1
        Origin::signed(acc.clone()),
59
1
        CHAIN,
60
1
        addr.clone(),
61
1
        sign
62
1
      ));
63
64
1
      roll_to(1);
65
1
      assert_ok!(Creditcoin::<Test>::request_collect_coins(
66
1
        Origin::signed(acc),
67
1
        addr,
68
1
        TX_HASH.hex_to_address()
69
1
      ));
70
1
      roll_to_with_ocw(2);
71
1
72
1
      let key = &nonce_key(&acct);
73
1
      let synced_nonce = StorageValueRef::persistent(key);
74
1
      let synced_nonce: u64 = synced_nonce.get().unwrap().unwrap();
75
1
      assert_eq!(synced_nonce, 1u64);
76
1
      let nonce = System::<Test>::account(acct).nonce;
77
1
      assert_eq!(nonce, 1u64);
78
1
    });
79
1
  }
80
81
1
  #[test]
82
1
  fn incremented_for_failed_task() {
83
1
    let mut ext = ExtBuilder::default();
84
1
    let pkey = ext.generate_authority();
85
1
    let acct = <Test as SystemConfig>::AccountId::from(pkey.into_account().0);
86
1
    ext.build_offchain_and_execute_with_state(|state, pool| {
87
1
      mock_rpc_for_collect_coins(&state);
88
1
89
1
      let (acc, addr, sign, _) = generate_address_with_proof("collector");
90
1
      assert_ok!(Creditcoin::<Test>::register_address(
91
1
        Origin::signed(acc.clone()),
92
1
        CHAIN,
93
1
        addr.clone(),
94
1
        sign
95
1
      ));
96
97
1
      let mut fake = addr;
98
1
      fake[0] = 0xff;
99
1
      let address_id = AddressId::new::<Test>(&CHAIN, &fake);
100
1
      let entry = Address { blockchain: CHAIN, value: fake.clone(), owner: acc.clone() };
101
1
      crate::Addresses::<Test>::insert(address_id, entry);
102
1
103
1
      roll_to(1);
104
1
      assert_ok!(Creditcoin::<Test>::request_collect_coins(
105
1
        Origin::signed(acc),
106
1
        fake.clone(),
107
1
        TX_HASH.hex_to_address()
108
1
      ));
109
110
1
      let deadline = Test::unverified_transfer_deadline();
111
1
      roll_to_with_ocw(2);
112
1
113
1
      let key = &nonce_key(&acct);
114
1
      let synced_nonce = StorageValueRef::persistent(key);
115
1
      let synced_nonce: u64 = synced_nonce.get().unwrap().unwrap();
116
1
      assert_eq!(synced_nonce, 1u64);
117
1
      let nonce = System::<Test>::account(acct).nonce;
118
1
      assert_eq!(nonce, 1u64);
119
120
1
      let expected_collected_coins_id = crate::CollectedCoinsId::new::<crate::mock::Test>(
121
1
        &CHAIN,
122
1
        &TX_HASH.hex_to_address(),
123
1
      );
124
1
125
1
      let call = crate::Call::<crate::mock::Test>::fail_task {
126
1
        task_id: expected_collected_coins_id.into(),
127
1
        cause: Cause::IncorrectSender,
128
1
        deadline,
129
1
      };
130
131
1
      
assert_matches!0
(pool.write().transactions.pop(),
132
1
        Some(tx) => {
133
1
          let tx = crate::mock::Extrinsic::decode(&mut &*tx).unwrap();
134
1
          assert_eq!(tx.call, crate::mock::Call::Creditcoin(call));
135
        }
136
      );
137
1
    });
138
1
  }
139
140
1
  #[test]
141
1
  fn unique_per_account() {
142
1
    let mut ext = ExtBuilder::default();
143
1
    let pkey = ext.generate_authority();
144
1
    let acct_1 = <Test as SystemConfig>::AccountId::from(pkey.into_account().0);
145
1
    let pkey = ext.generate_authority();
146
1
    let acct_2 = <Test as SystemConfig>::AccountId::from(pkey.into_account().0);
147
1
    assert!(nonce_key(&acct_1) != nonce_key(&acct_2));
148
1
    assert!(lock_key(&acct_1) != lock_key(&acct_2));
149
1
  }
150
151
1
  #[test]
152
1
  fn not_incremented_on_task_error() {
153
1
    let mut ext = ExtBuilder::default();
154
1
    let pkey = ext.generate_authority();
155
1
    let acct = <Test as SystemConfig>::AccountId::from(pkey.into_account().0);
156
1
    ext.build_offchain_and_execute_with_state(|_, pool| {
157
1
      let (acc, addr, sign, _) = generate_address_with_proof("collector");
158
1
      assert_ok!(Creditcoin::<Test>::register_address(
159
1
        Origin::signed(acc.clone()),
160
1
        CHAIN,
161
1
        addr.clone(),
162
1
        sign
163
1
      ));
164
165
1
      roll_to(1);
166
1
      assert_ok!(Creditcoin::<Test>::request_collect_coins(
167
1
        Origin::signed(acc),
168
1
        addr,
169
1
        TX_HASH.hex_to_address()
170
1
      ));
171
1
      roll_to_with_ocw(2);
172
1
173
1
      let key = &nonce_key(&acct);
174
1
      let synced_nonce = StorageValueRef::persistent(key);
175
1
      let synced_nonce = synced_nonce.get::<u64>().unwrap();
176
1
      assert!(synced_nonce.is_none());
177
1
      let nonce = System::<Test>::account(acct).nonce;
178
1
      assert_eq!(nonce, 0u64);
179
180
1
      assert!(pool.write().transactions.is_empty());
181
1
    });
182
1
  }
183
184
1
  #[test]
185
1
  fn parallel_increment() {
186
1
    let (offchain, _) = TestOffchainExt::new();
187
1
    const THREADS: u32 = 3;
188
1
    let nonces = Arc::new(AtomicU64::new(0));
189
1
190
3
    let handles = (0..THREADS).into_iter().map(|_| {
191
3
      let offchain = offchain.clone();
192
3
      let nonces = nonces.clone();
193
3
194
3
      std::thread::spawn(move || {
195
3
        let mut ext_builder = ExtBuilder::default();
196
3
        let acct_pubkey = ext_builder.generate_authority();
197
3
        let acct = <Test as SystemConfig>::AccountId::from(acct_pubkey.into_account().0);
198
3
        let expected_collected_coins_id =
199
3
          crate::CollectedCoinsId::new::<Test>(&CHAIN, &[0]);
200
3
        let (mut ext, pool) = ext_builder.build_with(offchain);
201
3
        let execute = || {
202
3
          crate::mock::roll_to(1);
203
3
          let call = crate::Call::<Test>::fail_task {
204
3
            task_id: expected_collected_coins_id.into(),
205
3
            cause: Cause::AbiMismatch,
206
3
            deadline: Test::unverified_transfer_deadline(),
207
3
          };
208
3
          assert_ok!(crate::Pallet::<Test>::submit_txn_with_synced_nonce(
209
3
            acct.clone(),
210
3
            |_| call.clone(),
211
3
          ));
212
213
3
          
assert_matches!0
(pool.write().transactions.pop(),
214
3
            Some(tx) => {
215
3
              let tx = crate::mock::Extrinsic::decode(&mut &*tx).unwrap();
216
3
              assert_eq!(tx.call, crate::mock::Call::Creditcoin(call));
217
            }
218
          );
219
220
3
          let nonce = System::<Test>::account(acct).nonce;
221
3
          nonces.fetch_add(nonce, Ordering::Relaxed);
222
3
        };
223
3
224
3
        ext.execute_with(execute);
225
3
      })
226
3
    }
)1
;
227
228
4
    for 
h3
in handles {
229
3
      h.join().expect("testing context is shared");
230
3
    }
231
232
1
    let ext_builder = ExtBuilder::default();
233
1
    let (mut ext, _) = ext_builder.build_with(offchain);
234
1
    ext.execute_with(|| {
235
1
      let nonce_post_submition_sum = (THREADS) * (THREADS + 1) / 2;
236
1
      assert_eq!(nonces.load(Ordering::Relaxed), nonce_post_submition_sum as u64);
237
1
    });
238
1
  }
239
240
1
  #[test]
241
1
  fn lock_works() {
242
1
    let (offchain, _) = TestOffchainExt::new();
243
1
    const THREADS: u32 = 2;
244
1
245
2
    let handles = (0..THREADS).map(|_| {
246
2
      let offchain = offchain.clone();
247
2
248
2
      std::thread::spawn(move || {
249
2
        let mut ext_builder = ExtBuilder::default();
250
2
        let acct_pubkey = ext_builder.generate_authority();
251
2
        let acct = <Test as SystemConfig>::AccountId::from(acct_pubkey.into_account().0);
252
2
        let (mut ext, _) = ext_builder.build_with(offchain);
253
2
254
2
        let execute = || {
255
2
          crate::mock::roll_to(1);
256
2
257
2
          let key = lock_key(&acct);
258
2
          let mut lock = Pallet::<Test>::nonce_lock_new(&key);
259
2
          let guard = lock.try_lock();
260
2
          guard.map(|g| 
g.forget()1
).or_else(|deadline| {
261
1
            // failed to acq guard; move to active guard's deadline boundaries
262
1
            sp_io::offchain::sleep_until(deadline);
263
1
            //deadline still effective
264
1
            lock.try_lock().map(|_| 
()0
)
265
2
          })
266
2
        };
267
2
268
2
        ext.execute_with(execute)
269
2
      })
270
2
    });
271
1
272
2
    if !handles.into_iter().any(|h| h.join().expect("thread joins").is_err()
)1
{
273
0
      panic!("lock should block")
274
1
    }
275
1
  }
276
277
1
  #[test]
278
1
  fn nonce_lock_expires() {
279
1
    let ext = ExtBuilder::default();
280
1
    ext.build_offchain_and_execute_with_state(|_, _| {
281
1
      System::<Test>::set_block_number(1);
282
1
283
1
      let key = &b"lock_key"[..];
284
1
      let mut lock = Pallet::<Test>::nonce_lock_new(key);
285
1
      let guard = lock.try_lock().expect("ok");
286
1
      guard.forget();
287
1
      let guard = lock.try_lock();
288
1
      let deadline = guard.map(|_| 
()0
).expect_err("deadline");
289
1
      // failed to acq guard; move past active guard's deadline boundary
290
1
      sp_io::offchain::sleep_until(deadline.add(Duration::from_millis(LOCK_DEADLINE + 1)));
291
1
      let g = lock.try_lock();
292
1
      assert!(g.is_ok());
293
1
    });
294
1
  }
295
}