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(%parent_hash))]
93 pub fn dequeue_children(
94 &mut self,
95 parent_hash: block::Hash,
96 ) -> Vec<QueuedSemanticallyVerified> {
97 let queued_children = self
98 .by_parent
99 .remove(&parent_hash)
100 .unwrap_or_default()
101 .into_iter()
102 .map(|hash| {
103 self.blocks
104 .remove(&hash)
105 .expect("block is present if its hash is in by_parent")
106 })
107 .collect::<Vec<_>>();
108
109 for queued in &queued_children {
110 if let Some(hashes) = self.by_height.get_mut(&queued.0.height) {
111 hashes.remove(&queued.0.hash);
112
113 if hashes.is_empty() {
114 self.by_height.remove(&queued.0.height);
115 }
116 }
117
118 for outpoint in queued.0.new_outputs.keys() {
121 self.known_utxos.remove(outpoint);
122 }
123 }
124
125 tracing::trace!(
126 dequeued = queued_children.len(),
127 remaining = self.blocks.len(),
128 "dequeued blocks"
129 );
130 self.update_metrics();
131
132 queued_children
133 }
134
135 #[instrument(skip(self))]
138 pub fn prune_by_height(&mut self, finalized_tip_height: block::Height) {
139 let split_height = finalized_tip_height + 1;
145 let split_height =
146 split_height.expect("height after finalized tip won't exceed max height");
147 let mut by_height = self.by_height.split_off(&split_height);
148 mem::swap(&mut self.by_height, &mut by_height);
149
150 for hash in by_height.into_values().flatten() {
151 let (expired_block, expired_sender) =
152 self.blocks.remove(&hash).expect("block is present");
153 let parent_hash = &expired_block.block.header.previous_block_hash;
154
155 let _ = expired_sender.send(Err(CommitBlockError::new_duplicate(
157 Some(expired_block.height.into()),
158 KnownBlock::Finalized,
159 )
160 .into()));
161
162 for outpoint in expired_block.new_outputs.keys() {
165 self.known_utxos.remove(outpoint);
166 }
167
168 let parent_list = self
169 .by_parent
170 .get_mut(parent_hash)
171 .expect("parent is present");
172
173 if parent_list.len() == 1 {
174 let removed = self
175 .by_parent
176 .remove(parent_hash)
177 .expect("parent is present");
178 assert!(
179 removed.contains(&hash),
180 "hash must be present in parent hash list"
181 );
182 } else {
183 assert!(
184 parent_list.remove(&hash),
185 "hash must be present in parent hash list"
186 );
187 }
188 }
189
190 tracing::trace!(num_blocks = %self.blocks.len(), "Finished pruning blocks at or beneath the finalized tip height");
191 self.update_metrics();
192 }
193
194 pub fn get_mut(&mut self, hash: &block::Hash) -> Option<&mut QueuedSemanticallyVerified> {
196 self.blocks.get_mut(hash)
197 }
198
199 fn update_metrics(&self) {
201 if let Some(min_height) = self.by_height.keys().next() {
202 metrics::gauge!("state.memory.queued.min.height").set(min_height.0 as f64);
203 } else {
204 metrics::gauge!("state.memory.queued.min.height").set(f64::NAN);
206 }
207 if let Some(max_height) = self.by_height.keys().next_back() {
208 metrics::gauge!("state.memory.queued.max.height").set(max_height.0 as f64);
209 } else {
210 metrics::gauge!("state.memory.queued.max.height").set(f64::NAN);
212 }
213
214 metrics::gauge!("state.memory.queued.block.count").set(self.blocks.len() as f64);
215 }
216
217 #[instrument(skip(self))]
219 pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option<transparent::Utxo> {
220 self.known_utxos.get(outpoint).cloned()
221 }
222
223 pub fn drain(&mut self) -> Drain<'_, block::Hash, QueuedSemanticallyVerified> {
228 self.known_utxos.clear();
229 self.known_utxos.shrink_to_fit();
230 self.by_parent.clear();
231 self.by_parent.shrink_to_fit();
232 self.by_height.clear();
233
234 self.blocks.drain()
235 }
236}
237
238#[derive(Debug, Default)]
239pub(crate) struct SentHashes {
240 bufs: Vec<VecDeque<(block::Hash, block::Height)>>,
243
244 curr_buf: VecDeque<(block::Hash, block::Height)>,
246
247 pub sent: HashMap<block::Hash, Vec<transparent::OutPoint>>,
250
251 known_utxos: HashMap<transparent::OutPoint, transparent::Utxo>,
253
254 pub(crate) can_fork_chain_at_hashes: bool,
257}
258
259impl SentHashes {
260 pub fn new(non_finalized_state: &NonFinalizedState) -> Self {
262 let mut sent_hashes = Self::default();
263 for (_, block) in non_finalized_state
264 .chain_iter()
265 .flat_map(|c| c.blocks.clone())
266 {
267 sent_hashes.add(&block.into());
268 }
269
270 if !sent_hashes.sent.is_empty() {
271 sent_hashes.can_fork_chain_at_hashes = true;
272 }
273
274 sent_hashes
275 }
276
277 pub fn add(&mut self, block: &SemanticallyVerifiedBlock) {
283 let outpoints = block
285 .new_outputs
286 .iter()
287 .map(|(outpoint, ordered_utxo)| {
288 self.known_utxos
289 .insert(*outpoint, ordered_utxo.utxo.clone());
290 outpoint
291 })
292 .cloned()
293 .collect();
294
295 self.curr_buf.push_back((block.hash, block.height));
296 self.sent.insert(block.hash, outpoints);
297
298 self.update_metrics_for_block(block.height);
299 }
300
301 pub fn add_finalized(&mut self, block: &CheckpointVerifiedBlock) {
312 let outpoints = block
314 .new_outputs
315 .iter()
316 .map(|(outpoint, ordered_utxo)| {
317 self.known_utxos
318 .insert(*outpoint, ordered_utxo.utxo.clone());
319 outpoint
320 })
321 .cloned()
322 .collect();
323
324 self.curr_buf.push_back((block.hash, block.height));
325 self.sent.insert(block.hash, outpoints);
326
327 self.update_metrics_for_block(block.height);
328 }
329
330 #[instrument(skip(self))]
332 pub fn utxo(&self, outpoint: &transparent::OutPoint) -> Option<transparent::Utxo> {
333 self.known_utxos.get(outpoint).cloned()
334 }
335
336 pub fn finish_batch(&mut self) {
338 if !self.curr_buf.is_empty() {
339 self.bufs.push(std::mem::take(&mut self.curr_buf));
340 }
341 }
342
343 pub fn prune_by_height(&mut self, height_bound: block::Height) {
350 self.finish_batch();
351
352 self.bufs.retain_mut(|buf| {
355 while let Some((hash, height)) = buf.pop_front() {
356 if height > height_bound {
357 buf.push_front((hash, height));
358 return true;
359 } else if let Some(expired_outpoints) = self.sent.remove(&hash) {
360 for outpoint in expired_outpoints.iter() {
363 self.known_utxos.remove(outpoint);
364 }
365 }
366 }
367
368 false
369 });
370
371 self.sent.shrink_to_fit();
372 self.known_utxos.shrink_to_fit();
373 self.bufs.shrink_to_fit();
374
375 self.update_metrics_for_cache();
376 }
377
378 pub fn contains(&self, hash: &block::Hash) -> bool {
380 self.sent.contains_key(hash)
381 }
382
383 pub fn remove(&mut self, hash: &block::Hash) {
390 let Some(outpoints) = self.sent.remove(hash) else {
391 return;
392 };
393
394 for outpoint in &outpoints {
395 self.known_utxos.remove(outpoint);
396 }
397
398 self.curr_buf.retain(|(h, _)| h != hash);
399 for buf in &mut self.bufs {
400 buf.retain(|(h, _)| h != hash);
401 }
402 }
403
404 pub fn can_fork_chain_at(&self, hash: &block::Hash) -> bool {
406 self.can_fork_chain_at_hashes && self.contains(hash)
407 }
408
409 fn update_metrics_for_block(&self, height: block::Height) {
411 metrics::counter!("state.memory.sent.block.count").increment(1);
412 metrics::gauge!("state.memory.sent.block.height").set(height.0 as f64);
413
414 self.update_metrics_for_cache();
415 }
416
417 fn update_metrics_for_cache(&self) {
419 let batch_iter = || self.bufs.iter().chain(iter::once(&self.curr_buf));
420
421 if let Some(min_height) = batch_iter()
422 .flat_map(|batch| batch.front().map(|(_hash, height)| height))
423 .min()
424 {
425 metrics::gauge!("state.memory.sent.cache.min.height").set(min_height.0 as f64);
426 } else {
427 metrics::gauge!("state.memory.sent.cache.min.height").set(f64::NAN);
429 }
430
431 if let Some(max_height) = batch_iter()
432 .flat_map(|batch| batch.back().map(|(_hash, height)| height))
433 .max()
434 {
435 metrics::gauge!("state.memory.sent.cache.max.height").set(max_height.0 as f64);
436 } else {
437 metrics::gauge!("state.memory.sent.cache.max.height").set(f64::NAN);
439 }
440
441 metrics::gauge!("state.memory.sent.cache.block.count")
442 .set(batch_iter().flatten().count() as f64);
443
444 metrics::gauge!("state.memory.sent.cache.batch.count").set(batch_iter().count() as f64);
445 }
446}