use crate::types::{ArchivedAttestationsDiff, AttestationsDiff};
pub fn diff_attestations(base_ssz: &[u8], target_ssz: &[u8]) -> AttestationsDiff {
if base_ssz == target_ssz {
return AttestationsDiff::Unchanged;
}
if target_ssz.starts_with(base_ssz) {
let appended = &target_ssz[base_ssz.len()..];
AttestationsDiff::Append(appended.to_vec())
} else {
AttestationsDiff::FullReplacement(target_ssz.to_vec())
}
}
pub fn apply_attestations(base: &mut Vec<u8>, delta: &ArchivedAttestationsDiff) {
match delta {
ArchivedAttestationsDiff::Unchanged => {}
ArchivedAttestationsDiff::Append(bytes) => {
base.extend_from_slice(bytes.as_slice());
}
ArchivedAttestationsDiff::FullReplacement(bytes) => {
base.clear();
base.extend_from_slice(bytes.as_slice());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::{ArchivedAttestationsDiff, AttestationsDiff};
use rkyv::Archive;
fn archive(diff: &AttestationsDiff) -> rkyv::util::AlignedVec {
rkyv::to_bytes::<rkyv::rancor::Error>(diff).expect("failed to archive diff")
}
fn archived(bytes: &[u8]) -> &ArchivedAttestationsDiff {
rkyv::access::<ArchivedAttestationsDiff, rkyv::rancor::Error>(bytes)
.expect("bytes are a valid rkyv payload generated by `archive`")
}
#[test]
fn diff_identical_lists_is_unchanged() {
assert_eq!(
diff_attestations(b"AAAA", b"AAAA"),
AttestationsDiff::Unchanged
);
}
#[test]
fn diff_target_extends_base_is_append() {
assert_eq!(
diff_attestations(b"AAAA", b"AAAABBBB"),
AttestationsDiff::Append(b"BBBB".to_vec()),
);
}
#[test]
fn diff_target_diverges_is_full_replacement() {
assert_eq!(
diff_attestations(b"AAAA", b"CCCC"),
AttestationsDiff::FullReplacement(b"CCCC".to_vec()),
);
}
#[test]
fn diff_target_diverges_mid_item_is_full_replacement() {
let base = b"AAAA-BBBB-CCCC-DDDD";
let target = b"AAAA-BBBB-XXXX-DDDD";
assert_eq!(
diff_attestations(base, target),
AttestationsDiff::FullReplacement(target.to_vec()),
);
}
#[test]
fn diff_empty_base_to_nonempty_is_append() {
assert_eq!(
diff_attestations(b"", b"AAAABBBB"),
AttestationsDiff::Append(b"AAAABBBB".to_vec()),
);
}
#[test]
fn diff_both_empty_is_unchanged() {
assert_eq!(diff_attestations(b"", b""), AttestationsDiff::Unchanged);
}
#[test]
fn diff_nonempty_base_to_empty_is_replacement_with_empty_payload() {
assert_eq!(
diff_attestations(b"AAAA", b""),
AttestationsDiff::FullReplacement(Vec::new()),
);
}
#[test]
fn apply_unchanged_leaves_buffer_untouched() {
let buf = archive(&AttestationsDiff::Unchanged);
let mut base = b"AAAA".to_vec();
apply_attestations(&mut base, archived(&buf));
assert_eq!(base, b"AAAA");
}
#[test]
fn apply_append_extends_buffer_in_place() {
let buf = archive(&AttestationsDiff::Append(b"BBBB".to_vec()));
let mut base = b"AAAA".to_vec();
apply_attestations(&mut base, archived(&buf));
assert_eq!(base, b"AAAABBBB");
}
#[test]
fn apply_full_replacement_clears_then_writes() {
let buf = archive(&AttestationsDiff::FullReplacement(b"CCCC".to_vec()));
let mut base = b"AAAA".to_vec();
apply_attestations(&mut base, archived(&buf));
assert_eq!(base, b"CCCC");
let buf = archive(&AttestationsDiff::FullReplacement(Vec::new()));
apply_attestations(&mut base, archived(&buf));
assert!(base.is_empty());
}
fn round_trip(base: &[u8], target: &[u8]) {
let diff = diff_attestations(base, target);
let buf = archive(&diff);
let mut current = base.to_vec();
apply_attestations(&mut current, archived(&buf));
assert_eq!(
current, target,
"round-trip failed for base={base:?} target={target:?}"
);
}
#[test]
fn round_trip_covers_all_branches() {
round_trip(b"AAAA", b"AAAA");
round_trip(b"AAAA", b"AAAABBBB");
round_trip(b"AAAA", b"CCCC");
round_trip(b"", b"AAAABBBB");
round_trip(b"", b"");
round_trip(b"AAAA", b"");
round_trip(b"AAAA-BBBB-CCCC-DDDD", b"AAAA-BBBB-XXXX-DDDD");
}
#[test]
fn round_trip_epoch_lifecycle_appends_then_replaces() {
let mut state: Vec<u8> = Vec::new();
let mut expected = Vec::new();
for chunk in [b"att1".as_slice(), b"att2", b"att3"] {
expected.extend_from_slice(chunk);
let buf = archive(&diff_attestations(&state, &expected));
apply_attestations(&mut state, archived(&buf));
assert_eq!(state, expected);
}
let new_epoch = b"prev1-prev2";
let diff = diff_attestations(&state, new_epoch);
assert!(
matches!(diff, AttestationsDiff::FullReplacement(_)),
"epoch transition must be a full replacement, got {diff:?}"
);
let buf = archive(&diff);
apply_attestations(&mut state, archived(&buf));
assert_eq!(state, new_epoch);
let mut expected = new_epoch.to_vec();
expected.extend_from_slice(b"-att4");
let diff = diff_attestations(&state, &expected);
assert!(
matches!(diff, AttestationsDiff::Append(_)),
"intra-epoch update must be an append, got {diff:?}"
);
let buf = archive(&diff);
apply_attestations(&mut state, archived(&buf));
assert_eq!(state, expected);
}
#[test]
fn round_trip_large_payloads() {
let one_att = b"AAAAAAAA-BBBBBBBB-CCCCCCCC-DDDDDDDD-EEEEEEEE";
let mut base = Vec::new();
for _ in 0..32 {
base.extend_from_slice(one_att);
}
let mut target = base.clone();
target.extend_from_slice(one_att);
target.extend_from_slice(one_att);
let diff = diff_attestations(&base, &target);
assert!(matches!(diff, AttestationsDiff::Append(_)));
let buf = archive(&diff);
let mut current = base.clone();
apply_attestations(&mut current, archived(&buf));
assert_eq!(current, target);
let mut divergent = target.clone();
*divergent.get_mut(0).expect("target vector is non-empty") ^= 0xFF;
let diff = diff_attestations(¤t, &divergent);
assert!(matches!(diff, AttestationsDiff::FullReplacement(_)));
let buf = archive(&diff);
apply_attestations(&mut current, archived(&buf));
assert_eq!(current, divergent);
}
#[test]
fn archived_alias_matches_attestations_diff() {
fn _assert_archived<T: Archive<Archived = ArchivedAttestationsDiff>>() {}
_assert_archived::<AttestationsDiff>();
}
}