use std::fmt;
use std::ops::Range;
use zcash_protocol::consensus::BlockHeight;
#[cfg(feature = "unstable-spanning-tree")]
pub mod spanning_tree;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum ScanPriority {
Ignored,
Scanned,
Historic,
OpenAdjacent,
FoundNote,
ChainTip,
Verify,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ScanRange {
block_range: Range<BlockHeight>,
priority: ScanPriority,
}
impl fmt::Display for ScanRange {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{:?}({}..{})",
self.priority, self.block_range.start, self.block_range.end,
)
}
}
impl ScanRange {
pub fn from_parts(block_range: Range<BlockHeight>, priority: ScanPriority) -> Self {
assert!(
block_range.end >= block_range.start,
"{block_range:?} is invalid for ScanRange({priority:?})",
);
ScanRange {
block_range,
priority,
}
}
pub fn block_range(&self) -> &Range<BlockHeight> {
&self.block_range
}
pub fn priority(&self) -> ScanPriority {
self.priority
}
pub fn is_empty(&self) -> bool {
self.block_range.is_empty()
}
pub fn len(&self) -> usize {
usize::try_from(u32::from(self.block_range.end) - u32::from(self.block_range.start))
.unwrap()
}
pub fn truncate_start(&self, block_height: BlockHeight) -> Option<Self> {
if block_height >= self.block_range.end || self.is_empty() {
None
} else {
Some(ScanRange {
block_range: self.block_range.start.max(block_height)..self.block_range.end,
priority: self.priority,
})
}
}
pub fn truncate_end(&self, block_height: BlockHeight) -> Option<Self> {
if block_height <= self.block_range.start || self.is_empty() {
None
} else {
Some(ScanRange {
block_range: self.block_range.start..self.block_range.end.min(block_height),
priority: self.priority,
})
}
}
pub fn split_at(&self, p: BlockHeight) -> Option<(Self, Self)> {
(p > self.block_range.start && p < self.block_range.end).then_some((
ScanRange {
block_range: self.block_range.start..p,
priority: self.priority,
},
ScanRange {
block_range: p..self.block_range.end,
priority: self.priority,
},
))
}
}
#[cfg(test)]
mod tests {
use super::{ScanPriority, ScanRange};
fn scan_range(start: u32, end: u32) -> ScanRange {
ScanRange::from_parts((start.into())..(end.into()), ScanPriority::Scanned)
}
#[test]
fn truncate_start() {
let r = scan_range(5, 8);
assert_eq!(r.truncate_start(4.into()), Some(scan_range(5, 8)));
assert_eq!(r.truncate_start(5.into()), Some(scan_range(5, 8)));
assert_eq!(r.truncate_start(6.into()), Some(scan_range(6, 8)));
assert_eq!(r.truncate_start(7.into()), Some(scan_range(7, 8)));
assert_eq!(r.truncate_start(8.into()), None);
assert_eq!(r.truncate_start(9.into()), None);
let empty = scan_range(5, 5);
assert_eq!(empty.truncate_start(4.into()), None);
assert_eq!(empty.truncate_start(5.into()), None);
assert_eq!(empty.truncate_start(6.into()), None);
}
#[test]
fn truncate_end() {
let r = scan_range(5, 8);
assert_eq!(r.truncate_end(9.into()), Some(scan_range(5, 8)));
assert_eq!(r.truncate_end(8.into()), Some(scan_range(5, 8)));
assert_eq!(r.truncate_end(7.into()), Some(scan_range(5, 7)));
assert_eq!(r.truncate_end(6.into()), Some(scan_range(5, 6)));
assert_eq!(r.truncate_end(5.into()), None);
assert_eq!(r.truncate_end(4.into()), None);
let empty = scan_range(5, 5);
assert_eq!(empty.truncate_end(4.into()), None);
assert_eq!(empty.truncate_end(5.into()), None);
assert_eq!(empty.truncate_end(6.into()), None);
}
#[test]
fn split_at() {
let r = scan_range(5, 8);
assert_eq!(r.split_at(4.into()), None);
assert_eq!(r.split_at(5.into()), None);
assert_eq!(
r.split_at(6.into()),
Some((scan_range(5, 6), scan_range(6, 8)))
);
assert_eq!(
r.split_at(7.into()),
Some((scan_range(5, 7), scan_range(7, 8)))
);
assert_eq!(r.split_at(8.into()), None);
assert_eq!(r.split_at(9.into()), None);
let empty = scan_range(5, 5);
assert_eq!(empty.split_at(4.into()), None);
assert_eq!(empty.split_at(5.into()), None);
assert_eq!(empty.split_at(6.into()), None);
}
}