1use crate::{
71 balances::{read_varint, write_varint},
72 types::{ArchivedParticipationDiff, ParticipationDiff},
73 Error,
74};
75
76pub fn diff_participation(base: &[u8], target: &[u8]) -> ParticipationDiff {
94 if target.iter().all(|&value| value == 0) {
95 return ParticipationDiff::AllZeros(
96 target
97 .len()
98 .try_into()
99 .expect("target length exceeds u32::MAX"),
100 );
101 }
102
103 diff_participation_iter(base.iter().copied(), target.iter().copied())
104}
105
106pub fn apply_participation(
133 base: &mut Vec<u8>,
134 delta: &ArchivedParticipationDiff,
135) -> Result<(), Error> {
136 match delta {
137 ArchivedParticipationDiff::AllZeros(len) => {
138 let len = usize::try_from(len.to_native()).map_err(|_| {
139 Error::InvalidDelta("all-zero participation length does not fit in usize".into())
140 })?;
141
142 base.clear();
143 base.resize(len, 0);
144
145 Ok(())
146 }
147 ArchivedParticipationDiff::Sparse { .. } => apply_participation_iter(base, delta),
148 }
149}
150
151pub fn diff_participation_iter<I1, I2>(mut base: I1, mut target: I2) -> ParticipationDiff
175where
176 I1: ExactSizeIterator<Item = u8>,
177 I2: ExactSizeIterator<Item = u8>,
178{
179 let common_len = base.len().min(target.len());
180
181 let mut sparse_indices = Vec::with_capacity(50_000);
182 let mut new_values = Vec::with_capacity(50_000);
183 let mut last_idx = 0u64;
184
185 for i in 0..common_len {
186 let Some(v1) = base.next() else {
187 break;
188 };
189
190 let Some(v2) = target.next() else {
191 break;
192 };
193
194 if v1 != v2 {
195 let idx = i as u64;
196
197 let gap = idx
198 .checked_sub(last_idx)
199 .expect("changed indices are processed in strictly increasing iterator order");
200 write_varint(gap, &mut sparse_indices);
201
202 new_values.push(v2);
203 last_idx = idx;
204 }
205 }
206
207 let extension = target.collect();
208
209 ParticipationDiff::Sparse {
210 sparse_indices,
211 new_values,
212 extension,
213 }
214}
215
216pub fn apply_participation_iter<T: crate::ListMutTarget<u8>>(
250 target: &mut T,
251 delta: &ArchivedParticipationDiff,
252) -> Result<(), Error> {
253 let ArchivedParticipationDiff::Sparse {
254 sparse_indices,
255 new_values,
256 extension,
257 } = delta
258 else {
259 return Err(Error::InvalidDelta(
260 "AllZeros participation delta cannot be applied through the generic iterator API"
261 .into(),
262 ));
263 };
264
265 let indices_raw = sparse_indices.as_slice();
266 let mut cursor = 0usize;
267 let mut current_idx = 0usize;
268
269 for value in new_values.iter() {
270 let gap = read_varint(indices_raw, &mut cursor)?;
271
272 let gap = usize::try_from(gap).map_err(|_| {
273 Error::MalformedDelta("participation index gap does not fit in usize".into())
274 })?;
275
276 current_idx = current_idx.checked_add(gap).ok_or_else(|| {
277 Error::MalformedDelta(
278 "participation index overflow while decoding sparse indices".into(),
279 )
280 })?;
281
282 let Some(target_value) = target.get_mut(current_idx) else {
283 return Err(Error::InvalidDelta(format!(
284 "participation index {current_idx} is outside target collection of length {}",
285 target.len()
286 )));
287 };
288
289 *target_value = *value;
290 }
291
292 if cursor != indices_raw.len() {
293 return Err(Error::InvalidDelta(
294 "participation sparse index payload contains unused bytes".into(),
295 ));
296 }
297
298 for byte in extension.iter() {
299 target.push(*byte);
300 }
301
302 Ok(())
303}
304
305#[cfg(test)]
306mod tests {
307 use super::*;
308 use crate::types::ArchivedParticipationDiff;
309 use crate::ListMutTarget;
310
311 struct MockTarget {
312 inner: Vec<u8>,
313 }
314
315 impl ListMutTarget<u8> for MockTarget {
316 fn len(&self) -> usize {
317 self.inner.len()
318 }
319
320 fn get_mut(&mut self, index: usize) -> Option<&mut u8> {
321 self.inner.get_mut(index)
322 }
323
324 fn push(&mut self, value: u8) {
325 self.inner.push(value);
326 }
327 }
328
329 fn assert_sparse_roundtrip(base: &[u8], target: &[u8]) {
331 let delta = diff_participation(base, target);
332
333 match &delta {
334 ParticipationDiff::AllZeros(_) => panic!("test setup: expected sparse delta"),
335 ParticipationDiff::Sparse { .. } => {}
336 }
337
338 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&delta).expect("test setup: serialize");
339 let archived = rkyv::access::<ArchivedParticipationDiff, rkyv::rancor::Error>(&bytes)
340 .expect("test setup: failed to access archived delta");
341
342 let mut reconstructed = base.to_vec();
343 apply_participation(&mut reconstructed, archived).expect("test setup: apply");
344
345 assert_eq!(reconstructed, target);
346 }
347
348 #[test]
349 fn test_diff_slice_no_changes() {
350 let base = vec![1, 2, 3];
351 let target = vec![1, 2, 3];
352 let delta = diff_participation(&base, &target);
353
354 match delta {
355 ParticipationDiff::Sparse {
356 sparse_indices,
357 new_values,
358 extension,
359 } => {
360 assert!(sparse_indices.is_empty());
361 assert!(new_values.is_empty());
362 assert!(extension.is_empty());
363 }
364 _ => panic!("test setup: expected sparse"),
365 }
366 }
367
368 #[test]
369 fn test_diff_slice_all_zeros_fast_path() {
370 let base = vec![1, 2, 3];
371 let target = vec![0, 0, 0];
372 let delta = diff_participation(&base, &target);
373
374 match delta {
375 ParticipationDiff::AllZeros(len) => assert_eq!(len, 3),
376 _ => panic!("expected AllZeros fast path"),
377 }
378 }
379
380 #[test]
381 fn test_apply_all_zeros() {
382 let mut base = vec![1, 2, 3];
383 let delta = ParticipationDiff::AllZeros(5);
384
385 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&delta).expect("test setup: serialize");
386 let archived = rkyv::access::<ArchivedParticipationDiff, rkyv::rancor::Error>(&bytes)
387 .expect("test setup: failed to access archived delta");
388
389 apply_participation(&mut base, archived).expect("test setup: apply");
390
391 assert_eq!(base, vec![0, 0, 0, 0, 0]);
392 }
393
394 #[test]
395 fn test_diff_iter_all_zeros_produces_sparse() {
396 let base = vec![1, 2, 3];
397 let target = vec![0, 0, 0];
398 let delta = diff_participation_iter(base.into_iter(), target.into_iter());
399
400 match delta {
401 ParticipationDiff::Sparse { .. } => {} _ => panic!("iterator API must produce sparse, not AllZeros"),
403 }
404 }
405
406 #[test]
407 fn test_sparse_roundtrip_with_changes() {
408 let base = vec![0, 0, 0, 0];
409 let target = vec![1, 0, 2, 0];
410 assert_sparse_roundtrip(&base, &target);
411 }
412
413 #[test]
414 fn test_sparse_roundtrip_with_appended() {
415 let base = vec![0];
416 let target = vec![0, 5, 6];
417 assert_sparse_roundtrip(&base, &target);
418 }
419
420 #[test]
421 fn test_sparse_roundtrip_combined() {
422 let base = vec![10, 20, 30];
423 let target = vec![10, 99, 30, 40, 50]; assert_sparse_roundtrip(&base, &target);
425 }
426
427 #[test]
428 fn test_apply_all_zeros_via_iter_api_errors() {
429 let delta = ParticipationDiff::AllZeros(5);
430 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&delta).expect("test setup: serialize");
431 let archived = rkyv::access::<ArchivedParticipationDiff, rkyv::rancor::Error>(&bytes)
432 .expect("test setup: failed to access archived delta");
433
434 let mut target = MockTarget { inner: vec![] };
435 let result = apply_participation_iter(&mut target, archived);
436
437 assert!(result.is_err());
438 let err_str = format!("{}", result.expect_err("test setup"));
439 assert!(err_str.contains("AllZeros participation delta cannot be applied"));
440 }
441
442 #[test]
443 fn test_apply_sparse_mismatched_counts() {
444 let delta = ParticipationDiff::Sparse {
446 sparse_indices: vec![0], new_values: vec![],
448 extension: vec![],
449 };
450 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&delta).expect("test setup: serialize");
451 let archived = rkyv::access::<ArchivedParticipationDiff, rkyv::rancor::Error>(&bytes)
452 .expect("test setup: failed to access archived delta");
453
454 let mut target = MockTarget { inner: vec![0] };
455 let result = apply_participation_iter(&mut target, archived);
456
457 assert!(result.is_err());
458 let err_str = format!("{}", result.expect_err("test setup"));
459 assert!(err_str.contains("unused bytes"));
460 }
461
462 #[test]
463 fn test_apply_sparse_index_out_of_bounds() {
464 let delta = ParticipationDiff::Sparse {
466 sparse_indices: vec![5],
467 new_values: vec![99],
468 extension: vec![],
469 };
470 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&delta).expect("test setup: serialize");
471 let archived = rkyv::access::<ArchivedParticipationDiff, rkyv::rancor::Error>(&bytes)
472 .expect("test setup: failed to access archived delta");
473
474 let mut target = MockTarget { inner: vec![0; 3] };
475 let result = apply_participation_iter(&mut target, archived);
476
477 assert!(result.is_err());
478 let err_str = format!("{}", result.expect_err("test setup"));
479 assert!(err_str.contains("outside target collection"));
480 }
481
482 #[test]
483 fn test_apply_sparse_truncated_varint() {
484 let delta = ParticipationDiff::Sparse {
485 sparse_indices: vec![0xFF], new_values: vec![0],
487 extension: vec![],
488 };
489 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&delta).expect("test setup: serialize");
490 let archived = rkyv::access::<ArchivedParticipationDiff, rkyv::rancor::Error>(&bytes)
491 .expect("test setup: failed to access archived delta");
492
493 let mut target = MockTarget { inner: vec![0] };
494 let result = apply_participation_iter(&mut target, archived);
495
496 assert!(result.is_err());
497 let err_str = format!("{}", result.expect_err("test setup"));
498 assert!(err_str.contains("truncated varint"));
499 }
500
501 #[test]
502 fn test_apply_sparse_index_sum_overflow() {
503 let sparse_indices: Vec<u8> = vec![
506 0xE7, 0x07, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x01, ];
509
510 let delta = ParticipationDiff::Sparse {
511 sparse_indices,
512 new_values: vec![0, 0],
513 extension: vec![],
514 };
515 let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&delta).expect("test setup: serialize");
516 let archived = rkyv::access::<ArchivedParticipationDiff, rkyv::rancor::Error>(&bytes)
517 .expect("test setup: failed to access archived delta");
518
519 let mut target = MockTarget {
521 inner: vec![0; 1000],
522 };
523 let result = apply_participation_iter(&mut target, archived);
524
525 assert!(result.is_err());
526 let err_str = format!("{}", result.expect_err("test setup"));
527 assert!(err_str.contains("index overflow"));
528 }
529}