commonware_storage/qmdb/sync/
target.rs1use crate::{
2 merkle::{Family, Location},
3 qmdb::sync::{self, error::EngineError},
4};
5use commonware_codec::{EncodeSize, Error as CodecError, Read, ReadExt as _, Write};
6use commonware_cryptography::Digest;
7use commonware_runtime::{Buf, BufMut};
8use commonware_utils::range::NonEmptyRange;
9
10#[derive(Debug)]
15pub struct Target<F: Family, D: Digest> {
16 pub root: D,
18 pub range: NonEmptyRange<Location<F>>,
20}
21
22impl<F: Family, D: Digest> Target<F, D> {
23 pub const fn new(root: D, range: NonEmptyRange<Location<F>>) -> Self {
25 Self { root, range }
26 }
27}
28
29impl<F: Family, D: Digest> Clone for Target<F, D> {
30 fn clone(&self) -> Self {
31 Self {
32 root: self.root,
33 range: self.range.clone(),
34 }
35 }
36}
37
38impl<F: Family, D: Digest> PartialEq for Target<F, D> {
39 fn eq(&self, other: &Self) -> bool {
40 self.root == other.root && self.range == other.range
41 }
42}
43
44impl<F: Family, D: Digest> Eq for Target<F, D> {}
45
46impl<F: Family, D: Digest> Write for Target<F, D> {
47 fn write(&self, buf: &mut impl BufMut) {
48 self.root.write(buf);
49 self.range.write(buf);
50 }
51}
52
53impl<F: Family, D: Digest> EncodeSize for Target<F, D> {
54 fn encode_size(&self) -> usize {
55 self.root.encode_size() + self.range.encode_size()
56 }
57}
58
59impl<F: Family, D: Digest> Read for Target<F, D> {
60 type Cfg = ();
61
62 fn read_cfg(buf: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
63 let root = D::read(buf)?;
64 let range = NonEmptyRange::<Location<F>>::read(buf)?;
65 if !range.start().is_valid() || !range.end().is_valid() {
66 return Err(CodecError::Invalid(
67 "storage::qmdb::sync::Target",
68 "range bounds out of valid range",
69 ));
70 }
71 Ok(Self { root, range })
72 }
73}
74
75#[cfg(feature = "arbitrary")]
76impl<F: Family, D: Digest> arbitrary::Arbitrary<'_> for Target<F, D>
77where
78 D: for<'a> arbitrary::Arbitrary<'a>,
79{
80 fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
81 let root = u.arbitrary()?;
82 let max_loc = F::MAX_LEAVES;
83 let lower = u.int_in_range(0..=*max_loc - 1)?;
84 let upper = u.int_in_range(lower + 1..=*max_loc)?;
85 Ok(Self {
86 root,
87 range: commonware_utils::non_empty_range!(Location::new(lower), Location::new(upper)),
88 })
89 }
90}
91
92pub fn validate_update<F, U, D>(
94 old_target: &Target<F, D>,
95 new_target: &Target<F, D>,
96) -> Result<(), sync::Error<F, U, D>>
97where
98 F: Family,
99 U: std::error::Error + Send + 'static,
100 D: Digest,
101{
102 if !new_target.range.end().is_valid() {
103 return Err(sync::Error::Engine(EngineError::InvalidTarget {
104 lower_bound_pos: new_target.range.start(),
105 upper_bound_pos: new_target.range.end(),
106 }));
107 }
108
109 if new_target.range.start() < old_target.range.start()
113 || new_target.range.end() <= old_target.range.end()
114 {
115 return Err(sync::Error::Engine(EngineError::SyncTargetMovedBackward {
116 old: old_target.clone(),
117 new: new_target.clone(),
118 }));
119 }
120
121 if new_target.root == old_target.root {
122 return Err(sync::Error::Engine(EngineError::SyncTargetRootUnchanged));
123 }
124
125 Ok(())
126}
127
128#[cfg(test)]
129mod tests {
134 use super::*;
135 use crate::merkle::mmr::Family as MmrFamily;
136 use commonware_cryptography::sha256;
137 use commonware_utils::non_empty_range;
138 use rstest::rstest;
139 use std::io::Cursor;
140
141 fn target(root: sha256::Digest, start: u64, end: u64) -> Target<MmrFamily, sha256::Digest> {
142 Target::new(
143 root,
144 non_empty_range!(Location::new(start), Location::new(end)),
145 )
146 }
147
148 #[test]
149 fn test_sync_target_serialization() {
150 let target = target(sha256::Digest::from([42; 32]), 100, 500);
151
152 let mut buffer = Vec::new();
154 target.write(&mut buffer);
155
156 assert_eq!(buffer.len(), target.encode_size());
158
159 let mut cursor = Cursor::new(buffer);
161 let deserialized = Target::read(&mut cursor).unwrap();
162
163 assert_eq!(target, deserialized);
165 assert_eq!(target.root, deserialized.root);
166 assert_eq!(target.range, deserialized.range);
167 }
168
169 #[test]
170 fn test_sync_target_read_invalid_bounds() {
171 let mut buffer = Vec::new();
173 sha256::Digest::from([42; 32]).write(&mut buffer);
174 Location::<MmrFamily>::new(100).write(&mut buffer); Location::<MmrFamily>::new(50).write(&mut buffer); let mut cursor = Cursor::new(buffer);
178 assert!(matches!(
179 Target::<MmrFamily, sha256::Digest>::read(&mut cursor),
180 Err(CodecError::Invalid("Range", "start must be <= end"))
181 ));
182
183 let root = sha256::Digest::from([42; 32]);
185 let mut buffer = Vec::new();
186 root.write(&mut buffer);
187 (Location::<MmrFamily>::new(100)..Location::<MmrFamily>::new(100)).write(&mut buffer);
188
189 let mut cursor = Cursor::new(buffer);
190 assert!(matches!(
191 Target::<MmrFamily, sha256::Digest>::read(&mut cursor),
192 Err(CodecError::Invalid("NonEmptyRange", "start must be < end"))
193 ));
194 }
195
196 type TestError = sync::Error<MmrFamily, std::io::Error, sha256::Digest>;
197
198 #[rstest]
199 #[case::valid_update(
200 target(sha256::Digest::from([0; 32]), 0, 100),
201 target(sha256::Digest::from([1; 32]), 50, 200),
202 Ok(())
203 )]
204 #[case::same_start(
205 target(sha256::Digest::from([0; 32]), 0, 100),
206 target(sha256::Digest::from([1; 32]), 0, 200),
207 Ok(())
208 )]
209 #[case::same_end(
210 target(sha256::Digest::from([0; 32]), 0, 100),
211 target(sha256::Digest::from([1; 32]), 50, 100),
212 Err(TestError::Engine(EngineError::SyncTargetMovedBackward {
213 old: target(sha256::Digest::from([0; 32]), 0, 100),
214 new: target(sha256::Digest::from([1; 32]), 50, 100),
215 }))
216 )]
217 #[case::moves_backward(
218 target(sha256::Digest::from([0; 32]), 0, 100),
219 target(sha256::Digest::from([1; 32]), 0, 50),
220 Err(TestError::Engine(EngineError::SyncTargetMovedBackward {
221 old: target(sha256::Digest::from([0; 32]), 0, 100),
222 new: target(sha256::Digest::from([1; 32]), 0, 50),
223 }))
224 )]
225 #[case::same_root(
226 target(sha256::Digest::from([0; 32]), 0, 100),
227 target(sha256::Digest::from([0; 32]), 50, 200),
228 Err(TestError::Engine(EngineError::SyncTargetRootUnchanged))
229 )]
230 fn test_validate_update(
231 #[case] old_target: Target<MmrFamily, sha256::Digest>,
232 #[case] new_target: Target<MmrFamily, sha256::Digest>,
233 #[case] expected: Result<(), TestError>,
234 ) {
235 let result = validate_update(&old_target, &new_target);
236 match (&result, &expected) {
237 (Ok(()), Ok(())) => {}
238 (Ok(()), Err(expected_err)) => {
239 panic!("Expected error {expected_err:?} but got success");
240 }
241 (Err(actual_err), Ok(())) => {
242 panic!("Expected success but got error: {actual_err:?}");
243 }
244 (Err(actual_err), Err(expected_err)) => match (actual_err, expected_err) {
245 (
246 TestError::Engine(EngineError::InvalidTarget {
247 lower_bound_pos: a_lower,
248 upper_bound_pos: a_upper,
249 }),
250 TestError::Engine(EngineError::InvalidTarget {
251 lower_bound_pos: e_lower,
252 upper_bound_pos: e_upper,
253 }),
254 ) => {
255 assert_eq!(a_lower, e_lower);
256 assert_eq!(a_upper, e_upper);
257 }
258 (
259 TestError::Engine(EngineError::SyncTargetMovedBackward { .. }),
260 TestError::Engine(EngineError::SyncTargetMovedBackward { .. }),
261 ) => {}
262 (
263 TestError::Engine(EngineError::SyncTargetRootUnchanged),
264 TestError::Engine(EngineError::SyncTargetRootUnchanged),
265 ) => {}
266 _ => panic!("Error type mismatch: got {actual_err:?}, expected {expected_err:?}"),
267 },
268 }
269 }
270
271 #[cfg(feature = "arbitrary")]
272 mod conformance {
273 use super::*;
274 use commonware_codec::conformance::CodecConformance;
275
276 commonware_conformance::conformance_tests! {
277 CodecConformance<Target<MmrFamily, sha256::Digest>>,
278 }
279 }
280}