zebra_state/service/block_iter.rs
1//! Iterators for blocks in the non-finalized and finalized state.
2
3use std::{marker::PhantomData, sync::Arc};
4
5use zebra_chain::block::{self, Block, Height};
6
7use crate::{
8 service::{
9 finalized_state::ZebraDb,
10 non_finalized_state::{Chain, NonFinalizedState},
11 read,
12 },
13 HashOrHeight,
14};
15
16/// Generic state chain iterator, which iterates by block height or hash.
17/// Can be used for blocks, block headers, or any type indexed by [`HashOrHeight`].
18///
19/// Starts at any hash or height in any non-finalized or finalized chain,
20/// and iterates in reverse height order. (Towards the genesis block.)
21#[derive(Clone, Debug)]
22pub(crate) struct Iter<Item: ChainItem> {
23 /// The non-finalized chain fork we're iterating, if the iterator is in the non-finalized state.
24 ///
25 /// This is a cloned copy of a potentially out-of-date chain fork.
26 pub(super) chain: Option<Arc<Chain>>,
27
28 /// The finalized database we're iterating.
29 ///
30 /// This is the shared live database instance, which can concurrently write blocks.
31 pub(super) db: ZebraDb,
32
33 /// The height of the item which will be yielded by `Iterator::next()`.
34 pub(super) height: Option<Height>,
35
36 /// An internal marker type that tells the Rust type system what we're iterating.
37 iterable: PhantomData<Item::Type>,
38}
39
40impl<Item> Iter<Item>
41where
42 Item: ChainItem,
43{
44 /// Returns an item by height, and updates the iterator's internal state to point to the
45 /// previous height.
46 fn yield_by_height(&mut self) -> Option<Item::Type> {
47 let current_height = self.height?;
48
49 // TODO:
50 // Check if the root of the chain connects to the finalized state. Cloned chains can become
51 // disconnected if they are concurrently pruned by a finalized block from another chain
52 // fork. If that happens, the iterator is invalid and should stop returning items.
53 //
54 // Currently, we skip from the disconnected chain root to the previous height in the
55 // finalized state, which is usually ok, but could cause consensus or light wallet bugs.
56 let item = Item::read(self.chain.as_ref(), &self.db, current_height);
57
58 // The iterator is finished if the current height is genesis.
59 self.height = current_height.previous().ok();
60
61 // Drop the chain if we've finished using it.
62 if let Some(chain) = self.chain.as_ref() {
63 if let Some(height) = self.height {
64 if !chain.contains_block_height(height) {
65 std::mem::drop(self.chain.take());
66 }
67 } else {
68 std::mem::drop(self.chain.take());
69 }
70 }
71
72 item
73 }
74}
75
76impl<Item> Iterator for Iter<Item>
77where
78 Item: ChainItem,
79{
80 type Item = Item::Type;
81
82 fn next(&mut self) -> Option<Self::Item> {
83 self.yield_by_height()
84 }
85
86 fn size_hint(&self) -> (usize, Option<usize>) {
87 let len = self.len();
88 (len, Some(len))
89 }
90}
91
92impl<Item> ExactSizeIterator for Iter<Item>
93where
94 Item: ChainItem,
95{
96 fn len(&self) -> usize {
97 // Add one to the height for the genesis block.
98 //
99 // TODO:
100 // If the Item can skip heights, or return multiple items per block, we can't calculate
101 // its length using the block height. For example, subtree end height iterators, or
102 // transaction iterators.
103 //
104 // TODO:
105 // Check if the root of the chain connects to the finalized state. If that happens, the
106 // iterator is invalid and the length should be zero. See the comment in yield_by_height()
107 // for details.
108 self.height.map_or(0, |height| height.as_usize() + 1)
109 }
110}
111
112// TODO:
113// If the Item can return None before it gets to genesis, it is not fused. For example, subtree
114// end height iterators.
115impl<Item> std::iter::FusedIterator for Iter<Item> where Item: ChainItem {}
116
117/// A trait that implements iteration for a specific chain type.
118pub(crate) trait ChainItem {
119 type Type;
120
121 /// Read the `Type` at `height` from the non-finalized `chain` or finalized `db`.
122 fn read(chain: Option<&Arc<Chain>>, db: &ZebraDb, height: Height) -> Option<Self::Type>;
123}
124
125// Block iteration
126
127impl ChainItem for Block {
128 type Type = Arc<Block>;
129
130 fn read(chain: Option<&Arc<Chain>>, db: &ZebraDb, height: Height) -> Option<Self::Type> {
131 read::block(chain, db, height.into())
132 }
133}
134
135// Block header iteration
136
137impl ChainItem for block::Header {
138 type Type = Arc<block::Header>;
139
140 fn read(chain: Option<&Arc<Chain>>, db: &ZebraDb, height: Height) -> Option<Self::Type> {
141 read::block_header(chain, db, height.into())
142 }
143}
144
145/// Returns a block iterator over the relevant chain containing `hash`,
146/// in order from the largest height to genesis.
147///
148/// The block with `hash` is included in the iterator.
149/// `hash` can come from any chain or `db`.
150///
151/// Use [`any_chain_ancestor_iter()`] in new code.
152pub(crate) fn any_ancestor_blocks(
153 non_finalized_state: &NonFinalizedState,
154 db: &ZebraDb,
155 hash: block::Hash,
156) -> Iter<Block> {
157 any_chain_ancestor_iter(non_finalized_state, db, hash)
158}
159
160/// Returns a generic chain item iterator over the relevant chain containing `hash`,
161/// in order from the largest height to genesis.
162///
163/// The item with `hash` is included in the iterator.
164/// `hash` can come from any chain or `db`.
165pub(crate) fn any_chain_ancestor_iter<Item>(
166 non_finalized_state: &NonFinalizedState,
167 db: &ZebraDb,
168 hash: block::Hash,
169) -> Iter<Item>
170where
171 Item: ChainItem,
172{
173 // We need to look up the relevant chain, and the height for the hash.
174 let chain = non_finalized_state.find_chain(|chain| chain.contains_block_hash(hash));
175 let height = read::height_by_hash(chain.as_ref(), db, hash);
176
177 Iter {
178 chain,
179 db: db.clone(),
180 height,
181 iterable: PhantomData,
182 }
183}
184
185/// Returns a generic chain item iterator over a `chain` containing `hash_or_height`,
186/// in order from the largest height to genesis.
187///
188/// The item with `hash_or_height` is included in the iterator.
189/// `hash_or_height` must be in `chain` or `db`.
190#[allow(dead_code)]
191pub(crate) fn known_chain_ancestor_iter<Item>(
192 chain: Option<Arc<Chain>>,
193 db: &ZebraDb,
194 hash_or_height: HashOrHeight,
195) -> Iter<Item>
196where
197 Item: ChainItem,
198{
199 // We need to look up the height for the hash.
200 let height =
201 hash_or_height.height_or_else(|hash| read::height_by_hash(chain.as_ref(), db, hash));
202
203 Iter {
204 chain,
205 db: db.clone(),
206 height,
207 iterable: PhantomData,
208 }
209}