pepper_sync/
shardtree_ext.rs1use shardtree::ShardTree;
17use shardtree::error::ShardTreeError;
18use shardtree::store::memory::MemoryShardStore;
19use std::convert::Infallible;
20
21use incrementalmerkletree::Hashable;
22use zcash_protocol::consensus::BlockHeight;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum CheckpointAppendOutcome {
28 Appended,
31 NotAboveNewest,
40}
41
42#[derive(Debug, Clone, Copy, PartialEq, Eq)]
44pub enum RollbackOutcome {
45 RolledBack,
47 NoSuchCheckpoint,
49}
50
51pub trait ShardTreeExt {
55 fn append_checkpoint(
58 &mut self,
59 checkpoint_id: BlockHeight,
60 ) -> Result<CheckpointAppendOutcome, ShardTreeError<Infallible>>;
61
62 fn rollback_to_checkpoint(
65 &mut self,
66 checkpoint_id: BlockHeight,
67 ) -> Result<RollbackOutcome, ShardTreeError<Infallible>>;
68}
69
70impl<H, const DEPTH: u8, const SHARD_HEIGHT: u8> ShardTreeExt
71 for ShardTree<MemoryShardStore<H, BlockHeight>, DEPTH, SHARD_HEIGHT>
72where
73 H: Hashable + Clone + PartialEq,
74{
75 fn append_checkpoint(
76 &mut self,
77 checkpoint_id: BlockHeight,
78 ) -> Result<CheckpointAppendOutcome, ShardTreeError<Infallible>> {
79 Ok(if self.checkpoint(checkpoint_id)? {
80 CheckpointAppendOutcome::Appended
81 } else {
82 CheckpointAppendOutcome::NotAboveNewest
83 })
84 }
85
86 fn rollback_to_checkpoint(
87 &mut self,
88 checkpoint_id: BlockHeight,
89 ) -> Result<RollbackOutcome, ShardTreeError<Infallible>> {
90 Ok(if self.truncate_to_checkpoint(&checkpoint_id)? {
91 RollbackOutcome::RolledBack
92 } else {
93 RollbackOutcome::NoSuchCheckpoint
94 })
95 }
96}
97
98#[cfg(test)]
99mod test {
100 use super::*;
101
102 #[test]
107 fn raw_boolean_calls_are_confined_to_this_module() {
108 let src_root = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
109 let mut rust_sources = Vec::new();
110 collect_rust_sources(&src_root, &mut rust_sources);
111 assert!(!rust_sources.is_empty());
112
113 let raw_patterns = [
114 ".checkpoint(",
115 ".truncate_to_checkpoint(",
116 ".truncate_to_checkpoint_depth(",
117 ];
118 for path in rust_sources {
119 if path.ends_with("shardtree_ext.rs") {
120 continue;
121 }
122 let source = std::fs::read_to_string(&path).unwrap();
123 for pattern in raw_patterns {
124 assert!(
125 !source.contains(pattern),
126 "{} calls a raw shardtree boolean operation ({pattern}); \
127 route it through crate::shardtree_ext instead",
128 path.display(),
129 );
130 }
131 }
132 }
133
134 fn collect_rust_sources(dir: &std::path::Path, out: &mut Vec<std::path::PathBuf>) {
135 for entry in std::fs::read_dir(dir).unwrap() {
136 let path = entry.unwrap().path();
137 if path.is_dir() {
138 collect_rust_sources(&path, out);
139 } else if path.extension().is_some_and(|extension| extension == "rs") {
140 out.push(path);
141 }
142 }
143 }
144
145 #[test]
149 fn outcomes_match_store_behavior() {
150 let mut tree: ShardTree<
151 MemoryShardStore<orchard::tree::MerkleHashOrchard, BlockHeight>,
152 { orchard::NOTE_COMMITMENT_TREE_DEPTH as u8 },
153 { crate::witness::SHARD_HEIGHT },
154 > = ShardTree::new(MemoryShardStore::empty(), 10);
155
156 let five = BlockHeight::from_u32(5);
157 assert_eq!(
158 tree.append_checkpoint(five).unwrap(),
159 CheckpointAppendOutcome::Appended
160 );
161 assert_eq!(
162 tree.append_checkpoint(five).unwrap(),
163 CheckpointAppendOutcome::NotAboveNewest
164 );
165 assert_eq!(
166 tree.rollback_to_checkpoint(five).unwrap(),
167 RollbackOutcome::RolledBack
168 );
169 assert_eq!(
170 tree.rollback_to_checkpoint(BlockHeight::from_u32(4))
171 .unwrap(),
172 RollbackOutcome::NoSuchCheckpoint
173 );
174 }
175}