eth_state_diff/
attestations.rs1use crate::types::{ArchivedAttestationsDiff, AttestationsDiff};
18
19pub fn diff_attestations(base_ssz: &[u8], target_ssz: &[u8]) -> AttestationsDiff {
76 if base_ssz == target_ssz {
77 return AttestationsDiff::Unchanged;
78 }
79
80 if target_ssz.starts_with(base_ssz) {
81 let appended = &target_ssz[base_ssz.len()..];
82 AttestationsDiff::Append(appended.to_vec())
83 } else {
84 AttestationsDiff::FullReplacement(target_ssz.to_vec())
85 }
86}
87
88pub fn apply_attestations(base: &mut Vec<u8>, delta: &ArchivedAttestationsDiff) {
127 match delta {
128 ArchivedAttestationsDiff::Unchanged => {}
129 ArchivedAttestationsDiff::Append(bytes) => {
130 base.extend_from_slice(bytes.as_slice());
131 }
132 ArchivedAttestationsDiff::FullReplacement(bytes) => {
133 base.clear();
134 base.extend_from_slice(bytes.as_slice());
135 }
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::types::{ArchivedAttestationsDiff, AttestationsDiff};
143 use rkyv::Archive;
144
145 fn archive(diff: &AttestationsDiff) -> rkyv::util::AlignedVec {
146 rkyv::to_bytes::<rkyv::rancor::Error>(diff).expect("failed to archive diff")
147 }
148
149 fn archived(bytes: &[u8]) -> &ArchivedAttestationsDiff {
150 rkyv::access::<ArchivedAttestationsDiff, rkyv::rancor::Error>(bytes)
151 .expect("bytes are a valid rkyv payload generated by `archive`")
152 }
153
154 #[test]
155 fn diff_identical_lists_is_unchanged() {
156 assert_eq!(
157 diff_attestations(b"AAAA", b"AAAA"),
158 AttestationsDiff::Unchanged
159 );
160 }
161
162 #[test]
163 fn diff_target_extends_base_is_append() {
164 assert_eq!(
165 diff_attestations(b"AAAA", b"AAAABBBB"),
166 AttestationsDiff::Append(b"BBBB".to_vec()),
167 );
168 }
169
170 #[test]
171 fn diff_target_diverges_is_full_replacement() {
172 assert_eq!(
173 diff_attestations(b"AAAA", b"CCCC"),
174 AttestationsDiff::FullReplacement(b"CCCC".to_vec()),
175 );
176 }
177
178 #[test]
179 fn diff_target_diverges_mid_item_is_full_replacement() {
180 let base = b"AAAA-BBBB-CCCC-DDDD";
181 let target = b"AAAA-BBBB-XXXX-DDDD";
182 assert_eq!(
183 diff_attestations(base, target),
184 AttestationsDiff::FullReplacement(target.to_vec()),
185 );
186 }
187
188 #[test]
189 fn diff_empty_base_to_nonempty_is_append() {
190 assert_eq!(
191 diff_attestations(b"", b"AAAABBBB"),
192 AttestationsDiff::Append(b"AAAABBBB".to_vec()),
193 );
194 }
195
196 #[test]
197 fn diff_both_empty_is_unchanged() {
198 assert_eq!(diff_attestations(b"", b""), AttestationsDiff::Unchanged);
199 }
200
201 #[test]
202 fn diff_nonempty_base_to_empty_is_replacement_with_empty_payload() {
203 assert_eq!(
204 diff_attestations(b"AAAA", b""),
205 AttestationsDiff::FullReplacement(Vec::new()),
206 );
207 }
208
209 #[test]
210 fn apply_unchanged_leaves_buffer_untouched() {
211 let buf = archive(&AttestationsDiff::Unchanged);
212 let mut base = b"AAAA".to_vec();
213 apply_attestations(&mut base, archived(&buf));
214 assert_eq!(base, b"AAAA");
215 }
216
217 #[test]
218 fn apply_append_extends_buffer_in_place() {
219 let buf = archive(&AttestationsDiff::Append(b"BBBB".to_vec()));
220 let mut base = b"AAAA".to_vec();
221 apply_attestations(&mut base, archived(&buf));
222 assert_eq!(base, b"AAAABBBB");
223 }
224
225 #[test]
226 fn apply_full_replacement_clears_then_writes() {
227 let buf = archive(&AttestationsDiff::FullReplacement(b"CCCC".to_vec()));
228 let mut base = b"AAAA".to_vec();
229 apply_attestations(&mut base, archived(&buf));
230 assert_eq!(base, b"CCCC");
231
232 let buf = archive(&AttestationsDiff::FullReplacement(Vec::new()));
233 apply_attestations(&mut base, archived(&buf));
234 assert!(base.is_empty());
235 }
236
237 fn round_trip(base: &[u8], target: &[u8]) {
238 let diff = diff_attestations(base, target);
239 let buf = archive(&diff);
240 let mut current = base.to_vec();
241 apply_attestations(&mut current, archived(&buf));
242 assert_eq!(
243 current, target,
244 "round-trip failed for base={base:?} target={target:?}"
245 );
246 }
247
248 #[test]
249 fn round_trip_covers_all_branches() {
250 round_trip(b"AAAA", b"AAAA");
251 round_trip(b"AAAA", b"AAAABBBB");
252 round_trip(b"AAAA", b"CCCC");
253 round_trip(b"", b"AAAABBBB");
254 round_trip(b"", b"");
255 round_trip(b"AAAA", b"");
256 round_trip(b"AAAA-BBBB-CCCC-DDDD", b"AAAA-BBBB-XXXX-DDDD");
257 }
258
259 #[test]
260 fn round_trip_epoch_lifecycle_appends_then_replaces() {
261 let mut state: Vec<u8> = Vec::new();
262 let mut expected = Vec::new();
263
264 for chunk in [b"att1".as_slice(), b"att2", b"att3"] {
265 expected.extend_from_slice(chunk);
266 let buf = archive(&diff_attestations(&state, &expected));
267 apply_attestations(&mut state, archived(&buf));
268 assert_eq!(state, expected);
269 }
270
271 let new_epoch = b"prev1-prev2";
273 let diff = diff_attestations(&state, new_epoch);
274 assert!(
275 matches!(diff, AttestationsDiff::FullReplacement(_)),
276 "epoch transition must be a full replacement, got {diff:?}"
277 );
278 let buf = archive(&diff);
279 apply_attestations(&mut state, archived(&buf));
280 assert_eq!(state, new_epoch);
281
282 let mut expected = new_epoch.to_vec();
284 expected.extend_from_slice(b"-att4");
285 let diff = diff_attestations(&state, &expected);
286 assert!(
287 matches!(diff, AttestationsDiff::Append(_)),
288 "intra-epoch update must be an append, got {diff:?}"
289 );
290 let buf = archive(&diff);
291 apply_attestations(&mut state, archived(&buf));
292 assert_eq!(state, expected);
293 }
294
295 #[test]
296 fn round_trip_large_payloads() {
297 let one_att = b"AAAAAAAA-BBBBBBBB-CCCCCCCC-DDDDDDDD-EEEEEEEE";
298 let mut base = Vec::new();
299 for _ in 0..32 {
300 base.extend_from_slice(one_att);
301 }
302
303 let mut target = base.clone();
304 target.extend_from_slice(one_att);
305 target.extend_from_slice(one_att);
306
307 let diff = diff_attestations(&base, &target);
308 assert!(matches!(diff, AttestationsDiff::Append(_)));
309 let buf = archive(&diff);
310 let mut current = base.clone();
311 apply_attestations(&mut current, archived(&buf));
312 assert_eq!(current, target);
313
314 let mut divergent = target.clone();
316 *divergent.get_mut(0).expect("target vector is non-empty") ^= 0xFF;
317 let diff = diff_attestations(¤t, &divergent);
318 assert!(matches!(diff, AttestationsDiff::FullReplacement(_)));
319 let buf = archive(&diff);
320 apply_attestations(&mut current, archived(&buf));
321 assert_eq!(current, divergent);
322 }
323
324 #[test]
325 fn archived_alias_matches_attestations_diff() {
326 fn _assert_archived<T: Archive<Archived = ArchivedAttestationsDiff>>() {}
327 _assert_archived::<AttestationsDiff>();
328 }
329}