1mod tests;
5
6use std::{
7 collections::{BTreeMap, HashSet},
8 io,
9 ops::Deref,
10 sync::Arc,
11};
12
13use thiserror::Error;
14
15use crate::{
16 block::{Block, ChainHistoryMmrRootHash, Height},
17 fmt::SummaryDebug,
18 parameters::{Network, NetworkUpgrade},
19 primitives::zcash_history::{
20 BlockCommitmentTreeRoots, Entry, Tree, V1 as PreOrchard, V2 as OrchardOnward,
21 V3 as IronwoodOnward,
22 },
23};
24
25#[derive(Debug, Error)]
27#[non_exhaustive]
28#[allow(missing_docs)]
29pub enum HistoryTreeError {
30 #[error("zcash_history error: {inner:?}")]
31 #[non_exhaustive]
32 InnerError { inner: zcash_history::Error },
33
34 #[error("I/O error: {0}")]
35 IOError(#[from] io::Error),
36}
37
38impl PartialEq for HistoryTreeError {
39 fn eq(&self, other: &Self) -> bool {
40 format!("{self:?}") == format!("{other:?}")
43 }
44}
45
46impl Eq for HistoryTreeError {}
47
48#[derive(Debug)]
50enum InnerHistoryTree {
51 PreOrchard(Tree<PreOrchard>),
53 OrchardOnward(Tree<OrchardOnward>),
55 IronwoodOnward(Tree<IronwoodOnward>),
57}
58
59#[derive(Debug)]
62pub struct NonEmptyHistoryTree {
63 network: Network,
64 network_upgrade: NetworkUpgrade,
65 inner: InnerHistoryTree,
69 size: u32,
71 peaks: SummaryDebug<BTreeMap<u32, Entry>>,
74 current_height: Height,
76}
77
78impl NonEmptyHistoryTree {
79 pub fn from_cache(
84 network: &Network,
85 size: u32,
86 peaks: BTreeMap<u32, Entry>,
87 current_height: Height,
88 ) -> Result<Self, HistoryTreeError> {
89 let network_upgrade = NetworkUpgrade::current(network, current_height);
90 let inner = match network_upgrade {
91 NetworkUpgrade::Genesis
92 | NetworkUpgrade::BeforeOverwinter
93 | NetworkUpgrade::Overwinter
94 | NetworkUpgrade::Sapling
95 | NetworkUpgrade::Blossom => {
96 panic!("HistoryTree does not exist for pre-Heartwood upgrades")
97 }
98 NetworkUpgrade::Heartwood | NetworkUpgrade::Canopy => {
99 let tree = Tree::<PreOrchard>::new_from_cache(
100 network,
101 network_upgrade,
102 size,
103 &peaks,
104 &Default::default(),
105 )?;
106 InnerHistoryTree::PreOrchard(tree)
107 }
108 NetworkUpgrade::Nu5
109 | NetworkUpgrade::Nu6
110 | NetworkUpgrade::Nu6_1
111 | NetworkUpgrade::Nu6_2 => {
112 let tree = Tree::<OrchardOnward>::new_from_cache(
113 network,
114 network_upgrade,
115 size,
116 &peaks,
117 &Default::default(),
118 )?;
119 InnerHistoryTree::OrchardOnward(tree)
120 }
121 NetworkUpgrade::Nu6_3 | NetworkUpgrade::Nu7 => {
122 let tree = Tree::<IronwoodOnward>::new_from_cache(
123 network,
124 network_upgrade,
125 size,
126 &peaks,
127 &Default::default(),
128 )?;
129 InnerHistoryTree::IronwoodOnward(tree)
130 }
131
132 #[cfg(zcash_unstable = "zfuture")]
133 NetworkUpgrade::ZFuture => {
134 let tree = Tree::<IronwoodOnward>::new_from_cache(
135 network,
136 network_upgrade,
137 size,
138 &peaks,
139 &Default::default(),
140 )?;
141 InnerHistoryTree::IronwoodOnward(tree)
142 }
143 };
144 Ok(Self {
145 network: network.clone(),
146 network_upgrade,
147 inner,
148 size,
149 peaks: peaks.into(),
150 current_height,
151 })
152 }
153
154 #[allow(clippy::unwrap_in_result)]
159 pub fn from_block(
160 network: &Network,
161 block: Arc<Block>,
162 roots: BlockCommitmentTreeRoots,
163 ) -> Result<Self, HistoryTreeError> {
164 let height = block
165 .coinbase_height()
166 .expect("block must have coinbase height during contextual verification");
167 let network_upgrade = NetworkUpgrade::current(network, height);
168 let (tree, entry) = match network_upgrade {
169 NetworkUpgrade::Genesis
170 | NetworkUpgrade::BeforeOverwinter
171 | NetworkUpgrade::Overwinter
172 | NetworkUpgrade::Sapling
173 | NetworkUpgrade::Blossom => {
174 panic!("HistoryTree does not exist for pre-Heartwood upgrades")
175 }
176 NetworkUpgrade::Heartwood | NetworkUpgrade::Canopy => {
177 let (tree, entry) = Tree::<PreOrchard>::new_from_block(network, block, roots)?;
178 (InnerHistoryTree::PreOrchard(tree), entry)
179 }
180 NetworkUpgrade::Nu5
181 | NetworkUpgrade::Nu6
182 | NetworkUpgrade::Nu6_1
183 | NetworkUpgrade::Nu6_2 => {
184 let (tree, entry) = Tree::<OrchardOnward>::new_from_block(network, block, roots)?;
185 (InnerHistoryTree::OrchardOnward(tree), entry)
186 }
187 NetworkUpgrade::Nu6_3 | NetworkUpgrade::Nu7 => {
188 let (tree, entry) = Tree::<IronwoodOnward>::new_from_block(network, block, roots)?;
189 (InnerHistoryTree::IronwoodOnward(tree), entry)
190 }
191
192 #[cfg(zcash_unstable = "zfuture")]
193 NetworkUpgrade::ZFuture => {
194 let (tree, entry) = Tree::<IronwoodOnward>::new_from_block(network, block, roots)?;
195 (InnerHistoryTree::IronwoodOnward(tree), entry)
196 }
197 };
198 let mut peaks = BTreeMap::new();
199 peaks.insert(0u32, entry);
200 Ok(NonEmptyHistoryTree {
201 network: network.clone(),
202 network_upgrade,
203 inner: tree,
204 size: 1,
205 peaks: peaks.into(),
206 current_height: height,
207 })
208 }
209
210 #[allow(clippy::unwrap_in_result)]
219 pub fn push(
220 &mut self,
221 block: Arc<Block>,
222 roots: BlockCommitmentTreeRoots,
223 ) -> Result<(), HistoryTreeError> {
224 let height = block
228 .coinbase_height()
229 .expect("block must have coinbase height during contextual verification");
230
231 assert!(
232 Some(height) == self.current_height + 1,
233 "added block with height {:?} but it must be {:?}+1",
234 height,
235 self.current_height
236 );
237
238 let network_upgrade = NetworkUpgrade::current(&self.network, height);
239 if network_upgrade != self.network_upgrade {
240 let new_tree = Self::from_block(&self.network, block, roots)?;
243 *self = new_tree;
245 assert_eq!(self.network_upgrade, network_upgrade);
246 return Ok(());
247 }
248
249 let new_entries = match &mut self.inner {
250 InnerHistoryTree::PreOrchard(tree) => tree
251 .append_leaf(block, roots)
252 .map_err(|e| HistoryTreeError::InnerError { inner: e })?,
253 InnerHistoryTree::OrchardOnward(tree) => tree
254 .append_leaf(block, roots)
255 .map_err(|e| HistoryTreeError::InnerError { inner: e })?,
256 InnerHistoryTree::IronwoodOnward(tree) => tree
257 .append_leaf(block, roots)
258 .map_err(|e| HistoryTreeError::InnerError { inner: e })?,
259 };
260 for entry in new_entries {
261 self.peaks.insert(self.size, entry);
263 self.size += 1;
264 }
265 self.prune()?;
266 self.current_height = height;
267 Ok(())
268 }
269
270 pub fn try_extend<'a, T: IntoIterator<Item = (Arc<Block>, BlockCommitmentTreeRoots<'a>)>>(
272 &mut self,
273 iter: T,
274 ) -> Result<(), HistoryTreeError> {
275 for (block, roots) in iter {
276 self.push(block, roots)?;
277 }
278 Ok(())
279 }
280
281 fn prune(&mut self) -> Result<(), io::Error> {
283 let mut peak_pos_set = HashSet::new();
290
291 let mut alt = (32 - (self.size + 1).leading_zeros() - 1) - 1;
306
307 let mut peak_pos = (1 << (alt + 1)) - 2;
312
313 loop {
330 if peak_pos >= self.size {
333 peak_pos -= 1 << alt;
335 alt -= 1;
336 }
337
338 if peak_pos < self.size {
340 peak_pos_set.insert(peak_pos);
342
343 peak_pos = peak_pos + (1 << (alt + 1)) - 1;
345 }
346
347 if alt == 0 {
348 break;
349 }
350 }
351
352 self.peaks.retain(|k, _| peak_pos_set.contains(k));
354 self.inner = self.rebuilt_inner()?;
356 Ok(())
357 }
358
359 fn rebuilt_inner(&self) -> Result<InnerHistoryTree, io::Error> {
364 Ok(match &self.inner {
365 InnerHistoryTree::PreOrchard(_) => {
366 InnerHistoryTree::PreOrchard(Tree::<PreOrchard>::new_from_cache(
367 &self.network,
368 self.network_upgrade,
369 self.size,
370 &self.peaks,
371 &Default::default(),
372 )?)
373 }
374 InnerHistoryTree::OrchardOnward(_) => {
375 InnerHistoryTree::OrchardOnward(Tree::<OrchardOnward>::new_from_cache(
376 &self.network,
377 self.network_upgrade,
378 self.size,
379 &self.peaks,
380 &Default::default(),
381 )?)
382 }
383 InnerHistoryTree::IronwoodOnward(_) => {
384 InnerHistoryTree::IronwoodOnward(Tree::<IronwoodOnward>::new_from_cache(
385 &self.network,
386 self.network_upgrade,
387 self.size,
388 &self.peaks,
389 &Default::default(),
390 )?)
391 }
392 })
393 }
394
395 pub fn hash(&self) -> ChainHistoryMmrRootHash {
397 match &self.inner {
398 InnerHistoryTree::PreOrchard(tree) => tree.hash(),
399 InnerHistoryTree::OrchardOnward(tree) => tree.hash(),
400 InnerHistoryTree::IronwoodOnward(tree) => tree.hash(),
401 }
402 }
403
404 pub fn peaks(&self) -> &BTreeMap<u32, Entry> {
406 &self.peaks
407 }
408
409 pub fn size(&self) -> u32 {
411 self.size
412 }
413
414 pub fn current_height(&self) -> Height {
416 self.current_height
417 }
418
419 pub fn network(&self) -> &Network {
421 &self.network
422 }
423}
424
425impl Clone for NonEmptyHistoryTree {
426 fn clone(&self) -> Self {
427 let tree = self
428 .rebuilt_inner()
429 .expect("rebuilding an existing tree should always work");
430 NonEmptyHistoryTree {
431 network: self.network.clone(),
432 network_upgrade: self.network_upgrade,
433 inner: tree,
434 size: self.size,
435 peaks: self.peaks.clone(),
436 current_height: self.current_height,
437 }
438 }
439}
440
441#[derive(Debug, Default, Clone)]
444pub struct HistoryTree(Option<NonEmptyHistoryTree>);
445
446impl HistoryTree {
447 #[allow(clippy::unwrap_in_result)]
451 pub fn from_block(
452 network: &Network,
453 block: Arc<Block>,
454 roots: BlockCommitmentTreeRoots,
455 ) -> Result<Self, HistoryTreeError> {
456 let Some(heartwood_height) = NetworkUpgrade::Heartwood.activation_height(network) else {
457 return Ok(HistoryTree(None));
459 };
460
461 match block
462 .coinbase_height()
463 .expect("must have height")
464 .cmp(&heartwood_height)
465 {
466 std::cmp::Ordering::Less => Ok(HistoryTree(None)),
467 _ => Ok(NonEmptyHistoryTree::from_block(network, block, roots)?.into()),
468 }
469 }
470
471 #[allow(clippy::unwrap_in_result)]
476 pub fn push(
477 &mut self,
478 network: &Network,
479 block: Arc<Block>,
480 roots: BlockCommitmentTreeRoots,
481 ) -> Result<(), HistoryTreeError> {
482 let Some(heartwood_height) = NetworkUpgrade::Heartwood.activation_height(network) else {
483 assert!(
484 self.0.is_none(),
485 "history tree must not exist pre-Heartwood"
486 );
487
488 return Ok(());
489 };
490
491 match block
492 .coinbase_height()
493 .expect("must have height")
494 .cmp(&heartwood_height)
495 {
496 std::cmp::Ordering::Less => {
497 assert!(
498 self.0.is_none(),
499 "history tree must not exist pre-Heartwood"
500 );
501 }
502 std::cmp::Ordering::Equal => {
503 let tree = Some(NonEmptyHistoryTree::from_block(network, block, roots)?);
504 *self = HistoryTree(tree);
506 }
507 std::cmp::Ordering::Greater => {
508 self.0
509 .as_mut()
510 .expect("history tree must exist Heartwood-onward")
511 .push(block.clone(), roots)?;
512 }
513 };
514 Ok(())
515 }
516
517 pub fn hash(&self) -> Option<ChainHistoryMmrRootHash> {
519 Some(self.0.as_ref()?.hash())
520 }
521}
522
523impl From<NonEmptyHistoryTree> for HistoryTree {
524 fn from(tree: NonEmptyHistoryTree) -> Self {
525 HistoryTree(Some(tree))
526 }
527}
528
529impl From<Option<NonEmptyHistoryTree>> for HistoryTree {
530 fn from(tree: Option<NonEmptyHistoryTree>) -> Self {
531 HistoryTree(tree)
532 }
533}
534
535impl Deref for HistoryTree {
536 type Target = Option<NonEmptyHistoryTree>;
537 fn deref(&self) -> &Self::Target {
538 &self.0
539 }
540}
541
542impl PartialEq for HistoryTree {
543 fn eq(&self, other: &Self) -> bool {
544 self.as_ref().map(|tree| tree.hash()) == other.as_ref().map(|other_tree| other_tree.hash())
545 }
546}
547
548impl Eq for HistoryTree {}