1use std::collections::{BTreeMap, BTreeSet, HashMap};
4
5use tokio::sync::mpsc;
6use zip32::DiversifierIndex;
7
8use incrementalmerkletree::Hashable;
9use orchard::tree::MerkleHashOrchard;
10use shardtree::ShardTree;
11use shardtree::store::memory::MemoryShardStore;
12use shardtree::store::{Checkpoint, ShardStore, TreeState};
13use zcash_client_backend::data_api::anchor_retention::AnchorRetention;
14use zcash_keys::keys::UnifiedFullViewingKey;
15use zcash_primitives::transaction::TxId;
16use zcash_protocol::consensus::{self, BlockHeight};
17use zcash_protocol::{PoolType, ShieldedPool};
18use zip32::AccountId;
19
20use crate::error::{ServerError, SyncError};
21use crate::keys::transparent::TransparentAddressId;
22use crate::reset_spends;
23use crate::shardtree_ext::{RollbackOutcome, ShardTreeExt};
24use crate::sync::truncate::{PoolTruncation, plan_pool_truncation, tree_facts};
25use crate::sync::{MAX_SHARDTREE_CHECKPOINTS, ScanRange};
26use crate::wallet::{
27 Ironwood, NullifierMap, Orchard, OutputId, Sapling, ShardTrees, SyncState, WalletBlock,
28 WalletTransaction, empty_shard_tree,
29};
30use crate::witness::LocatedTreeData;
31use crate::{SyncDomain, client, sync::set_transactions_failed_unchecked};
32
33use super::{FetchRequest, ScanTarget, witness};
34
35pub trait SyncWallet {
37 type Error: std::fmt::Debug + std::fmt::Display + std::error::Error;
39
40 fn get_birthday(&self) -> Result<BlockHeight, Self::Error>;
42
43 fn get_sync_state(&self) -> Result<&SyncState, Self::Error>;
45
46 fn get_sync_state_mut(&mut self) -> Result<&mut SyncState, Self::Error>;
48
49 fn get_unified_full_viewing_keys(
51 &self,
52 ) -> Result<HashMap<AccountId, UnifiedFullViewingKey>, Self::Error>;
53
54 fn add_orchard_address(
56 &mut self,
57 account_id: zip32::AccountId,
58 address: orchard::Address,
59 diversifier_index: DiversifierIndex,
60 ) -> Result<(), Self::Error>;
61
62 fn add_sapling_address(
64 &mut self,
65 account_id: zip32::AccountId,
66 address: sapling_crypto::PaymentAddress,
67 diversifier_index: DiversifierIndex,
68 ) -> Result<(), Self::Error>;
69
70 fn get_transparent_addresses(
74 &self,
75 ) -> Result<&BTreeMap<TransparentAddressId, String>, Self::Error>;
76
77 fn get_transparent_addresses_mut(
81 &mut self,
82 ) -> Result<&mut BTreeMap<TransparentAddressId, String>, Self::Error>;
83
84 fn set_save_flag(&mut self) -> Result<(), Self::Error> {
88 Ok(())
89 }
90}
91
92pub trait SyncBlocks: SyncWallet {
94 fn get_wallet_block(&self, block_height: BlockHeight) -> Result<WalletBlock, Self::Error>;
98
99 fn get_wallet_blocks_mut(
101 &mut self,
102 ) -> Result<&mut BTreeMap<BlockHeight, WalletBlock>, Self::Error>;
103
104 fn append_wallet_blocks(
106 &mut self,
107 mut wallet_blocks: BTreeMap<BlockHeight, WalletBlock>,
108 ) -> Result<(), Self::Error> {
109 self.get_wallet_blocks_mut()?.append(&mut wallet_blocks);
110
111 Ok(())
112 }
113
114 fn truncate_wallet_blocks(&mut self, truncate_height: BlockHeight) -> Result<(), Self::Error> {
116 self.get_wallet_blocks_mut()?
117 .retain(|block_height, _| *block_height <= truncate_height);
118
119 Ok(())
120 }
121}
122
123pub trait SyncTransactions: SyncWallet {
125 fn get_wallet_transactions(&self) -> Result<&HashMap<TxId, WalletTransaction>, Self::Error>;
127
128 fn get_wallet_transactions_mut(
130 &mut self,
131 ) -> Result<&mut HashMap<TxId, WalletTransaction>, Self::Error>;
132
133 fn insert_wallet_transaction(
135 &mut self,
136 wallet_transaction: WalletTransaction,
137 ) -> Result<(), Self::Error> {
138 self.get_wallet_transactions_mut()?
139 .insert(wallet_transaction.txid(), wallet_transaction);
140
141 Ok(())
142 }
143
144 fn extend_wallet_transactions(
148 &mut self,
149 wallet_transactions: HashMap<TxId, WalletTransaction>,
150 ) -> Result<(), Self::Error> {
151 self.get_wallet_transactions_mut()?
152 .extend(wallet_transactions);
153
154 Ok(())
155 }
156
157 fn truncate_wallet_transactions(
161 &mut self,
162 truncate_height: BlockHeight,
163 set_truncated_transactions_failed: bool,
164 ) -> Result<(), Self::Error> {
165 let invalid_txids: Vec<TxId> = self
166 .get_wallet_transactions()?
167 .values()
168 .filter(|tx| tx.status().is_confirmed_after(&truncate_height))
169 .map(|tx| tx.transaction().txid())
170 .collect();
171
172 if set_truncated_transactions_failed {
173 set_transactions_failed_unchecked(self.get_wallet_transactions_mut()?, invalid_txids);
174 } else {
175 let wallet_transactions = self.get_wallet_transactions_mut()?;
176 wallet_transactions.retain(|txid, _| !invalid_txids.contains(txid));
177 reset_spends(wallet_transactions, invalid_txids);
178 }
179
180 Ok(())
181 }
182}
183
184pub trait SyncNullifiers: SyncWallet {
186 fn get_nullifiers(&self) -> Result<&NullifierMap, Self::Error>;
188
189 fn get_nullifiers_mut(&mut self) -> Result<&mut NullifierMap, Self::Error>;
191
192 fn append_nullifiers(&mut self, nullifiers: &mut NullifierMap) -> Result<(), Self::Error> {
194 self.get_nullifiers_mut()?
195 .sapling
196 .append(&mut nullifiers.sapling);
197 self.get_nullifiers_mut()?
198 .orchard
199 .append(&mut nullifiers.orchard);
200 self.get_nullifiers_mut()?
201 .ironwood
202 .append(&mut nullifiers.ironwood);
203
204 Ok(())
205 }
206
207 fn truncate_nullifiers(&mut self, truncate_height: BlockHeight) -> Result<(), Self::Error> {
209 let nullifier_map = self.get_nullifiers_mut()?;
210 nullifier_map
211 .sapling
212 .retain(|_, scan_target| scan_target.block_height() <= truncate_height);
213 nullifier_map
214 .orchard
215 .retain(|_, scan_target| scan_target.block_height() <= truncate_height);
216 nullifier_map
217 .ironwood
218 .retain(|_, scan_target| scan_target.block_height() <= truncate_height);
219
220 Ok(())
221 }
222}
223
224pub trait SyncOutPoints: SyncWallet {
226 fn get_outpoints(&self) -> Result<&BTreeMap<OutputId, ScanTarget>, Self::Error>;
228
229 fn get_outpoints_mut(&mut self) -> Result<&mut BTreeMap<OutputId, ScanTarget>, Self::Error>;
231
232 fn append_outpoints(
234 &mut self,
235 outpoints: &mut BTreeMap<OutputId, ScanTarget>,
236 ) -> Result<(), Self::Error> {
237 self.get_outpoints_mut()?.append(outpoints);
238
239 Ok(())
240 }
241
242 fn truncate_outpoints(&mut self, truncate_height: BlockHeight) -> Result<(), Self::Error> {
244 self.get_outpoints_mut()?
245 .retain(|_, scan_target| scan_target.block_height() <= truncate_height);
246
247 Ok(())
248 }
249}
250
251pub trait SyncShardTrees: SyncWallet {
253 fn get_shard_trees(&self) -> Result<&ShardTrees, Self::Error>;
255
256 fn get_shard_trees_mut(&mut self) -> Result<&mut ShardTrees, Self::Error>;
258
259 #[allow(clippy::too_many_arguments)]
266 fn update_shard_trees(
267 &mut self,
268 consensus_parameters: &(impl consensus::Parameters + Sync),
269 fetch_request_sender: mpsc::UnboundedSender<FetchRequest>,
270 scan_range: &ScanRange,
271 chain_height: BlockHeight,
272 highest_scanned_height: BlockHeight,
273 anchor_retention: Option<AnchorRetention>,
274 sapling_located_trees: Vec<LocatedTreeData<sapling_crypto::Node>>,
275 orchard_located_trees: Vec<LocatedTreeData<MerkleHashOrchard>>,
276 ironwood_located_trees: Vec<LocatedTreeData<MerkleHashOrchard>>,
277 ) -> impl std::future::Future<Output = Result<(), SyncError<Self::Error>>> + Send
278 where
279 Self: std::marker::Send,
280 {
281 async move {
282 let shard_trees = self.get_shard_trees_mut().map_err(SyncError::WalletError)?;
283
284 let anchor_retention_window = anchor_retention.as_ref().map(|retention| {
290 (
291 retention,
292 witness::anchor_retention_window(
293 retention,
294 std::cmp::max(highest_scanned_height, scan_range.block_range().end - 1),
295 ),
296 )
297 });
298 let checkpoint_heights = witness::checkpoint_heights(
299 scan_range.block_range(),
300 chain_height,
301 highest_scanned_height,
302 anchor_retention.as_ref(),
303 );
304
305 let mut sapling_checkpoints = BTreeMap::new();
308 let mut orchard_checkpoints = BTreeMap::new();
309 let mut ironwood_checkpoints = BTreeMap::new();
310 for checkpoint_height in checkpoint_heights {
311 let sapling_checkpoint = determine_checkpoint::<
312 Sapling,
313 sapling_crypto::Node,
314 { sapling_crypto::NOTE_COMMITMENT_TREE_DEPTH },
315 { witness::SHARD_HEIGHT },
316 >(
317 consensus_parameters,
318 fetch_request_sender.clone(),
319 checkpoint_height,
320 &sapling_located_trees,
321 &shard_trees.sapling,
322 &sapling_checkpoints,
323 )
324 .await?;
325 sapling_checkpoints.insert(checkpoint_height, sapling_checkpoint);
326 let orchard_checkpoint = determine_checkpoint::<
327 Orchard,
328 MerkleHashOrchard,
329 { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
330 { witness::SHARD_HEIGHT },
331 >(
332 consensus_parameters,
333 fetch_request_sender.clone(),
334 checkpoint_height,
335 &orchard_located_trees,
336 &shard_trees.orchard,
337 &orchard_checkpoints,
338 )
339 .await?;
340 orchard_checkpoints.insert(checkpoint_height, orchard_checkpoint);
341 let ironwood_checkpoint = determine_checkpoint::<
342 Ironwood,
343 MerkleHashOrchard,
344 { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
345 { witness::SHARD_HEIGHT },
346 >(
347 consensus_parameters,
348 fetch_request_sender.clone(),
349 checkpoint_height,
350 &ironwood_located_trees,
351 &shard_trees.ironwood,
352 &ironwood_checkpoints,
353 )
354 .await?;
355 ironwood_checkpoints.insert(checkpoint_height, ironwood_checkpoint);
356 }
357
358 if let Some((retention, window)) = &anchor_retention_window {
359 witness::repin_anchor_checkpoints(
360 retention,
361 window,
362 shard_trees.sapling.store_mut(),
363 );
364 witness::repin_anchor_checkpoints(
365 retention,
366 window,
367 shard_trees.orchard.store_mut(),
368 );
369 witness::repin_anchor_checkpoints(
370 retention,
371 window,
372 shard_trees.ironwood.store_mut(),
373 );
374 }
375 for (checkpoint_height, checkpoint) in sapling_checkpoints {
376 shard_trees
377 .sapling
378 .store_mut()
379 .add_checkpoint(checkpoint_height, checkpoint)
380 .expect("infallible");
381 }
382 for (checkpoint_height, checkpoint) in orchard_checkpoints {
383 shard_trees
384 .orchard
385 .store_mut()
386 .add_checkpoint(checkpoint_height, checkpoint)
387 .expect("infallible");
388 }
389 for (checkpoint_height, checkpoint) in ironwood_checkpoints {
390 shard_trees
391 .ironwood
392 .store_mut()
393 .add_checkpoint(checkpoint_height, checkpoint)
394 .expect("infallible");
395 }
396
397 for tree in sapling_located_trees {
399 shard_trees
400 .sapling
401 .insert_tree(tree.subtree, tree.checkpoints)?;
402 }
403 for tree in orchard_located_trees {
404 shard_trees
405 .orchard
406 .insert_tree(tree.subtree, tree.checkpoints)?;
407 }
408 for tree in ironwood_located_trees {
409 shard_trees
410 .ironwood
411 .insert_tree(tree.subtree, tree.checkpoints)?;
412 }
413
414 Ok(())
415 }
416 }
417
418 fn clear_shard_trees(&mut self) -> Result<(), SyncError<Self::Error>> {
420 let shard_trees = self.get_shard_trees_mut().map_err(SyncError::WalletError)?;
421 tracing::info!("Clearing shard trees.");
422 shard_trees.sapling = empty_shard_tree();
423 shard_trees.orchard = empty_shard_tree();
424 shard_trees.ironwood = empty_shard_tree();
425
426 Ok(())
427 }
428
429 fn truncate_shard_trees(
438 &mut self,
439 truncate_height: BlockHeight,
440 ) -> Result<(), SyncError<Self::Error>> {
441 let shard_trees = self.get_shard_trees_mut().map_err(SyncError::WalletError)?;
442 truncate_pool_tree(&mut shard_trees.sapling, truncate_height, PoolType::SAPLING)?;
443 truncate_pool_tree(&mut shard_trees.orchard, truncate_height, PoolType::ORCHARD)?;
444 truncate_pool_tree(
445 &mut shard_trees.ironwood,
446 truncate_height,
447 PoolType::IRONWOOD,
448 )?;
449
450 Ok(())
451 }
452}
453
454fn truncate_pool_tree<H, E, const DEPTH: u8, const SHARD_HEIGHT: u8>(
465 tree: &mut ShardTree<MemoryShardStore<H, BlockHeight>, DEPTH, SHARD_HEIGHT>,
466 truncate_height: BlockHeight,
467 pool: PoolType,
468) -> Result<(), SyncError<E>>
469where
470 H: Hashable + Clone + PartialEq,
471 E: std::fmt::Debug + std::fmt::Display,
472{
473 match plan_pool_truncation(tree_facts(tree, truncate_height), truncate_height) {
474 PoolTruncation::Untouched => Ok(()),
475 PoolTruncation::ToCheckpoint { checkpoint } => {
476 match tree.rollback_to_checkpoint(checkpoint)? {
477 RollbackOutcome::RolledBack => Ok(()),
478 RollbackOutcome::NoSuchCheckpoint => {
479 tracing::error!(
480 "{pool} shard tree refused the planned rollback to its checkpoint at \
481 {checkpoint}! Beginning rescan."
482 );
483 Err(SyncError::TruncationError(truncate_height, pool))
484 }
485 }
486 }
487 PoolTruncation::RequiresRescan { newest_checkpoint } => {
488 tracing::error!(
489 "{pool} shard tree holds state up to checkpoint {newest_checkpoint}, above the \
490 truncation target {truncate_height}, with no checkpoint at the target! \
491 Beginning rescan."
492 );
493 Err(SyncError::TruncationError(truncate_height, pool))
494 }
495 }
496}
497
498async fn determine_checkpoint<D, L, const DEPTH: u8, const SHARD_HEIGHT: u8>(
506 consensus_parameters: &impl consensus::Parameters,
507 fetch_request_sender: mpsc::UnboundedSender<FetchRequest>,
508 checkpoint_height: BlockHeight,
509 located_trees: &[LocatedTreeData<L>],
510 shard_tree: &shardtree::ShardTree<
511 shardtree::store::memory::MemoryShardStore<L, BlockHeight>,
512 DEPTH,
513 SHARD_HEIGHT,
514 >,
515 determined_checkpoints: &BTreeMap<BlockHeight, Checkpoint>,
516) -> Result<Checkpoint, ServerError>
517where
518 L: Clone + PartialEq + incrementalmerkletree::Hashable,
519 D: SyncDomain,
520{
521 if let Some((_, position)) = located_trees
522 .iter()
523 .flat_map(|tree| tree.checkpoints.iter())
524 .find(|(height, _)| **height == checkpoint_height)
525 {
526 return Ok(Checkpoint::at_position(*position));
527 }
528
529 let mut previous_checkpoint = determined_checkpoints
530 .get(&(checkpoint_height - 1))
531 .cloned();
532 if previous_checkpoint.is_none() {
533 shard_tree
534 .store()
535 .for_each_checkpoint(
536 MAX_SHARDTREE_CHECKPOINTS as usize + 100,
537 |height, checkpoint| {
538 if *height == checkpoint_height - 1 {
539 previous_checkpoint = Some(checkpoint.clone());
540 }
541 Ok(())
542 },
543 )
544 .expect("infallible");
545 }
546
547 let tree_state = if let Some(checkpoint) = previous_checkpoint {
548 checkpoint.tree_state()
549 } else {
550 let frontiers = client::get_frontiers(
551 fetch_request_sender.clone(),
552 consensus_parameters,
553 checkpoint_height,
554 )
555 .await?;
556 let tree_size = match D::SHIELDED_PROTOCOL {
557 ShieldedPool::Sapling => frontiers.final_sapling_tree().tree_size(),
558 ShieldedPool::Orchard => frontiers.final_orchard_tree().tree_size(),
559 ShieldedPool::Ironwood => frontiers.final_ironwood_tree().tree_size(),
560 };
561 if tree_size == 0 {
562 TreeState::Empty
563 } else {
564 TreeState::AtPosition(incrementalmerkletree::Position::from(tree_size - 1))
565 }
566 };
567
568 Ok(Checkpoint::from_parts(tree_state, BTreeSet::new()))
569}