kaspa_mining/mempool/model/frontier/
search_tree.rs1use super::feerate_key::FeerateTransactionKey;
2use std::iter::FusedIterator;
3use sweep_bptree::tree::visit::{DescendVisit, DescendVisitResult};
4use sweep_bptree::tree::{Argument, SearchArgument};
5use sweep_bptree::{BPlusTree, NodeStoreVec};
6
7type FeerateKey = FeerateTransactionKey;
8
9#[derive(Clone, Copy, Debug, Default)]
21struct FeerateWeight(f64);
22
23impl FeerateWeight {
24 pub fn weight(&self) -> f64 {
26 self.0
27 }
28}
29
30impl Argument<FeerateKey> for FeerateWeight {
31 fn from_leaf(keys: &[FeerateKey]) -> Self {
32 Self(keys.iter().map(|k| k.weight()).sum())
33 }
34
35 fn from_inner(_keys: &[FeerateKey], arguments: &[Self]) -> Self {
36 Self(arguments.iter().map(|a| a.0).sum())
37 }
38}
39
40impl SearchArgument<FeerateKey> for FeerateWeight {
41 type Query = f64;
42
43 fn locate_in_leaf(query: Self::Query, keys: &[FeerateKey]) -> Option<usize> {
44 let mut sum = 0.0;
45 for (i, k) in keys.iter().enumerate() {
46 let w = k.weight();
47 sum += w;
48 if query < sum {
49 return Some(i);
50 }
51 }
52 match keys.len() {
56 0 => None,
57 #[allow(clippy::arithmetic_side_effects, reason = "The preceding arm handles zero, so n >= 1.")]
58 n => Some(n - 1),
59 }
60 }
61
62 fn locate_in_inner(mut query: Self::Query, _keys: &[FeerateKey], arguments: &[Self]) -> Option<(usize, Self::Query)> {
63 for (i, a) in arguments.iter().enumerate() {
66 if query >= a.0 {
67 query -= a.0;
68 } else {
69 return Some((i, query));
70 }
71 }
72 match arguments.len() {
77 0 => None,
78 #[allow(clippy::arithmetic_side_effects, reason = "The preceding arm handles zero, so both n - 1 expressions are safe.")]
79 n => Some((n - 1, arguments[n - 1].0)),
80 }
81 }
82}
83
84struct PrefixWeightVisitor<'a> {
89 key: &'a FeerateKey,
91 accumulated_weight: f64,
93}
94
95impl<'a> PrefixWeightVisitor<'a> {
96 pub fn new(key: &'a FeerateKey) -> Self {
97 Self { key, accumulated_weight: Default::default() }
98 }
99
100 fn search_in_keys(&self, keys: &[FeerateKey]) -> usize {
103 match keys.binary_search(self.key) {
104 Err(idx) => {
105 idx
107 }
108 Ok(idx) => {
109 #[allow(clippy::arithmetic_side_effects, reason = "ARITH-SAFETY(INDEX)")]
111 {
112 idx + 1
113 }
114 }
115 }
116 }
117}
118
119impl DescendVisit<FeerateKey, (), FeerateWeight> for PrefixWeightVisitor<'_> {
120 type Result = f64;
121
122 fn visit_inner(&mut self, keys: &[FeerateKey], arguments: &[FeerateWeight]) -> DescendVisitResult<Self::Result> {
123 let idx = self.search_in_keys(keys);
124 for argument in arguments.iter().take(idx) {
130 self.accumulated_weight += argument.weight();
131 }
132
133 DescendVisitResult::GoDown(idx)
135 }
136
137 fn visit_leaf(&mut self, keys: &[FeerateKey], _values: &[()]) -> Option<Self::Result> {
138 let idx = self.search_in_keys(keys);
140 for key in keys.iter().take(idx) {
142 self.accumulated_weight += key.weight();
143 }
144 Some(self.accumulated_weight)
146 }
147}
148
149type InnerTree = BPlusTree<NodeStoreVec<FeerateKey, (), FeerateWeight>>;
150
151pub struct SearchTree {
169 tree: InnerTree,
170}
171
172impl Default for SearchTree {
173 fn default() -> Self {
174 Self { tree: InnerTree::new(Default::default()) }
175 }
176}
177
178impl Clone for SearchTree {
179 fn clone(&self) -> Self {
180 Self { tree: InnerTree::bulk_load(self.ascending_iter().cloned().map(|key| (key, ())).collect()) }
182 }
183}
184
185impl SearchTree {
186 pub fn new() -> Self {
187 Self { tree: InnerTree::new(Default::default()) }
188 }
189
190 pub fn len(&self) -> usize {
191 self.tree.len()
192 }
193
194 pub fn is_empty(&self) -> bool {
195 self.len() == 0
196 }
197
198 pub fn insert(&mut self, key: FeerateKey) -> bool {
200 self.tree.insert(key, ()).is_none()
201 }
202
203 pub fn remove(&mut self, key: &FeerateKey) -> bool {
205 self.tree.remove(key).is_some()
206 }
207
208 pub fn search(&self, query: f64) -> &FeerateKey {
210 self.tree.get_by_argument(query).expect("clamped").0
211 }
212
213 pub fn remove_by_query(&mut self, query: f64) -> FeerateKey {
215 self.tree.remove_by_argument(query).expect("clamped").0
216 }
217
218 pub fn total_weight(&self) -> f64 {
220 self.tree.root_argument().weight()
221 }
222
223 pub fn prefix_weight(&self, key: &FeerateKey) -> f64 {
226 self.tree.descend_visit(PrefixWeightVisitor::new(key)).unwrap()
227 }
228
229 pub fn descending_iter(&self) -> impl DoubleEndedIterator<Item = &FeerateKey> + ExactSizeIterator + FusedIterator {
232 self.tree.iter().rev().map(|(key, ())| key)
233 }
234
235 pub fn ascending_iter(&self) -> impl DoubleEndedIterator<Item = &FeerateKey> + ExactSizeIterator + FusedIterator {
238 self.tree.iter().map(|(key, ())| key)
239 }
240
241 pub fn first(&self) -> Option<&FeerateKey> {
243 self.tree.first().map(|(k, ())| k)
244 }
245
246 pub fn last(&self) -> Option<&FeerateKey> {
248 self.tree.last().map(|(k, ())| k)
249 }
250}
251
252#[cfg(test)]
253mod tests {
254 use super::super::feerate_key::tests::build_feerate_key;
255 use super::*;
256 use itertools::Itertools;
257 use std::collections::HashSet;
258 use std::ops::Sub;
259
260 #[test]
261 fn test_feerate_weight_queries() {
262 let mut tree = SearchTree::new();
263 let mass = 2000;
264 let fees = vec![[123, 113, 10_000, 1000, 2050, 2048]; 64 * (64 + 1)].into_iter().flatten().collect_vec();
267
268 #[allow(clippy::mutable_key_type)]
269 let mut s = HashSet::with_capacity(fees.len());
270 for (i, fee) in fees.iter().copied().enumerate() {
271 let key = build_feerate_key(fee, mass, i as u64);
272 s.insert(key.clone());
273 tree.insert(key);
274 }
275
276 let remove = s.iter().take(fees.len() / 6).cloned().collect_vec();
278 for r in remove {
279 s.remove(&r);
280 tree.remove(&r);
281 }
282
283 let mut v = s.into_iter().collect_vec();
285 v.sort();
286
287 for (expected, item) in v.iter().rev().zip(tree.descending_iter()) {
289 assert_eq!(&expected, &item);
290 assert!(expected.cmp(item).is_eq()); }
292
293 let eps: f64 = 0.001;
295 let mut sum = 0.0;
296 for expected in v.iter() {
297 let weight = expected.weight();
298 let eps = eps.min(weight / 3.0);
299 let samples = [sum + eps, sum + weight / 2.0, sum + weight - eps];
300 for sample in samples {
301 let key = tree.search(sample);
302 assert_eq!(expected, key);
303 assert!(expected.cmp(key).is_eq()); }
305 sum += weight;
306 }
307
308 println!("{}, {}", sum, tree.total_weight());
309
310 assert_eq!(tree.first(), Some(tree.search(f64::NEG_INFINITY)));
312 assert_eq!(tree.first(), Some(tree.search(-1.0)));
313 assert_eq!(tree.first(), Some(tree.search(-eps)));
314 assert_eq!(tree.first(), Some(tree.search(0.0)));
315 assert_eq!(tree.last(), Some(tree.search(sum)));
316 assert_eq!(tree.last(), Some(tree.search(sum + eps)));
317 assert_eq!(tree.last(), Some(tree.search(sum + 1.0)));
318 assert_eq!(tree.last(), Some(tree.search(1.0 / 0.0)));
319 assert_eq!(tree.last(), Some(tree.search(f64::INFINITY)));
320 let _ = tree.search(f64::NAN);
321
322 let mut prefix = Vec::with_capacity(v.len());
324 prefix.push(v[0].weight());
325 for i in 1..v.len() {
326 prefix.push(prefix[i - 1] + v[i].weight());
327 }
328 let eps = v.iter().map(|k| k.weight()).min_by(f64::total_cmp).unwrap() * 1e-4;
329 for (expected_prefix, key) in prefix.into_iter().zip(v) {
330 let prefix = tree.prefix_weight(&key);
331 assert!(expected_prefix.sub(prefix).abs() < eps);
332 }
333 }
334
335 #[test]
336 fn test_tree_rev_iter() {
337 let mut tree = SearchTree::new();
338 let mass = 2000;
339 let fees = vec![[123, 113, 10_000, 1000, 2050, 2048]; 64 * (64 + 1)].into_iter().flatten().collect_vec();
340 let mut v = Vec::with_capacity(fees.len());
341 for (i, fee) in fees.iter().copied().enumerate() {
342 let key = build_feerate_key(fee, mass, i as u64);
343 v.push(key.clone());
344 tree.insert(key);
345 }
346 v.sort();
347
348 for (expected, item) in v.into_iter().rev().zip(tree.descending_iter()) {
349 assert_eq!(&expected, item);
350 assert!(expected.cmp(item).is_eq()); }
352 }
353
354 #[test]
355 fn test_remove_by_query_removes_searched_key() {
356 let mut tree = SearchTree::new();
357 let keys = (0..200).map(|i| build_feerate_key(i + 1, 1650, i)).collect_vec();
358
359 for key in keys.iter().cloned() {
360 tree.insert(key);
361 }
362
363 let query = tree.total_weight() / 2.0;
364 let expected = tree.search(query).clone();
365 let removed = tree.remove_by_query(query);
366
367 assert_eq!(removed, expected);
368 assert!(!tree.ascending_iter().any(|candidate| candidate == &removed));
369 assert_eq!(tree.len(), keys.len() - 1);
370 }
371
372 #[test]
373 fn test_clone_preserves_order_and_weight_queries() {
374 for size in [1, 2, 63, 64, 65, 4096, 4097] {
375 let mut tree = SearchTree::new();
376 for i in 0..size {
377 let fee = ((i * 1_103 + size * 37) % 1_000_000 + 1) as u64;
378 let mass = ((i * 97 + size * 13) % 10_000 + 1_000) as u64;
379 tree.insert(build_feerate_key(fee, mass, i as u64));
380 }
381
382 let cloned = tree.clone();
383 let original_keys = tree.ascending_iter().cloned().collect_vec();
384 let cloned_keys = cloned.ascending_iter().cloned().collect_vec();
385
386 assert_eq!(original_keys, cloned_keys, "size {size}");
387 assert_eq!(tree.len(), cloned.len(), "size {size}");
388 let total_weight = original_keys.iter().map(|key| key.weight()).sum::<f64>();
389 let eps = total_weight * 1e-10;
390 assert!(tree.total_weight().sub(cloned.total_weight()).abs() < eps, "size {size}");
391
392 let mut prefix = 0.0;
393 let stride = (size / 32).max(1);
394 for (idx, key) in original_keys.iter().enumerate() {
395 let sample = prefix + key.weight() / 2.0;
396 prefix += key.weight();
397 if idx % stride == 0 {
398 assert_eq!(tree.search(sample), cloned.search(sample), "size {size}, idx {idx}");
399 assert!(tree.prefix_weight(key).sub(cloned.prefix_weight(key)).abs() < eps, "size {size}, idx {idx}");
400 }
401 }
402 }
403 }
404
405 #[test]
406 fn test_clone_is_independent_after_mutation() {
407 let mut tree = SearchTree::new();
408 let keys = (0..200).map(|i| build_feerate_key(i + 1, 1650, i)).collect_vec();
409
410 for key in keys.iter().cloned() {
411 tree.insert(key);
412 }
413
414 let mut cloned = tree.clone();
415 for key in keys.iter().take(50) {
416 cloned.remove(key);
417 }
418 for key in keys.iter().skip(200 - 25) {
419 tree.remove(key);
420 }
421
422 assert_eq!(tree.len(), 175);
423 assert_eq!(cloned.len(), 150);
424 for key in keys.iter().take(50) {
425 assert!(tree.ascending_iter().any(|candidate| candidate == key));
426 assert!(!cloned.ascending_iter().any(|candidate| candidate == key));
427 }
428 }
429}