use sp_core::storage::StorageKey;
use std::{
collections::VecDeque,
sync::{Arc, Mutex},
};
pub(crate) const DEFAULT_PAGE_SIZE: u32 = 1000;
#[derive(Debug, Clone)]
pub(crate) struct KeyRange {
pub(crate) start_key: StorageKey,
pub(crate) end_key: Option<StorageKey>,
pub(crate) prefix: StorageKey,
pub(crate) page_size: u32,
pub(crate) exclude_start_key: bool,
}
impl KeyRange {
pub(crate) fn new(
start_key: StorageKey,
end_key: Option<StorageKey>,
prefix: StorageKey,
) -> Self {
Self { start_key, end_key, prefix, page_size: DEFAULT_PAGE_SIZE, exclude_start_key: false }
}
pub(crate) fn continuation(
start_key: StorageKey,
end_key: Option<StorageKey>,
prefix: StorageKey,
) -> Self {
Self { start_key, end_key, prefix, page_size: DEFAULT_PAGE_SIZE, exclude_start_key: true }
}
pub(crate) fn with_halved_page_size(&self) -> Self {
Self { page_size: (self.page_size / 2).max(10), ..self.clone() }
}
pub(crate) fn filter_keys(&self, keys: Vec<StorageKey>) -> (Vec<StorageKey>, bool) {
let rpc_returned_full_batch = keys.len() == self.page_size as usize;
let filtered: Vec<_> = keys
.into_iter()
.filter(|key| {
if self.exclude_start_key && key == &self.start_key {
return false;
}
self.end_key.as_ref().map_or(true, |end| key < end)
})
.collect();
let is_full_batch = rpc_returned_full_batch && !filtered.is_empty();
(filtered, is_full_batch)
}
}
pub(crate) type WorkQueue = Arc<Mutex<VecDeque<KeyRange>>>;
pub(crate) fn gen_key_ranges(prefix: &StorageKey) -> Vec<KeyRange> {
let prefix_bytes = prefix.as_ref().to_vec();
let mut ranges = Vec::with_capacity(16);
for i in 0u8..16u8 {
let mut start_key = prefix_bytes.clone();
start_key.push(i << 4);
let end_key = if i < 15 {
let mut end = prefix_bytes.clone();
end.push((i + 1) << 4); Some(StorageKey(end))
} else {
None
};
ranges.push(KeyRange::new(StorageKey(start_key), end_key, prefix.clone()));
}
ranges
}
pub(crate) fn initialize_work_queue(prefixes: &[StorageKey]) -> WorkQueue {
let mut queue = VecDeque::new();
for prefix in prefixes {
let ranges = gen_key_ranges(prefix);
queue.extend(ranges);
}
Arc::new(Mutex::new(queue))
}
pub(crate) fn subdivide_remaining_range(
second_last_key: &StorageKey,
last_key: &StorageKey,
end_key: Option<&StorageKey>,
prefix: &StorageKey,
) -> Vec<KeyRange> {
let second_last_bytes = second_last_key.as_ref();
let last_key_bytes = last_key.as_ref();
let divergence_pos = second_last_bytes
.iter()
.zip(last_key_bytes.iter())
.position(|(a, b)| a != b)
.unwrap_or(second_last_bytes.len().min(last_key_bytes.len()));
let mut subdivision_nibble = None;
for subdivision_pos in divergence_pos..last_key_bytes.len() {
let byte = last_key_bytes[subdivision_pos];
let nibble = byte >> 4;
if nibble < 15 {
subdivision_nibble = Some((subdivision_pos, nibble));
break;
}
}
let mut ranges = Vec::new();
if let Some((pos, current_nibble)) = subdivision_nibble {
let subdivision_prefix = &last_key_bytes[..pos];
let mut end = subdivision_prefix.to_vec();
end.push((current_nibble + 1) << 4);
ranges.push(KeyRange::continuation(
last_key.clone(),
Some(StorageKey(end)),
prefix.clone(),
));
for nibble in (current_nibble + 1)..16u8 {
let mut start = subdivision_prefix.to_vec();
start.push(nibble << 4);
let start_key = StorageKey(start);
if end_key.map_or(false, |ek| start_key >= *ek) {
break;
}
let chunk_end_key = if nibble < 15 {
let mut end = subdivision_prefix.to_vec();
end.push((nibble + 1) << 4);
let computed_end = StorageKey(end);
Some(match end_key {
Some(actual_end) if &computed_end > actual_end => actual_end.clone(),
_ => computed_end,
})
} else {
end_key.cloned()
};
ranges.push(KeyRange::new(start_key, chunk_end_key, prefix.clone()));
}
} else if end_key.as_ref().map_or(true, |ek| last_key <= ek) {
ranges.push(KeyRange::continuation(last_key.clone(), end_key.cloned(), prefix.clone()));
}
ranges
}
#[cfg(test)]
mod tests {
use super::*;
fn key(bytes: &[u8]) -> StorageKey {
StorageKey(bytes.to_vec())
}
#[test]
fn gen_key_ranges_creates_16_non_overlapping_ranges() {
let prefix = key(&[0xAB, 0xCD]);
let ranges = gen_key_ranges(&prefix);
assert_eq!(ranges.len(), 16);
assert_eq!(ranges[0].start_key, key(&[0xAB, 0xCD, 0x00]));
assert_eq!(ranges[0].end_key, Some(key(&[0xAB, 0xCD, 0x10])));
assert!(!ranges[0].exclude_start_key);
assert_eq!(ranges[8].start_key, key(&[0xAB, 0xCD, 0x80]));
assert_eq!(ranges[8].end_key, Some(key(&[0xAB, 0xCD, 0x90])));
assert_eq!(ranges[15].start_key, key(&[0xAB, 0xCD, 0xF0]));
assert_eq!(ranges[15].end_key, None);
for i in 0..15 {
assert_eq!(ranges[i].end_key, Some(ranges[i + 1].start_key.clone()));
}
}
#[test]
fn gen_key_ranges_with_empty_prefix() {
let prefix = key(&[]);
let ranges = gen_key_ranges(&prefix);
assert_eq!(ranges.len(), 16);
assert_eq!(ranges[0].start_key, key(&[0x00]));
assert_eq!(ranges[0].end_key, Some(key(&[0x10])));
assert_eq!(ranges[15].start_key, key(&[0xF0]));
assert_eq!(ranges[15].end_key, None);
}
#[test]
fn subdivide_creates_continuation_ranges() {
let prefix = key(&[0xAB]);
let second_last = key(&[0xAB, 0x12, 0x34]);
let last = key(&[0xAB, 0x12, 0x35]);
let ranges = subdivide_remaining_range(&second_last, &last, None, &prefix);
assert!(!ranges.is_empty());
assert!(ranges[0].exclude_start_key, "First subdivided range should exclude start_key");
assert_eq!(ranges[0].start_key, last);
}
#[test]
fn subdivide_respects_end_key() {
let prefix = key(&[0xAB]);
let second_last = key(&[0xAB, 0x12, 0x34]);
let last = key(&[0xAB, 0x12, 0x35]);
let end = key(&[0xAB, 0x20]);
let ranges = subdivide_remaining_range(&second_last, &last, Some(&end), &prefix);
for range in &ranges {
if let Some(range_end) = &range.end_key {
assert!(range_end <= &end, "Range end should not exceed overall end_key");
}
assert!(range.start_key < end, "Range start should be before end_key");
}
}
#[test]
fn subdivide_when_last_key_at_nibble_boundary() {
let prefix = key(&[0xAB]);
let second_last = key(&[0xAB, 0x1F, 0xFF]);
let last = key(&[0xAB, 0x20, 0x00]);
let ranges = subdivide_remaining_range(&second_last, &last, None, &prefix);
assert!(!ranges.is_empty());
assert_eq!(ranges[0].start_key, last);
assert!(ranges[0].exclude_start_key);
}
#[test]
fn subdivide_clamps_ranges_to_end_key() {
let prefix = key(&[0xAB]);
let second_last = key(&[0xAB, 0x12, 0x34]);
let last = key(&[0xAB, 0x12, 0x50]);
let end = key(&[0xAB, 0x12, 0x60]);
let ranges = subdivide_remaining_range(&second_last, &last, Some(&end), &prefix);
assert!(!ranges.is_empty());
for range in &ranges {
assert!(
range.start_key < end,
"Range start {:?} should be before end {:?}",
range.start_key,
end
);
}
}
#[test]
fn key_range_with_halved_page_size_preserves_exclude_flag() {
let range = KeyRange::continuation(key(&[0x00]), Some(key(&[0x10])), key(&[]));
assert!(range.exclude_start_key);
assert_eq!(range.page_size, DEFAULT_PAGE_SIZE);
let halved = range.with_halved_page_size();
assert!(halved.exclude_start_key, "exclude_start_key should be preserved");
assert_eq!(halved.page_size, DEFAULT_PAGE_SIZE / 2);
assert_eq!(halved.start_key, range.start_key);
assert_eq!(halved.end_key, range.end_key);
}
#[test]
fn key_range_new_vs_continuation() {
let start = key(&[0x00]);
let end = Some(key(&[0x10]));
let prefix = key(&[]);
let new_range = KeyRange::new(start.clone(), end.clone(), prefix.clone());
let cont_range = KeyRange::continuation(start.clone(), end.clone(), prefix.clone());
assert!(!new_range.exclude_start_key, "new() should not exclude start_key");
assert!(cont_range.exclude_start_key, "continuation() should exclude start_key");
assert_eq!(new_range.start_key, cont_range.start_key);
assert_eq!(new_range.end_key, cont_range.end_key);
assert_eq!(new_range.prefix, cont_range.prefix);
assert_eq!(new_range.page_size, cont_range.page_size);
}
#[test]
fn filter_keys_excludes_start_key_for_continuation() {
let start = key(&[0xAB, 0x12, 0x34]);
let end = key(&[0xAB, 0x20]);
let prefix = key(&[0xAB]);
let range = KeyRange::continuation(start.clone(), Some(end.clone()), prefix);
let keys = vec![start.clone(), key(&[0xAB, 0x12, 0x35]), key(&[0xAB, 0x12, 0x40])];
let (filtered, is_full_batch) = range.filter_keys(keys);
assert!(!filtered.contains(&start), "start_key should be filtered out");
assert_eq!(filtered.len(), 2);
assert!(!is_full_batch);
}
#[test]
fn filter_keys_prevents_infinite_subdivision_on_empty_result() {
let start = key(&[0xAB, 0x12, 0x34]);
let prefix = key(&[0xAB]);
let mut range = KeyRange::continuation(start.clone(), None, prefix);
range.page_size = 1;
let keys = vec![start.clone()];
let (filtered, is_full_batch) = range.filter_keys(keys);
assert!(filtered.is_empty(), "start_key should be filtered out");
assert!(!is_full_batch, "is_full_batch should be false when filtered result is empty");
}
#[test]
fn filter_keys_excludes_keys_beyond_end() {
let start = key(&[0xAB, 0x10]);
let end = key(&[0xAB, 0x20]);
let prefix = key(&[0xAB]);
let range = KeyRange::new(start.clone(), Some(end.clone()), prefix);
let keys = vec![
key(&[0xAB, 0x10]),
key(&[0xAB, 0x15]),
key(&[0xAB, 0x20]), key(&[0xAB, 0x25]), ];
let (filtered, _) = range.filter_keys(keys);
assert_eq!(filtered.len(), 2);
assert!(!filtered.contains(&end), "keys at end should be excluded");
assert!(!filtered.contains(&key(&[0xAB, 0x25])), "keys beyond end should be excluded");
}
#[test]
fn filter_keys_reports_full_batch_correctly() {
let start = key(&[0x00]);
let prefix = key(&[]);
let mut range = KeyRange::new(start, None, prefix);
range.page_size = 3;
let keys = vec![key(&[0x00]), key(&[0x01]), key(&[0x02])];
let (filtered, is_full_batch) = range.filter_keys(keys);
assert_eq!(filtered.len(), 3);
assert!(is_full_batch, "should be a full batch when page_size keys returned");
}
}