chainindex_core/
tracker.rs1use std::collections::VecDeque;
5
6use crate::types::BlockSummary;
7
8pub type BlockInfo = BlockSummary;
10
11pub struct BlockTracker {
16 window: VecDeque<BlockInfo>,
18 window_size: usize,
20}
21
22impl BlockTracker {
23 pub fn new(window_size: usize) -> Self {
26 Self {
27 window: VecDeque::with_capacity(window_size),
28 window_size,
29 }
30 }
31
32 pub fn push(&mut self, block: BlockInfo) -> Result<(), u64> {
37 if let Some(head) = self.window.back() {
38 if !block.extends(head) {
39 let depth = self.find_reorg_depth(&block);
41 return Err(depth);
42 }
43 }
44 if self.window.len() >= self.window_size {
45 self.window.pop_front();
46 }
47 self.window.push_back(block);
48 Ok(())
49 }
50
51 pub fn head(&self) -> Option<&BlockInfo> {
53 self.window.back()
54 }
55
56 pub fn get(&self, number: u64) -> Option<&BlockInfo> {
58 self.window.iter().find(|b| b.number == number)
59 }
60
61 pub fn len(&self) -> usize {
63 self.window.len()
64 }
65
66 pub fn is_empty(&self) -> bool {
68 self.window.is_empty()
69 }
70
71 pub fn rewind_to(&mut self, block_number: u64) {
73 while let Some(back) = self.window.back() {
74 if back.number > block_number {
75 self.window.pop_back();
76 } else {
77 break;
78 }
79 }
80 }
81
82 fn find_reorg_depth(&self, new_block: &BlockInfo) -> u64 {
84 for (i, tracked) in self.window.iter().enumerate().rev() {
86 if tracked.hash == new_block.parent_hash {
87 return (self.window.len() - 1 - i) as u64;
89 }
90 }
91 self.window.len() as u64
93 }
94}
95
96#[cfg(test)]
97mod tests {
98 use super::*;
99
100 fn block(number: u64, hash: &str, parent: &str) -> BlockInfo {
101 BlockSummary {
102 number,
103 hash: hash.into(),
104 parent_hash: parent.into(),
105 timestamp: (number * 12) as i64,
106 tx_count: 0,
107 }
108 }
109
110 #[test]
111 fn push_normal_chain() {
112 let mut tracker = BlockTracker::new(10);
113 tracker.push(block(100, "0xa", "0x0")).unwrap();
114 tracker.push(block(101, "0xb", "0xa")).unwrap();
115 tracker.push(block(102, "0xc", "0xb")).unwrap();
116 assert_eq!(tracker.head().unwrap().number, 102);
117 assert_eq!(tracker.len(), 3);
118 }
119
120 #[test]
121 fn push_detects_reorg() {
122 let mut tracker = BlockTracker::new(10);
123 tracker.push(block(100, "0xa", "0x0")).unwrap();
124 tracker.push(block(101, "0xb", "0xa")).unwrap();
125 let result = tracker.push(block(102, "0xc2", "0xb-different"));
127 assert!(result.is_err(), "should detect reorg");
128 }
129
130 #[test]
131 fn rewind_to() {
132 let mut tracker = BlockTracker::new(10);
133 for i in 100..=110 {
134 let prev = if i == 100 {
135 "0x0".to_string()
136 } else {
137 format!("0x{}", i - 1)
138 };
139 tracker.push(block(i, &format!("0x{i}"), &prev)).unwrap();
140 }
141 assert_eq!(tracker.head().unwrap().number, 110);
142 tracker.rewind_to(105);
143 assert_eq!(tracker.head().unwrap().number, 105);
144 }
145
146 #[test]
147 fn window_size_enforced() {
148 let mut tracker = BlockTracker::new(5);
149 for i in 0..10 {
150 let prev = if i == 0 {
151 "0x0".to_string()
152 } else {
153 format!("0x{}", i - 1)
154 };
155 tracker.push(block(i, &format!("0x{i}"), &prev)).unwrap();
156 }
157 assert_eq!(tracker.len(), 5); }
159}