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