common_primitives/
offchain.rs

1use crate::msa::MessageSourceId;
2use numtoa::NumToA;
3use parity_scale_codec::{Decode, Encode};
4#[cfg(feature = "std")]
5use sp_externalities::ExternalitiesExt;
6use sp_runtime::offchain::storage::{StorageRetrievalError, StorageValueRef};
7extern crate alloc;
8use alloc::vec::Vec;
9use core::fmt::Debug;
10use sp_runtime_interface::{
11	pass_by::{AllocateAndReturnByCodec, PassFatPointerAndReadWrite},
12	runtime_interface,
13};
14
15#[cfg(feature = "std")]
16sp_externalities::decl_extension! {
17	/// Offchain worker custom extension
18	pub struct OcwCustomExt (
19		// rpc address provided to offchain worker
20		Vec<u8>
21	);
22}
23
24/// runtime new customized
25#[runtime_interface]
26pub trait Custom: ExternalitiesExt {
27	/// another function
28	/// TODO: Remove this legacy method once all collators are upgraded
29	fn get_val(&mut self) -> AllocateAndReturnByCodec<Option<Vec<u8>>> {
30		None
31	}
32
33	/// Get extension value by writing to output buffer
34	/// Returns the total length of encoded data,
35	/// or 0 if no extension found.
36	fn get_val_buffered(&mut self, output: PassFatPointerAndReadWrite<&mut [u8]>) -> u32 {
37		match self.extension::<OcwCustomExt>() {
38			Some(ext) => {
39				let encoded = ext.0.clone().encode();
40				let written = core::cmp::min(encoded.len(), output.len());
41				output[..written].copy_from_slice(&encoded[..written]);
42				written as u32
43			},
44			None => 0,
45		}
46	}
47}
48/// Lock expiration timeout in milli-seconds for msa pallet per msa account
49pub const MSA_ACCOUNT_LOCK_TIMEOUT_EXPIRATION_MS: u64 = 50;
50/// Lock name prefix for msa account
51pub const MSA_ACCOUNT_LOCK_NAME_PREFIX: &[u8; 16] = b"Msa::ofw::lock::";
52/// Offchain storage prefix for msa account
53pub const MSA_ACCOUNT_STORAGE_NAME_PREFIX: &[u8; 16] = b"Msa::ofw::keys::";
54/// msa account lock name
55pub fn get_msa_account_lock_name(msa_id: MessageSourceId) -> Vec<u8> {
56	let mut buff = [0u8; 30];
57	[MSA_ACCOUNT_LOCK_NAME_PREFIX, msa_id.numtoa(10, &mut buff)].concat()
58}
59/// msa account storage key name
60pub fn get_msa_account_storage_key_name(msa_id: MessageSourceId) -> Vec<u8> {
61	let mut buff = [0u8; 30];
62	[MSA_ACCOUNT_STORAGE_NAME_PREFIX, msa_id.numtoa(10, &mut buff)].concat()
63}
64
65/// Locks the execution of the function
66#[derive(Debug)]
67pub enum LockStatus {
68	/// Lock is acquired
69	Locked,
70	/// Lock is released
71	Released,
72}
73
74/// Wrapper for offchain get operations
75pub fn get_index_value<V: Decode + Debug>(key: &[u8]) -> Result<Option<V>, StorageRetrievalError> {
76	get_impl::<V>(key)
77}
78
79/// Gets a value by the key from persistent storage
80fn get_impl<V: Decode + Debug>(key: &[u8]) -> Result<Option<V>, StorageRetrievalError> {
81	let oci_mem = StorageValueRef::persistent(key);
82	match oci_mem.get::<V>() {
83		Ok(Some(data)) => Ok(Some(data)),
84		Ok(None) => Ok(None),
85		Err(_) => Err(StorageRetrievalError::Undecodable),
86	}
87}
88
89#[cfg(test)]
90mod tests {
91	use super::*;
92	use sp_core::offchain::{testing, OffchainDbExt, OffchainWorkerExt};
93	use sp_io::TestExternalities;
94
95	#[test]
96	fn get_msa_account_lock_name_should_return_expected_value() {
97		let msa_id: MessageSourceId = 2_000_000;
98		let result = get_msa_account_lock_name(msa_id);
99		assert_eq!(result, b"Msa::ofw::lock::2000000".to_vec());
100	}
101
102	#[test]
103	fn get_msa_account_storage_name_should_return_expected_value() {
104		let msa_id: MessageSourceId = 2_000_000;
105		let result = get_msa_account_storage_key_name(msa_id);
106		assert_eq!(result, b"Msa::ofw::keys::2000000".to_vec());
107	}
108
109	#[test]
110	fn get_index_for_not_set_should_return_none() {
111		let (offchain, _state) = testing::TestOffchainExt::new();
112		let mut t = TestExternalities::default();
113		t.register_extension(OffchainDbExt::new(offchain.clone()));
114		t.register_extension(OffchainWorkerExt::new(offchain));
115
116		t.execute_with(|| {
117			let key = b"my_key";
118			let result = get_index_value::<MessageSourceId>(key);
119			assert_eq!(result, Ok(None));
120		});
121	}
122
123	#[test]
124	fn get_index_for_set_should_return_expected() {
125		// arrange
126		let (offchain, _state) = testing::TestOffchainExt::new();
127		let mut t = TestExternalities::default();
128		t.register_extension(OffchainDbExt::new(offchain.clone()));
129		t.register_extension(OffchainWorkerExt::new(offchain));
130
131		t.execute_with(|| {
132			let key = b"my_key1";
133			let msa_id: MessageSourceId = 1000000;
134			let oci_mem = StorageValueRef::persistent(key);
135			oci_mem.set(&msa_id);
136
137			// act
138			let result = get_index_value::<MessageSourceId>(key);
139
140			// assert
141			assert_eq!(result, Ok(Some(msa_id)));
142		});
143	}
144
145	#[test]
146	fn get_index_for_not_decodable_should_return_error() {
147		let (offchain, _state) = testing::TestOffchainExt::new();
148		let mut t = TestExternalities::default();
149		t.register_extension(OffchainDbExt::new(offchain.clone()));
150		t.register_extension(OffchainWorkerExt::new(offchain));
151
152		#[derive(Debug, Decode, PartialEq)]
153		struct Testing {
154			pub a: u64,
155			pub b: u32,
156			pub c: u16,
157		}
158
159		t.execute_with(|| {
160			// arrange
161			let key = b"my_key2";
162			let msa_id: MessageSourceId = 1000000;
163			let oci_mem = StorageValueRef::persistent(key);
164			oci_mem.set(&msa_id);
165
166			// act
167			let result = get_index_value::<Testing>(key);
168
169			// assert
170			assert_eq!(result, Err(StorageRetrievalError::Undecodable));
171		});
172	}
173}