use crate::types::{ArchivedHistoricalLogDiff, HistoricalLogDiff};
const SLOTS_PER_HISTORICAL_PERIOD: u64 = 8192;
#[inline]
fn calculate_log_count(slot: u64, activation_slot: Option<u64>) -> u64 {
match activation_slot {
Some(act_slot) if slot <= act_slot => 0,
Some(act_slot) => (slot - act_slot) / SLOTS_PER_HISTORICAL_PERIOD,
None => (slot + 1) / SLOTS_PER_HISTORICAL_PERIOD,
}
}
pub fn diff_historical_log(
base_slot: u64,
target_slot: u64,
target_ssz: &[u8],
item_ssz_size: usize,
activation_slot: Option<u64>,
) -> HistoricalLogDiff {
let base_count = calculate_log_count(base_slot, activation_slot);
let target_count = calculate_log_count(target_slot, activation_slot);
let Some(items_to_append) = target_count.checked_sub(base_count) else {
return HistoricalLogDiff::Unchanged;
};
if items_to_append == 0 {
return HistoricalLogDiff::Unchanged;
}
let required_bytes = usize::try_from(items_to_append)
.ok()
.and_then(|count| count.checked_mul(item_ssz_size))
.expect("items_to_append * item_ssz_size fits in usize");
debug_assert!(
target_ssz.len() >= required_bytes,
"Historical log math expects {required_bytes} bytes, but target_ssz is too short",
);
let start_byte = target_ssz.len().checked_sub(required_bytes).expect(
"target_ssz must be large enough to hold the derived items, as guaranteed by caller",
);
let appended_bytes = target_ssz
.get(start_byte..)
.expect("start_byte is valid as it is derived from target_ssz.len()");
HistoricalLogDiff::Append(appended_bytes.to_vec())
}
pub fn apply_historical_log(base: &mut Vec<u8>, delta: &ArchivedHistoricalLogDiff) {
match delta {
ArchivedHistoricalLogDiff::Unchanged => {}
ArchivedHistoricalLogDiff::Append(bytes) => {
base.extend_from_slice(bytes.as_slice());
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::types::ArchivedHistoricalLogDiff;
const ITEM_SIZE: usize = 32;
fn archive(diff: &HistoricalLogDiff) -> rkyv::util::AlignedVec {
rkyv::to_bytes::<rkyv::rancor::Error>(diff).expect("test setup: failed to serialize delta")
}
fn archived(bytes: &[u8]) -> &ArchivedHistoricalLogDiff {
rkyv::access::<ArchivedHistoricalLogDiff, rkyv::rancor::Error>(bytes)
.expect("test setup: failed to access archived delta")
}
#[test]
fn test_calculate_log_count_genesis() {
assert_eq!(calculate_log_count(0, None), 0);
}
#[test]
fn test_calculate_log_count_first_period_end() {
assert_eq!(calculate_log_count(8191, None), 1);
}
#[test]
fn test_calculate_log_count_second_period_start() {
assert_eq!(calculate_log_count(8192, None), 1);
}
#[test]
fn test_calculate_log_count_exact_multiple() {
assert_eq!(calculate_log_count(16383, None), 2);
}
#[test]
fn test_calculate_log_count_before_activation() {
assert_eq!(calculate_log_count(0, Some(1000)), 0);
assert_eq!(calculate_log_count(1000, Some(1000)), 0);
}
#[test]
fn test_calculate_log_count_just_after_activation() {
assert_eq!(calculate_log_count(1001, Some(1000)), 0);
}
#[test]
fn test_calculate_log_count_first_period_after_activation() {
assert_eq!(calculate_log_count(9192, Some(1000)), 1);
}
#[test]
fn test_diff_unchanged_within_period() {
let target = vec![0u8; ITEM_SIZE * 2]; let delta = diff_historical_log(100, 200, &target, ITEM_SIZE, None);
assert!(matches!(delta, HistoricalLogDiff::Unchanged));
}
#[test]
fn test_diff_append_crosses_one_period() {
let target = vec![1u8; ITEM_SIZE];
let delta = diff_historical_log(0, 8192, &target, ITEM_SIZE, None);
match delta {
HistoricalLogDiff::Append(bytes) => assert_eq!(bytes, vec![1u8; ITEM_SIZE]),
_ => panic!("test setup: expected Append"),
}
}
#[test]
fn test_diff_append_crosses_two_periods() {
let target = vec![0u8; ITEM_SIZE * 2];
let delta = diff_historical_log(8191, 16383, &target, ITEM_SIZE, None);
match delta {
HistoricalLogDiff::Append(bytes) => {
assert_eq!(bytes.len(), ITEM_SIZE);
assert_eq!(bytes, vec![0u8; ITEM_SIZE]);
}
_ => panic!("test setup: expected Append"),
}
}
#[test]
fn test_diff_unchanged_with_activation_slot() {
let target = vec![0u8; ITEM_SIZE];
let delta = diff_historical_log(0, 100, &target, ITEM_SIZE, Some(1000));
assert!(matches!(delta, HistoricalLogDiff::Unchanged));
}
#[test]
fn test_diff_append_with_activation_slot() {
let target = vec![1u8; ITEM_SIZE];
let delta = diff_historical_log(1000, 9192, &target, ITEM_SIZE, Some(1000));
match delta {
HistoricalLogDiff::Append(bytes) => assert_eq!(bytes, vec![1u8; ITEM_SIZE]),
_ => panic!("test setup: expected Append"),
}
}
#[test]
fn test_apply_unchanged() {
let mut base = vec![0u8; ITEM_SIZE];
let delta = HistoricalLogDiff::Unchanged;
let bytes = archive(&delta);
let archived = archived(&bytes);
apply_historical_log(&mut base, archived);
assert_eq!(base, vec![0u8; ITEM_SIZE]);
}
#[test]
fn test_apply_append() {
let mut base = vec![0u8; ITEM_SIZE];
let delta = HistoricalLogDiff::Append(vec![1u8; ITEM_SIZE]);
let bytes = archive(&delta);
let archived = archived(&bytes);
apply_historical_log(&mut base, archived);
let mut expected = vec![0u8; ITEM_SIZE];
expected.extend_from_slice(&[1u8; ITEM_SIZE]);
assert_eq!(base, expected);
}
#[test]
fn test_full_roundtrip_no_change() {
let base_slot = 100;
let target_slot = 200;
let target_ssz = vec![0u8; ITEM_SIZE];
let delta = diff_historical_log(base_slot, target_slot, &target_ssz, ITEM_SIZE, None);
let bytes = archive(&delta);
let archived = archived(&bytes);
let mut base_ssz = vec![0u8; ITEM_SIZE];
apply_historical_log(&mut base_ssz, archived);
assert_eq!(base_ssz, target_ssz);
}
#[test]
fn test_full_roundtrip_with_append() {
let base_slot = 0;
let target_slot = 8191;
let target_ssz = vec![0u8; ITEM_SIZE];
let delta = diff_historical_log(base_slot, target_slot, &target_ssz, ITEM_SIZE, None);
let bytes = archive(&delta);
let archived = archived(&bytes);
let mut base_ssz = vec![]; apply_historical_log(&mut base_ssz, archived);
assert_eq!(base_ssz, target_ssz);
}
}