1use std::collections::{BTreeMap, BTreeSet, HashMap};
4use std::ops::{Range, RangeInclusive};
5
6use tokio::sync::mpsc;
7use zip32::DiversifierIndex;
8
9use incrementalmerkletree::Hashable;
10use orchard::tree::MerkleHashOrchard;
11use shardtree::ShardTree;
12use shardtree::store::memory::MemoryShardStore;
13use shardtree::store::{Checkpoint, ShardStore, TreeState};
14use zcash_client_backend::data_api::anchor_retention::AnchorRetention;
15use zcash_keys::keys::UnifiedFullViewingKey;
16use zcash_primitives::transaction::TxId;
17use zcash_protocol::consensus::{self, BlockHeight};
18use zcash_protocol::{PoolType, ShieldedPool};
19use zip32::AccountId;
20
21use crate::error::{ServerError, SyncError};
22use crate::keys::transparent::TransparentAddressId;
23use crate::reset_spends;
24use crate::shardtree_ext::{RollbackOutcome, ShardTreeExt};
25use crate::sync::truncate::{PoolTruncation, plan_pool_truncation, tree_facts};
26use crate::sync::{MAX_SHARDTREE_CHECKPOINTS, ScanRange};
27use crate::wallet::{
28 Ironwood, NullifierMap, Orchard, OutputId, Sapling, ShardTrees, SyncState, WalletBlock,
29 WalletTransaction, empty_shard_tree,
30};
31use crate::witness::LocatedTreeData;
32use crate::{SyncDomain, client, sync::set_transactions_failed_unchecked};
33
34use super::{FetchRequest, ScanTarget, witness};
35
36pub trait SyncWallet {
38 type Error: std::fmt::Debug + std::fmt::Display + std::error::Error;
40
41 fn get_birthday(&self) -> Result<BlockHeight, Self::Error>;
43
44 fn get_sync_state(&self) -> Result<&SyncState, Self::Error>;
46
47 fn get_sync_state_mut(&mut self) -> Result<&mut SyncState, Self::Error>;
49
50 fn get_unified_full_viewing_keys(
52 &self,
53 ) -> Result<HashMap<AccountId, UnifiedFullViewingKey>, Self::Error>;
54
55 fn add_orchard_address(
57 &mut self,
58 account_id: zip32::AccountId,
59 address: orchard::Address,
60 diversifier_index: DiversifierIndex,
61 ) -> Result<(), Self::Error>;
62
63 fn add_sapling_address(
65 &mut self,
66 account_id: zip32::AccountId,
67 address: sapling_crypto::PaymentAddress,
68 diversifier_index: DiversifierIndex,
69 ) -> Result<(), Self::Error>;
70
71 fn get_transparent_addresses(
75 &self,
76 ) -> Result<&BTreeMap<TransparentAddressId, String>, Self::Error>;
77
78 fn get_transparent_addresses_mut(
82 &mut self,
83 ) -> Result<&mut BTreeMap<TransparentAddressId, String>, Self::Error>;
84
85 fn set_save_flag(&mut self) -> Result<(), Self::Error> {
89 Ok(())
90 }
91}
92
93pub trait SyncBlocks: SyncWallet {
95 fn get_wallet_block(&self, block_height: BlockHeight) -> Result<WalletBlock, Self::Error>;
99
100 fn get_wallet_blocks_mut(
102 &mut self,
103 ) -> Result<&mut BTreeMap<BlockHeight, WalletBlock>, Self::Error>;
104
105 fn append_wallet_blocks(
107 &mut self,
108 mut wallet_blocks: BTreeMap<BlockHeight, WalletBlock>,
109 ) -> Result<(), Self::Error> {
110 self.get_wallet_blocks_mut()?.append(&mut wallet_blocks);
111
112 Ok(())
113 }
114
115 fn truncate_wallet_blocks(&mut self, truncate_height: BlockHeight) -> Result<(), Self::Error> {
117 self.get_wallet_blocks_mut()?
118 .retain(|block_height, _| *block_height <= truncate_height);
119
120 Ok(())
121 }
122}
123
124pub trait SyncTransactions: SyncWallet {
126 fn get_wallet_transactions(&self) -> Result<&HashMap<TxId, WalletTransaction>, Self::Error>;
128
129 fn get_wallet_transactions_mut(
131 &mut self,
132 ) -> Result<&mut HashMap<TxId, WalletTransaction>, Self::Error>;
133
134 fn insert_wallet_transaction(
136 &mut self,
137 wallet_transaction: WalletTransaction,
138 ) -> Result<(), Self::Error> {
139 self.get_wallet_transactions_mut()?
140 .insert(wallet_transaction.txid(), wallet_transaction);
141
142 Ok(())
143 }
144
145 fn extend_wallet_transactions(
149 &mut self,
150 wallet_transactions: HashMap<TxId, WalletTransaction>,
151 ) -> Result<(), Self::Error> {
152 self.get_wallet_transactions_mut()?
153 .extend(wallet_transactions);
154
155 Ok(())
156 }
157
158 fn truncate_wallet_transactions(
162 &mut self,
163 truncate_height: BlockHeight,
164 set_truncated_transactions_failed: bool,
165 ) -> Result<(), Self::Error> {
166 let invalid_txids: Vec<TxId> = self
167 .get_wallet_transactions()?
168 .values()
169 .filter(|tx| tx.status().is_confirmed_after(&truncate_height))
170 .map(|tx| tx.transaction().txid())
171 .collect();
172
173 if set_truncated_transactions_failed {
174 set_transactions_failed_unchecked(self.get_wallet_transactions_mut()?, invalid_txids);
175 } else {
176 let wallet_transactions = self.get_wallet_transactions_mut()?;
177 wallet_transactions.retain(|txid, _| !invalid_txids.contains(txid));
178 reset_spends(wallet_transactions, invalid_txids);
179 }
180
181 Ok(())
182 }
183}
184
185pub trait SyncNullifiers: SyncWallet {
187 fn get_nullifiers(&self) -> Result<&NullifierMap, Self::Error>;
189
190 fn get_nullifiers_mut(&mut self) -> Result<&mut NullifierMap, Self::Error>;
192
193 fn append_nullifiers(&mut self, nullifiers: &mut NullifierMap) -> Result<(), Self::Error> {
195 self.get_nullifiers_mut()?
196 .sapling
197 .append(&mut nullifiers.sapling);
198 self.get_nullifiers_mut()?
199 .orchard
200 .append(&mut nullifiers.orchard);
201 self.get_nullifiers_mut()?
202 .ironwood
203 .append(&mut nullifiers.ironwood);
204
205 Ok(())
206 }
207
208 fn truncate_nullifiers(&mut self, truncate_height: BlockHeight) -> Result<(), Self::Error> {
210 let nullifier_map = self.get_nullifiers_mut()?;
211 nullifier_map
212 .sapling
213 .retain(|_, scan_target| scan_target.block_height() <= truncate_height);
214 nullifier_map
215 .orchard
216 .retain(|_, scan_target| scan_target.block_height() <= truncate_height);
217 nullifier_map
218 .ironwood
219 .retain(|_, scan_target| scan_target.block_height() <= truncate_height);
220
221 Ok(())
222 }
223}
224
225pub trait SyncOutPoints: SyncWallet {
227 fn get_outpoints(&self) -> Result<&BTreeMap<OutputId, ScanTarget>, Self::Error>;
229
230 fn get_outpoints_mut(&mut self) -> Result<&mut BTreeMap<OutputId, ScanTarget>, Self::Error>;
232
233 fn append_outpoints(
235 &mut self,
236 outpoints: &mut BTreeMap<OutputId, ScanTarget>,
237 ) -> Result<(), Self::Error> {
238 self.get_outpoints_mut()?.append(outpoints);
239
240 Ok(())
241 }
242
243 fn truncate_outpoints(&mut self, truncate_height: BlockHeight) -> Result<(), Self::Error> {
245 self.get_outpoints_mut()?
246 .retain(|_, scan_target| scan_target.block_height() <= truncate_height);
247
248 Ok(())
249 }
250}
251
252pub trait SyncShardTrees: SyncWallet {
254 fn get_shard_trees(&self) -> Result<&ShardTrees, Self::Error>;
256
257 fn get_shard_trees_mut(&mut self) -> Result<&mut ShardTrees, Self::Error>;
259
260 #[allow(clippy::too_many_arguments)]
264 fn update_shard_trees(
265 &mut self,
266 consensus_parameters: &(impl consensus::Parameters + Sync),
267 fetch_request_sender: mpsc::UnboundedSender<FetchRequest>,
268 scan_range: &ScanRange,
269 highest_scanned_height: BlockHeight,
270 anchor_retention: Option<AnchorRetention>,
271 sapling_located_trees: Vec<LocatedTreeData<sapling_crypto::Node>>,
272 orchard_located_trees: Vec<LocatedTreeData<MerkleHashOrchard>>,
273 ironwood_located_trees: Vec<LocatedTreeData<MerkleHashOrchard>>,
274 ) -> impl std::future::Future<Output = Result<(), SyncError<Self::Error>>> + Send
275 where
276 Self: std::marker::Send,
277 {
278 async move {
279 let shard_trees = self.get_shard_trees_mut().map_err(SyncError::WalletError)?;
280
281 let checkpoint_range = if scan_range.block_range().start > highest_scanned_height {
285 let verification_window_start = scan_range
286 .block_range()
287 .end
288 .saturating_sub(MAX_SHARDTREE_CHECKPOINTS);
289
290 std::cmp::max(scan_range.block_range().start, verification_window_start)
291 ..scan_range.block_range().end
292 } else if scan_range.block_range().end
293 > highest_scanned_height.saturating_sub(MAX_SHARDTREE_CHECKPOINTS) + 1
294 {
295 let verification_window_start =
296 highest_scanned_height.saturating_sub(MAX_SHARDTREE_CHECKPOINTS) + 1;
297
298 std::cmp::max(scan_range.block_range().start, verification_window_start)
299 ..scan_range.block_range().end
300 } else {
301 BlockHeight::from_u32(0)..BlockHeight::from_u32(0)
302 };
303
304 let anchor_retention_window = anchor_retention.as_ref().map(|retention| {
308 let as_of = std::cmp::max(highest_scanned_height, scan_range.block_range().end - 1);
309 (
310 retention,
311 witness::anchor_retention_window(retention, as_of),
312 )
313 });
314 let checkpoint_heights = checkpoint_heights(
315 scan_range.block_range(),
316 &checkpoint_range,
317 anchor_retention_window
318 .as_ref()
319 .map(|(retention, window)| (*retention, window)),
320 );
321
322 let mut sapling_checkpoints = BTreeMap::new();
325 let mut orchard_checkpoints = BTreeMap::new();
326 let mut ironwood_checkpoints = BTreeMap::new();
327 for checkpoint_height in checkpoint_heights {
328 let sapling_checkpoint = determine_checkpoint::<
329 Sapling,
330 sapling_crypto::Node,
331 { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH },
332 { witness::SHARD_HEIGHT },
333 >(
334 consensus_parameters,
335 fetch_request_sender.clone(),
336 checkpoint_height,
337 &sapling_located_trees,
338 &shard_trees.sapling,
339 &sapling_checkpoints,
340 )
341 .await?;
342 sapling_checkpoints.insert(checkpoint_height, sapling_checkpoint);
343 let orchard_checkpoint = determine_checkpoint::<
344 Orchard,
345 MerkleHashOrchard,
346 { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
347 { witness::SHARD_HEIGHT },
348 >(
349 consensus_parameters,
350 fetch_request_sender.clone(),
351 checkpoint_height,
352 &orchard_located_trees,
353 &shard_trees.orchard,
354 &orchard_checkpoints,
355 )
356 .await?;
357 orchard_checkpoints.insert(checkpoint_height, orchard_checkpoint);
358 let ironwood_checkpoint = determine_checkpoint::<
359 Ironwood,
360 MerkleHashOrchard,
361 { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
362 { witness::SHARD_HEIGHT },
363 >(
364 consensus_parameters,
365 fetch_request_sender.clone(),
366 checkpoint_height,
367 &ironwood_located_trees,
368 &shard_trees.ironwood,
369 &ironwood_checkpoints,
370 )
371 .await?;
372 ironwood_checkpoints.insert(checkpoint_height, ironwood_checkpoint);
373 }
374
375 if let Some((retention, window)) = &anchor_retention_window {
376 witness::repin_anchor_checkpoints(
377 retention,
378 window,
379 shard_trees.sapling.store_mut(),
380 );
381 witness::repin_anchor_checkpoints(
382 retention,
383 window,
384 shard_trees.orchard.store_mut(),
385 );
386 witness::repin_anchor_checkpoints(
387 retention,
388 window,
389 shard_trees.ironwood.store_mut(),
390 );
391 }
392 for (checkpoint_height, checkpoint) in sapling_checkpoints {
393 shard_trees
394 .sapling
395 .store_mut()
396 .add_checkpoint(checkpoint_height, checkpoint)
397 .expect("infallible");
398 }
399 for (checkpoint_height, checkpoint) in orchard_checkpoints {
400 shard_trees
401 .orchard
402 .store_mut()
403 .add_checkpoint(checkpoint_height, checkpoint)
404 .expect("infallible");
405 }
406 for (checkpoint_height, checkpoint) in ironwood_checkpoints {
407 shard_trees
408 .ironwood
409 .store_mut()
410 .add_checkpoint(checkpoint_height, checkpoint)
411 .expect("infallible");
412 }
413
414 for tree in sapling_located_trees {
416 shard_trees
417 .sapling
418 .insert_tree(tree.subtree, tree.checkpoints)?;
419 }
420 for tree in orchard_located_trees {
421 shard_trees
422 .orchard
423 .insert_tree(tree.subtree, tree.checkpoints)?;
424 }
425 for tree in ironwood_located_trees {
426 shard_trees
427 .ironwood
428 .insert_tree(tree.subtree, tree.checkpoints)?;
429 }
430
431 Ok(())
432 }
433 }
434
435 fn clear_shard_trees(&mut self) -> Result<(), SyncError<Self::Error>> {
437 let shard_trees = self.get_shard_trees_mut().map_err(SyncError::WalletError)?;
438 tracing::info!("Clearing shard trees.");
439 shard_trees.sapling = empty_shard_tree();
440 shard_trees.orchard = empty_shard_tree();
441 shard_trees.ironwood = empty_shard_tree();
442
443 Ok(())
444 }
445
446 fn truncate_shard_trees(
455 &mut self,
456 truncate_height: BlockHeight,
457 ) -> Result<(), SyncError<Self::Error>> {
458 let shard_trees = self.get_shard_trees_mut().map_err(SyncError::WalletError)?;
459 truncate_pool_tree(&mut shard_trees.sapling, truncate_height, PoolType::SAPLING)?;
460 truncate_pool_tree(&mut shard_trees.orchard, truncate_height, PoolType::ORCHARD)?;
461 truncate_pool_tree(
462 &mut shard_trees.ironwood,
463 truncate_height,
464 PoolType::IRONWOOD,
465 )?;
466
467 Ok(())
468 }
469}
470
471fn checkpoint_heights(
484 block_range: &Range<BlockHeight>,
485 checkpoint_range: &Range<BlockHeight>,
486 anchor_retention_window: Option<(&AnchorRetention, &RangeInclusive<BlockHeight>)>,
487) -> Vec<BlockHeight> {
488 let mut heights = (u32::from(checkpoint_range.start)..u32::from(checkpoint_range.end))
489 .map(BlockHeight::from_u32)
490 .collect::<Vec<_>>();
491 if let Some((retention, window)) = anchor_retention_window {
492 let start = std::cmp::max(*window.start(), block_range.start);
493 let end = std::cmp::min(*window.end(), block_range.end - 1);
494 heights.extend(
495 retention
496 .retained_in_range(start..=end)
497 .into_iter()
498 .filter(|boundary| !checkpoint_range.contains(boundary)),
499 );
500 }
501
502 heights
503}
504
505fn truncate_pool_tree<H, E, const DEPTH: u8, const SHARD_HEIGHT: u8>(
506 tree: &mut ShardTree<MemoryShardStore<H, BlockHeight>, DEPTH, SHARD_HEIGHT>,
507 truncate_height: BlockHeight,
508 pool: PoolType,
509) -> Result<(), SyncError<E>>
510where
511 H: Hashable + Clone + PartialEq,
512 E: std::fmt::Debug + std::fmt::Display,
513{
514 match plan_pool_truncation(tree_facts(tree, truncate_height), truncate_height) {
515 PoolTruncation::Untouched => Ok(()),
516 PoolTruncation::ToCheckpoint { checkpoint } => {
517 match tree.rollback_to_checkpoint(checkpoint)? {
518 RollbackOutcome::RolledBack => Ok(()),
519 RollbackOutcome::NoSuchCheckpoint => {
520 tracing::error!(
521 "{pool} shard tree refused the planned rollback to its checkpoint at \
522 {checkpoint}! Beginning rescan."
523 );
524 Err(SyncError::TruncationError(truncate_height, pool))
525 }
526 }
527 }
528 PoolTruncation::RequiresRescan { newest_checkpoint } => {
529 tracing::error!(
530 "{pool} shard tree holds state up to checkpoint {newest_checkpoint}, above the \
531 truncation target {truncate_height}, with no checkpoint at the target! \
532 Beginning rescan."
533 );
534 Err(SyncError::TruncationError(truncate_height, pool))
535 }
536 }
537}
538
539async fn determine_checkpoint<D, L, const DEPTH: u8, const SHARD_HEIGHT: u8>(
547 consensus_parameters: &impl consensus::Parameters,
548 fetch_request_sender: mpsc::UnboundedSender<FetchRequest>,
549 checkpoint_height: BlockHeight,
550 located_trees: &[LocatedTreeData<L>],
551 shard_tree: &shardtree::ShardTree<
552 shardtree::store::memory::MemoryShardStore<L, BlockHeight>,
553 DEPTH,
554 SHARD_HEIGHT,
555 >,
556 determined_checkpoints: &BTreeMap<BlockHeight, Checkpoint>,
557) -> Result<Checkpoint, ServerError>
558where
559 L: Clone + PartialEq + incrementalmerkletree::Hashable,
560 D: SyncDomain,
561{
562 if let Some((_, position)) = located_trees
563 .iter()
564 .flat_map(|tree| tree.checkpoints.iter())
565 .find(|(height, _)| **height == checkpoint_height)
566 {
567 return Ok(Checkpoint::at_position(*position));
568 }
569
570 let mut previous_checkpoint = determined_checkpoints
571 .get(&(checkpoint_height - 1))
572 .cloned();
573 if previous_checkpoint.is_none() {
574 shard_tree
575 .store()
576 .for_each_checkpoint(
577 MAX_SHARDTREE_CHECKPOINTS as usize + 100,
578 |height, checkpoint| {
579 if *height == checkpoint_height - 1 {
580 previous_checkpoint = Some(checkpoint.clone());
581 }
582 Ok(())
583 },
584 )
585 .expect("infallible");
586 }
587
588 let tree_state = if let Some(checkpoint) = previous_checkpoint {
589 checkpoint.tree_state()
590 } else {
591 let frontiers = client::get_frontiers(
592 fetch_request_sender.clone(),
593 consensus_parameters,
594 checkpoint_height,
595 )
596 .await?;
597 let tree_size = match D::SHIELDED_PROTOCOL {
598 ShieldedPool::Sapling => frontiers.final_sapling_tree().tree_size(),
599 ShieldedPool::Orchard => frontiers.final_orchard_tree().tree_size(),
600 ShieldedPool::Ironwood => frontiers.final_ironwood_tree().tree_size(),
601 };
602 if tree_size == 0 {
603 TreeState::Empty
604 } else {
605 TreeState::AtPosition(incrementalmerkletree::Position::from(tree_size - 1))
606 }
607 };
608
609 Ok(Checkpoint::from_parts(tree_state, BTreeSet::new()))
610}