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}