vote_commitment_tree/
memory_server.rs1use std::convert::Infallible;
12
13use pasta_curves::Fp;
14use shardtree::store::ShardStore;
15
16use crate::hash::MerkleHashVote;
17use crate::server::SyncableServer;
18use crate::sync_api::{BlockCommitmentsPage, TreeState, TreeSyncApi};
19
20impl<S> TreeSyncApi for SyncableServer<S>
25where
26 S: ShardStore<H = MerkleHashVote, CheckpointId = u32>,
27 S::Error: std::fmt::Debug,
28{
29 type Error = Infallible;
32
33 fn get_block_commitments(
34 &self,
35 from_height: u32,
36 to_height: u32,
37 ) -> Result<BlockCommitmentsPage, Infallible> {
38 let blocks = self
39 .blocks
40 .range(from_height..=to_height)
41 .map(|(_, bc)| bc.clone())
42 .collect();
43 Ok(BlockCommitmentsPage {
44 blocks,
45 next_from_height: 0,
46 })
47 }
48
49 fn get_root_at_height(&self, height: u32) -> Result<Option<Fp>, Infallible> {
50 Ok(self.root_at_height(height))
51 }
52
53 fn get_tree_state(&self) -> Result<TreeState, Infallible> {
54 Ok(TreeState {
55 next_index: self.size(),
56 root: self.root(),
57 height: self.latest_checkpoint().unwrap_or(0),
58 })
59 }
60}
61
62#[cfg(test)]
67mod tests {
68 use super::*;
69 use crate::anchor::Anchor;
70 use crate::hash::EMPTY_ROOTS;
71 use crate::server::{CheckpointError, MemoryTreeServer};
72
73 fn fp(x: u64) -> Fp {
74 Fp::from(x)
75 }
76
77 #[test]
78 fn empty_tree_has_deterministic_root() {
79 let t1 = MemoryTreeServer::empty();
80 let t2 = MemoryTreeServer::empty();
81 assert_eq!(t1.root(), t2.root());
82 assert_eq!(t1.size(), 0);
83 }
84
85 #[test]
86 fn empty_roots_are_consistent() {
87 use crate::hash::{MerkleHashVote, TREE_DEPTH};
88 use incrementalmerkletree::{Hashable, Level};
89
90 let leaf = MerkleHashVote::empty_leaf();
91 assert_eq!(EMPTY_ROOTS[0], leaf);
92 let mut expected = leaf;
93 for level in 0..TREE_DEPTH {
94 assert_eq!(EMPTY_ROOTS[level], expected, "level {level}");
95 expected = MerkleHashVote::combine(Level::from(level as u8), &expected, &expected);
96 }
97 }
98
99 #[test]
100 fn append_one_and_path() {
101 let mut tree = MemoryTreeServer::empty();
102 let idx = tree.append(fp(100)).unwrap();
103 assert_eq!(idx, 0);
104 assert_eq!(tree.size(), 1);
105 tree.checkpoint(1).unwrap();
106 let path = tree.path(0, 1).unwrap();
107 assert!(path.verify(fp(100), tree.root()));
108 }
109
110 #[test]
111 fn append_two_leaves_paths_verify() {
112 let mut tree = MemoryTreeServer::empty();
113 tree.append(fp(1)).unwrap();
114 tree.append(fp(2)).unwrap();
115 tree.checkpoint(1).unwrap();
116 let root = tree.root();
117 for i in 0..2u64 {
118 let path = tree.path(i, 1).unwrap();
119 assert!(path.verify(Fp::from(i + 1), root));
120 }
121 }
122
123 #[test]
124 fn checkpoint_preserves_root() {
125 let mut tree = MemoryTreeServer::empty();
126 tree.append(fp(1)).unwrap();
127 tree.append(fp(2)).unwrap();
128 tree.checkpoint(10).unwrap();
129 let root_at_10 = tree.root_at_height(10).unwrap();
130 tree.append(fp(3)).unwrap();
131 tree.checkpoint(11).unwrap();
132 assert_eq!(tree.root_at_height(10).unwrap(), root_at_10);
133 assert_ne!(tree.root_at_height(11).unwrap(), root_at_10);
134 }
135
136 #[test]
137 fn anchor_empty_tree() {
138 let anchor = Anchor::empty_tree();
139 let tree = MemoryTreeServer::empty();
140 assert_eq!(anchor.inner(), tree.root());
141 }
142
143 #[test]
144 fn checkpoint_monotonicity_returns_error_on_regression() {
145 let mut tree = MemoryTreeServer::empty();
146 tree.append(fp(1)).unwrap();
147 tree.checkpoint(5).unwrap();
148 tree.append(fp(2)).unwrap();
149
150 let err = tree
152 .checkpoint(5)
153 .expect_err("expected error on duplicate checkpoint height");
154 assert!(
155 matches!(
156 err,
157 CheckpointError::NotMonotonic {
158 prev: 5,
159 requested: 5
160 }
161 ),
162 "unexpected error variant: {:?}",
163 err,
164 );
165
166 let err = tree
168 .checkpoint(3)
169 .expect_err("expected error on regressing checkpoint height");
170 assert!(
171 matches!(
172 err,
173 CheckpointError::NotMonotonic {
174 prev: 5,
175 requested: 3
176 }
177 ),
178 "unexpected error variant: {:?}",
179 err,
180 );
181
182 tree.checkpoint(6)
184 .expect("checkpoint with higher height must succeed");
185 }
186
187 #[test]
190 fn old_checkpoints_pruned_after_max_checkpoints() {
191 use crate::hash::MAX_CHECKPOINTS;
192
193 let mut tree = MemoryTreeServer::empty();
194 let total = MAX_CHECKPOINTS + 1;
195 for h in 1u32..=(total as u32) {
196 tree.append(Fp::from(h as u64)).unwrap();
197 tree.checkpoint(h).unwrap();
198 }
199
200 assert!(
201 tree.root_at_height(1).is_none(),
202 "checkpoint at height 1 should be pruned after {} blocks",
203 total
204 );
205
206 let first_retained = (total - MAX_CHECKPOINTS + 1) as u32;
207 assert!(
208 tree.root_at_height(first_retained).is_some(),
209 "checkpoint at height {} should still be present",
210 first_retained
211 );
212 assert!(
213 tree.root_at_height(total as u32).is_some(),
214 "latest checkpoint must be present"
215 );
216 }
217}