referrerpolicy=no-referrer-when-downgrade

snowbridge_core/
sparse_bitmap.rs

1// SPDX-License-Identifier: Apache-2.0
2// SPDX-FileCopyrightText: 2023 Snowfork <hello@snowfork.com>
3
4//! # Sparse Bitmap
5//!
6//! A module that provides an efficient way to track message nonces using a sparse bitmap.
7//!
8//! ## Overview
9//!
10//! The `SparseBitmap` uses a `StorageMap<u64, u128>` to store bit flags for a large range of
11//! nonces. Each key (bucket) in the storage map contains a 128-bit value that can track 128
12//! individual nonces.
13//!
14//! The implementation efficiently maps a u64 index (nonce) to:
15//! 1. A bucket - calculated as `index >> 7` (dividing by 128)
16//! 2. A bit position - calculated as `index & 127` (remainder when dividing by 128)
17//!
18//! ## Example
19//!
20//! For nonce 300:
21//! - Bucket = 300 >> 7 = 2 (third bucket)
22//! - Bit position = 300 & 127 = 44 (45th bit in the bucket)
23//! - Corresponding bit mask = 1 << 44
24//!
25//! This approach allows tracking up to 2^64 nonces while only storing buckets that actually contain
26//! data, making it suitable for sparse sets of nonces across a wide range.
27
28use frame_support::storage::StorageMap;
29use sp_std::marker::PhantomData;
30
31/// Sparse bitmap interface.
32pub trait SparseBitmap<BitMap>
33where
34	BitMap: StorageMap<u64, u128, Query = u128>,
35{
36	/// Get the bool at the provided index.
37	fn get(index: u64) -> bool;
38	/// Set the bool at the given index to true.
39	fn set(index: u64);
40}
41
42/// Sparse bitmap implementation.
43pub struct SparseBitmapImpl<BitMap>(PhantomData<BitMap>);
44
45impl<BitMap> SparseBitmapImpl<BitMap>
46where
47	BitMap: StorageMap<u64, u128, Query = u128>,
48{
49	/// Computes the bucket index and the bit mask for a given bit index.
50	/// Each bucket contains 128 bits.
51	fn compute_bucket_and_mask(index: u64) -> (u64, u128) {
52		(index >> 7, 1u128 << (index & 127))
53	}
54}
55
56impl<BitMap> SparseBitmap<BitMap> for SparseBitmapImpl<BitMap>
57where
58	BitMap: StorageMap<u64, u128, Query = u128>,
59{
60	/// Checks if the bit at the specified index is set.
61	/// Returns `true` if the bit is set, `false` otherwise.
62	/// * `index`: The index (nonce) to check.
63	fn get(index: u64) -> bool {
64		// Calculate bucket and mask
65		let (bucket, mask) = Self::compute_bucket_and_mask(index);
66
67		// Retrieve bucket and check bit
68		let bucket_value = BitMap::get(bucket);
69		bucket_value & mask != 0
70	}
71
72	/// Sets the bit at the specified index.
73	/// This marks the nonce as processed by setting its corresponding bit in the bitmap.
74	/// * `index`: The index (nonce) to set.
75	fn set(index: u64) {
76		// Calculate bucket and mask
77		let (bucket, mask) = Self::compute_bucket_and_mask(index);
78
79		// Mutate the storage to set the bit
80		BitMap::mutate(bucket, |value| {
81			*value |= mask; // Set the bit in the bucket
82		});
83	}
84}
85
86#[cfg(test)]
87mod tests {
88	use super::*;
89	use frame_support::{
90		storage::{generator::StorageMap as StorageMapHelper, storage_prefix},
91		Twox64Concat,
92	};
93	use sp_io::TestExternalities;
94	pub struct MockStorageMap;
95
96	impl StorageMapHelper<u64, u128> for MockStorageMap {
97		type Query = u128;
98		type Hasher = Twox64Concat;
99		fn pallet_prefix() -> &'static [u8] {
100			b"MyModule"
101		}
102
103		fn storage_prefix() -> &'static [u8] {
104			b"MyStorageMap"
105		}
106
107		fn prefix_hash() -> [u8; 32] {
108			storage_prefix(Self::pallet_prefix(), Self::storage_prefix())
109		}
110
111		fn from_optional_value_to_query(v: Option<u128>) -> Self::Query {
112			v.unwrap_or_default()
113		}
114
115		fn from_query_to_optional_value(v: Self::Query) -> Option<u128> {
116			Some(v)
117		}
118	}
119
120	type TestSparseBitmap = SparseBitmapImpl<MockStorageMap>;
121
122	#[test]
123	fn test_sparse_bitmap_set_and_get() {
124		TestExternalities::default().execute_with(|| {
125			let index = 300u64;
126			let (bucket, mask) = TestSparseBitmap::compute_bucket_and_mask(index);
127
128			// Test initial state
129			assert_eq!(MockStorageMap::get(bucket), 0);
130			assert!(!TestSparseBitmap::get(index));
131
132			// Set the bit
133			TestSparseBitmap::set(index);
134
135			// Test after setting
136			assert_eq!(MockStorageMap::get(bucket), mask);
137			assert!(TestSparseBitmap::get(index));
138		});
139	}
140
141	#[test]
142	fn test_sparse_bitmap_multiple_sets() {
143		TestExternalities::default().execute_with(|| {
144			let index1 = 300u64;
145			let index2 = 305u64; // Same bucket, different bit
146			let (bucket, _) = TestSparseBitmap::compute_bucket_and_mask(index1);
147
148			let (_, mask1) = TestSparseBitmap::compute_bucket_and_mask(index1);
149			let (_, mask2) = TestSparseBitmap::compute_bucket_and_mask(index2);
150
151			// Test initial state
152			assert_eq!(MockStorageMap::get(bucket), 0);
153			assert!(!TestSparseBitmap::get(index1));
154			assert!(!TestSparseBitmap::get(index2));
155
156			// Set the first bit
157			TestSparseBitmap::set(index1);
158
159			// Test after first set
160			assert_eq!(MockStorageMap::get(bucket), mask1);
161			assert!(TestSparseBitmap::get(index1));
162			assert!(!TestSparseBitmap::get(index2));
163
164			// Set the second bit
165			TestSparseBitmap::set(index2);
166
167			// Test after second set
168			assert_eq!(MockStorageMap::get(bucket), mask1 | mask2); // Bucket should contain both masks
169			assert!(TestSparseBitmap::get(index1));
170			assert!(TestSparseBitmap::get(index2));
171		})
172	}
173
174	#[test]
175	fn test_sparse_bitmap_different_buckets() {
176		TestExternalities::default().execute_with(|| {
177			let index1 = 300u64; // Bucket 1
178			let index2 = 300u64 + (1 << 7); // Bucket 2 (128 bits apart)
179
180			let (bucket1, _) = TestSparseBitmap::compute_bucket_and_mask(index1);
181			let (bucket2, _) = TestSparseBitmap::compute_bucket_and_mask(index2);
182
183			let (_, mask1) = TestSparseBitmap::compute_bucket_and_mask(index1);
184			let (_, mask2) = TestSparseBitmap::compute_bucket_and_mask(index2);
185
186			// Test initial state
187			assert_eq!(MockStorageMap::get(bucket1), 0);
188			assert_eq!(MockStorageMap::get(bucket2), 0);
189
190			// Set bits in different buckets
191			TestSparseBitmap::set(index1);
192			TestSparseBitmap::set(index2);
193
194			// Test after setting
195			assert_eq!(MockStorageMap::get(bucket1), mask1); // Bucket 1 should contain mask1
196			assert_eq!(MockStorageMap::get(bucket2), mask2); // Bucket 2 should contain mask2
197
198			assert!(TestSparseBitmap::get(index1));
199			assert!(TestSparseBitmap::get(index2));
200		})
201	}
202
203	#[test]
204	fn test_sparse_bitmap_wide_range() {
205		TestExternalities::default().execute_with(|| {
206			// Test wide range of values across u64 spectrum
207			let test_indices = [
208				0u64,             // Smallest possible value
209				1u64,             // Early value
210				127u64,           // Last value in first bucket
211				128u64,           // First value in second bucket
212				255u64,           // End of second bucket
213				1000u64,          // Medium-small value
214				123456u64,        // Medium value
215				(1u64 << 32) - 1, // Max u32 value
216				1u64 << 32,       // First value after max u32
217				(1u64 << 32) + 1, // Just after u32 max
218				(1u64 << 40) - 1, // Large value near a power of 2
219				(1u64 << 40),     // Power of 2 value
220				(1u64 << 40) + 1, // Just after power of 2
221				u64::MAX / 2,     // Middle of u64 range
222				u64::MAX - 128,   // Near the end
223				u64::MAX - 1,     // Second-to-last possible value
224				u64::MAX,         // Largest possible value
225			];
226
227			// Verify each bit can be set and read correctly
228			for &index in &test_indices {
229				// Verify initial state - bit should be unset
230				assert!(!TestSparseBitmap::get(index), "Index {} should initially be unset", index);
231
232				// Set the bit
233				TestSparseBitmap::set(index);
234
235				// Verify bit was set
236				assert!(
237					TestSparseBitmap::get(index),
238					"Index {} should be set after setting",
239					index
240				);
241
242				// Calculate bucket and mask for verification
243				let (bucket, mask) = TestSparseBitmap::compute_bucket_and_mask(index);
244
245				// Verify the storage contains the bit
246				let value = MockStorageMap::get(bucket);
247				assert!(value & mask != 0, "Storage for index {} should have bit set", index);
248			}
249
250			// Verify all set bits can still be read correctly
251			for &index in &test_indices {
252				assert!(TestSparseBitmap::get(index), "Index {} should still be set", index);
253			}
254		})
255	}
256
257	#[test]
258	fn test_sparse_bitmap_bucket_boundaries() {
259		TestExternalities::default().execute_with(|| {
260			// Test adjacent indices on bucket boundaries
261			let boundary_pairs = [
262				(127u64, 128u64),   // End of bucket 0, start of bucket 1
263				(255u64, 256u64),   // End of bucket 1, start of bucket 2
264				(1023u64, 1024u64), // End of bucket 7, start of bucket 8
265			];
266
267			for (i1, i2) in boundary_pairs {
268				// Calculate buckets - should be different
269				let (b1, m1) = TestSparseBitmap::compute_bucket_and_mask(i1);
270				let (b2, m2) = TestSparseBitmap::compute_bucket_and_mask(i2);
271
272				// Ensure they're in different buckets
273				assert_ne!(b1, b2, "Indices {} and {} should be in different buckets", i1, i2);
274
275				// Set both bits
276				TestSparseBitmap::set(i1);
277				TestSparseBitmap::set(i2);
278
279				// Verify both are set
280				assert!(TestSparseBitmap::get(i1), "Boundary index {} should be set", i1);
281				assert!(TestSparseBitmap::get(i2), "Boundary index {} should be set", i2);
282
283				// Verify storage contains correct masks
284				let stored_b1_value = MockStorageMap::get(b1);
285				let stored_b2_value = MockStorageMap::get(b2);
286
287				// Just verify the bits are set in the masks (not checking exact mask values)
288				assert_ne!(stored_b1_value, 0, "Storage for bucket {} should not be 0", b1);
289				assert_ne!(stored_b2_value, 0, "Storage for bucket {} should not be 0", b2);
290				assert!(
291					stored_b1_value & m1 != 0,
292					"Bit for index {} should be set in bucket {}",
293					i1,
294					b1
295				);
296				assert!(
297					stored_b2_value & m2 != 0,
298					"Bit for index {} should be set in bucket {}",
299					i2,
300					b2
301				);
302			}
303		})
304	}
305
306	#[test]
307	fn test_sparse_bitmap_large_buckets() {
308		TestExternalities::default().execute_with(|| {
309			// Test indices that produce large bucket numbers (near u64::MAX)
310			let large_indices = [u64::MAX - 1, u64::MAX];
311
312			for &index in &large_indices {
313				let (bucket, mask) = TestSparseBitmap::compute_bucket_and_mask(index);
314
315				// Verify bucket calculation is as expected
316				assert_eq!(
317					bucket,
318					u64::from(index) >> 7,
319					"Bucket calculation incorrect for {}",
320					index
321				);
322
323				// Set and verify the bit
324				TestSparseBitmap::set(index);
325				assert!(TestSparseBitmap::get(index), "Large index {} should be set", index);
326
327				// Verify the bit is set in storage
328				let stored_value = MockStorageMap::get(bucket);
329				assert_ne!(stored_value, 0, "Storage for bucket {} should not be 0", bucket);
330				assert!(
331					stored_value & mask != 0,
332					"Bit for index {} should be set in bucket {}",
333					index,
334					bucket
335				);
336			}
337		})
338	}
339}