1use std::{
4 collections::{hash_map::Drain, BTreeMap, HashMap, HashSet, VecDeque},
5 iter, mem,
6};
7
8use tokio::sync::oneshot;
9use tracing::instrument;
10
11use zebra_chain::{block, transparent};
12
13use crate::{
14 error::{CommitBlockError, CommitCheckpointVerifiedError},
15 CheckpointVerifiedBlock, CommitSemanticallyVerifiedError, KnownBlock, NonFinalizedState,
16 SemanticallyVerifiedBlock,
17};
18
19#[cfg(test)]
20mod tests;
21
22pub type QueuedCheckpointVerified = (
24 CheckpointVerifiedBlock,
25 oneshot::Sender<Result<block::Hash, CommitCheckpointVerifiedError>>,
26);
27
28pub type QueuedSemanticallyVerified = (
30 SemanticallyVerifiedBlock,
31 oneshot::Sender<Result<block::Hash, CommitSemanticallyVerifiedError>>,
32);
33
34#[derive(Debug, Default)]
36pub struct QueuedBlocks {
37 blocks: HashMap<block::Hash, QueuedSemanticallyVerified>,
39 by_parent: HashMap<block::Hash, HashSet<block::Hash>>,
41 by_height: BTreeMap<block::Height, HashSet<block::Hash>>,
43 known_utxos: HashMap<transparent::OutPoint, transparent::Utxo>,
45}
46
47impl QueuedBlocks {
48 #[instrument(skip(self), fields(height = ?new.0.height, hash = %new.0.hash))]
54 pub fn queue(&mut self, new: QueuedSemanticallyVerified) {
55 let new_hash = new.0.hash;
56 let new_height = new.0.height;
57 let parent_hash = new.0.block.header.previous_block_hash;
58
59 if self.blocks.contains_key(&new_hash) {
60 return;
62 }
63
64 for (outpoint, ordered_utxo) in new.0.new_outputs.iter() {
66 self.known_utxos
67 .insert(*outpoint, ordered_utxo.utxo.clone());
68 }
69
70 self.blocks.insert(new_hash, new);
71 self.by_height
72 .entry(new_height)
73 .or_default()
74 .insert(new_hash);
75 self.by_parent
76 .entry(parent_hash)
77 .or_default()
78 .insert(new_hash);
79
80 tracing::trace!(%parent_hash, queued = %self.blocks.len(), "queued block");
81 self.update_metrics();
82 }
83
84 #[instrument(skip(self), fields(%parent_hash))]
86 pub fn has_queued_children(&self, parent_hash: block::Hash) -> bool {
87 self.by_parent.contains_key(&parent_hash)
88 }
89
90 #[instrument(skip(self), fields(%hash))]
92 pub fn has(&self, hash: block::Hash) -> bool {
93 self.blocks.contains_key(&hash)
94 }
95
96 #[instrument(skip(self), fields(%parent_hash))]
99 pub fn dequeue_children(
100 &mut self,
101 parent_hash: block::Hash,
102 ) -> Vec<QueuedSemanticallyVerified> {
103 let queued_children = self
104 .by_parent
105 .remove(&parent_hash)
106 .unwrap_or_default()
107 .into_iter()
108 .map(|hash| {
109 self.blocks
110 .remove(&hash)
111 .expect("block is present if its hash is in by_parent")
112 })
113 .collect::<Vec<_>>();
114
115 for queued in &queued_children {
116 if let Some(hashes) = self.by_height.get_mut(&queued.0.height) {
117 hashes.remove(&queued.0.hash);
118
119 if hashes.is_empty() {
120 self.by_height.remove(&queued.0.height);
121 }
122 }
123
124 for outpoint in queued.0.new_outputs.keys() {
127 self.known_utxos.remove(outpoint);
128 }
129 }
130
131 tracing::trace!(
132 dequeued = queued_children.len(),
133 remaining = self.blocks.len(),
134 "dequeued blocks"
135 );
136 self.update_metrics();
137
138 queued_children
139 }
140
141 #[instrument(skip(self))]
144 pub fn prune_by_height(&mut self, finalized_tip_height: block::Height) {
145 let split_height = finalized_tip_height + 1;
151 let split_height =
152 split_height.expect("height after finalized tip won't exceed max height");
153 let mut by_height = self.by_height.split_off(&split_height);
154 mem::swap(&mut self.by_height, &mut by_height);
155
156 for hash in by_height.into_values().flatten() {
157 let (expired_block, expired_sender) =
158 self.blocks.remove(&hash).expect("block is present");
159 let parent_hash = &expired_block.block.header.previous_block_hash;
160
161 let _ = expired_sender.send(Err(CommitBlockError::new_duplicate(
163 Some(expired_block.height.into()),
164 KnownBlock::Finalized,
165 )
166 .into()));
167
168 for outpoint in expired_block.new_outputs.keys() {
171 self.known_utxos.remove(outpoint);
172 }
173
174 let parent_list = self
175 .by_parent
176 .get_mut(parent_hash)
177 .expect("parent is present");
178
179 if parent_list.len() == 1 {
180 let removed = self
181 .by_parent
182 .remove(parent_hash)
183 .expect("parent is present");
184 assert!(
185 removed.contains(&hash),
186 "hash must be present in parent hash list"
187 );
188 } else {
189 assert!(
190 parent_list.remove(&hash),
191 "hash must be present in parent hash list"
192 );
193 }
194 }
195
196 tracing::trace!(num_blocks = %self.blocks.len(), "Finished pruning blocks at or beneath the finalized tip height");
197 self.update_metrics();
198 }
199
200 pub fn get_mut(&mut self, hash: &block::Hash) -> Option<&mut QueuedSemanticallyVerified> {
202 self.blocks.get_mut(hash)
203 }
204
205 fn update_metrics(&self) {
207 if let Some(min_height) = self.by_height.keys().next() {
208 metrics::gauge!("state.memory.queued.min.height").set(min_height.0 as f64);
209 } else {
210 metrics::gauge!("state.memory.queued.min.height").set(f64::NAN);
212 }
213 if let Some(max_height) = self.by_height.keys().next_back() {
214 metrics::gauge!("state.memory.queued.max.height").set(max_height.0 as f64);
215 } else {
216 metrics::gauge!("state.memory.queued.max.height").set(f64::NAN);
218 }
219
220 metrics::gauge!("state.memory.queued.block.count").set(self.blocks.len() as f64);
221 }
222
223 #[instrument(skip(self))]
225 pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option<transparent::Utxo> {
226 self.known_utxos.get(outpoint).cloned()
227 }
228
229 pub fn drain(&mut self) -> Drain<'_, block::Hash, QueuedSemanticallyVerified> {
234 self.known_utxos.clear();
235 self.known_utxos.shrink_to_fit();
236 self.by_parent.clear();
237 self.by_parent.shrink_to_fit();
238 self.by_height.clear();
239
240 self.blocks.drain()
241 }
242}
243
244#[derive(Debug, Default)]
245pub(crate) struct SentHashes {
246 bufs: Vec<VecDeque<(block::Hash, block::Height)>>,
249
250 curr_buf: VecDeque<(block::Hash, block::Height)>,
252
253 pub sent: HashMap<block::Hash, Vec<transparent::OutPoint>>,
256
257 known_utxos: HashMap<transparent::OutPoint, transparent::Utxo>,
259
260 pub(crate) can_fork_chain_at_hashes: bool,
263}
264
265impl SentHashes {
266 pub fn new(non_finalized_state: &NonFinalizedState) -> Self {
268 let mut sent_hashes = Self::default();
269 for (_, block) in non_finalized_state
270 .chain_iter()
271 .flat_map(|c| c.blocks.clone())
272 {
273 sent_hashes.add(&block.into());
274 }
275
276 if !sent_hashes.sent.is_empty() {
277 sent_hashes.can_fork_chain_at_hashes = true;
278 }
279
280 sent_hashes
281 }
282
283 pub fn add(&mut self, block: &SemanticallyVerifiedBlock) {
289 let outpoints = block
291 .new_outputs
292 .iter()
293 .map(|(outpoint, ordered_utxo)| {
294 self.known_utxos
295 .insert(*outpoint, ordered_utxo.utxo.clone());
296 outpoint
297 })
298 .cloned()
299 .collect();
300
301 self.curr_buf.push_back((block.hash, block.height));
302 self.sent.insert(block.hash, outpoints);
303
304 self.update_metrics_for_block(block.height);
305 }
306
307 pub fn add_finalized(&mut self, block: &CheckpointVerifiedBlock) {
318 let outpoints = block
320 .new_outputs
321 .iter()
322 .map(|(outpoint, ordered_utxo)| {
323 self.known_utxos
324 .insert(*outpoint, ordered_utxo.utxo.clone());
325 outpoint
326 })
327 .cloned()
328 .collect();
329
330 self.curr_buf.push_back((block.hash, block.height));
331 self.sent.insert(block.hash, outpoints);
332
333 self.update_metrics_for_block(block.height);
334 }
335
336 #[instrument(skip(self))]
338 pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option<transparent::Utxo> {
339 self.known_utxos.get(outpoint).cloned()
340 }
341
342 pub fn finish_batch(&mut self) {
344 if !self.curr_buf.is_empty() {
345 self.bufs.push(std::mem::take(&mut self.curr_buf));
346 }
347 }
348
349 pub fn prune_by_height(&mut self, height_bound: block::Height) {
356 self.finish_batch();
357
358 self.bufs.retain_mut(|buf| {
361 while let Some((hash, height)) = buf.pop_front() {
362 if height > height_bound {
363 buf.push_front((hash, height));
364 return true;
365 } else if let Some(expired_outpoints) = self.sent.remove(&hash) {
366 for outpoint in expired_outpoints.iter() {
369 self.known_utxos.remove(outpoint);
370 }
371 }
372 }
373
374 false
375 });
376
377 self.sent.shrink_to_fit();
378 self.known_utxos.shrink_to_fit();
379 self.bufs.shrink_to_fit();
380
381 self.update_metrics_for_cache();
382 }
383
384 pub fn contains(&self, hash: &block::Hash) -> bool {
386 self.sent.contains_key(hash)
387 }
388
389 pub fn remove(&mut self, hash: &block::Hash) {
396 let Some(outpoints) = self.sent.remove(hash) else {
397 return;
398 };
399
400 for outpoint in &outpoints {
401 self.known_utxos.remove(outpoint);
402 }
403
404 self.curr_buf.retain(|(h, _)| h != hash);
405 for buf in &mut self.bufs {
406 buf.retain(|(h, _)| h != hash);
407 }
408 }
409
410 pub fn can_fork_chain_at(&self, hash: &block::Hash) -> bool {
412 self.can_fork_chain_at_hashes && self.contains(hash)
413 }
414
415 fn update_metrics_for_block(&self, height: block::Height) {
417 metrics::counter!("state.memory.sent.block.count").increment(1);
418 metrics::gauge!("state.memory.sent.block.height").set(height.0 as f64);
419
420 self.update_metrics_for_cache();
421 }
422
423 fn update_metrics_for_cache(&self) {
425 let batch_iter = || self.bufs.iter().chain(iter::once(&self.curr_buf));
426
427 if let Some(min_height) = batch_iter()
428 .flat_map(|batch| batch.front().map(|(_hash, height)| height))
429 .min()
430 {
431 metrics::gauge!("state.memory.sent.cache.min.height").set(min_height.0 as f64);
432 } else {
433 metrics::gauge!("state.memory.sent.cache.min.height").set(f64::NAN);
435 }
436
437 if let Some(max_height) = batch_iter()
438 .flat_map(|batch| batch.back().map(|(_hash, height)| height))
439 .max()
440 {
441 metrics::gauge!("state.memory.sent.cache.max.height").set(max_height.0 as f64);
442 } else {
443 metrics::gauge!("state.memory.sent.cache.max.height").set(f64::NAN);
445 }
446
447 metrics::gauge!("state.memory.sent.cache.block.count")
448 .set(batch_iter().flatten().count() as f64);
449
450 metrics::gauge!("state.memory.sent.cache.batch.count").set(batch_iter().count() as f64);
451 }
452}