use crate::types::{
ArchivedValidatorField, ArchivedValidatorsDiff, ValidatorField, ValidatorPatch, ValidatorsDiff,
MIN_VALIDATOR_WITHDRAWABILITY_DELAY, VALIDATOR_SSZ_SIZE,
};
pub fn diff_validators(base_bytes: &[u8], target_bytes: &[u8]) -> ValidatorsDiff {
let base_len = base_bytes.len() / VALIDATOR_SSZ_SIZE;
let target_len = target_bytes.len() / VALIDATOR_SSZ_SIZE;
let common_len = base_len.min(target_len);
let mut patches = Vec::with_capacity(512);
for i in 0..common_len {
let b_start = i * VALIDATOR_SSZ_SIZE;
let t_start = i * VALIDATOR_SSZ_SIZE;
if base_bytes[b_start..b_start + VALIDATOR_SSZ_SIZE]
== target_bytes[t_start..t_start + VALIDATOR_SSZ_SIZE]
{
continue;
}
let base_wc = &base_bytes[b_start + 48..b_start + 80];
let target_wc = &target_bytes[t_start + 48..t_start + 80];
if base_wc != target_wc {
patches.push(ValidatorPatch {
index: i as u32,
field: ValidatorField::WithdrawalCredentials,
value: target_wc.to_vec(),
});
}
let base_eb = &base_bytes[b_start + 80..b_start + 88];
let target_eb = &target_bytes[t_start + 80..t_start + 88];
if base_eb != target_eb {
patches.push(ValidatorPatch {
index: i as u32,
field: ValidatorField::EffectiveBalance,
value: target_eb.to_vec(),
});
}
if base_bytes[b_start + 88] != target_bytes[t_start + 88] {
patches.push(ValidatorPatch {
index: i as u32,
field: ValidatorField::Slashed,
value: vec![target_bytes[t_start + 88]],
});
}
let base_aee = &base_bytes[b_start + 89..b_start + 97];
let target_aee = &target_bytes[t_start + 89..t_start + 97];
if base_aee != target_aee {
patches.push(ValidatorPatch {
index: i as u32,
field: ValidatorField::ActivationEligibilityEpoch,
value: target_aee.to_vec(),
});
}
let base_ae = &base_bytes[b_start + 97..b_start + 105];
let target_ae = &target_bytes[t_start + 97..t_start + 105];
if base_ae != target_ae {
patches.push(ValidatorPatch {
index: i as u32,
field: ValidatorField::ActivationEpoch,
value: target_ae.to_vec(),
});
}
let base_ee = &base_bytes[b_start + 105..b_start + 113];
let target_ee = &target_bytes[t_start + 105..t_start + 113];
if base_ee != target_ee {
patches.push(ValidatorPatch {
index: i as u32,
field: ValidatorField::ExitEpoch,
value: target_ee.to_vec(),
});
}
if target_bytes[t_start + 88] != 0 {
let base_we = &base_bytes[b_start + 113..b_start + 121];
let target_we = &target_bytes[t_start + 113..t_start + 121];
if base_we != target_we {
patches.push(ValidatorPatch {
index: i as u32,
field: ValidatorField::WithdrawableEpochSlashed,
value: target_we.to_vec(),
});
}
}
}
let appended_start = common_len * VALIDATOR_SSZ_SIZE;
let appended_validators = if target_bytes.len() > appended_start {
target_bytes[appended_start..].to_vec()
} else {
Vec::new()
};
ValidatorsDiff {
patches,
appended_validators,
}
}
pub fn apply_validators(base: &mut Vec<u8>, delta: &ArchivedValidatorsDiff) {
for patch in delta.patches.iter() {
let idx = patch.index.to_native() as usize;
let start = idx * VALIDATOR_SSZ_SIZE;
debug_assert!(
start + VALIDATOR_SSZ_SIZE <= base.len(),
"Patch index out of bounds"
);
let val_bytes = patch.value.as_slice();
match &patch.field {
ArchivedValidatorField::WithdrawalCredentials => {
base[start + 48..start + 80].copy_from_slice(val_bytes);
}
ArchivedValidatorField::EffectiveBalance => {
base[start + 80..start + 88].copy_from_slice(val_bytes);
}
ArchivedValidatorField::Slashed => {
base[start + 88] = val_bytes[0];
}
ArchivedValidatorField::ActivationEligibilityEpoch => {
base[start + 89..start + 97].copy_from_slice(val_bytes);
}
ArchivedValidatorField::ActivationEpoch => {
base[start + 97..start + 105].copy_from_slice(val_bytes);
}
ArchivedValidatorField::ExitEpoch => {
base[start + 105..start + 113].copy_from_slice(val_bytes);
let is_slashed = base[start + 88] != 0;
if !is_slashed {
let exit_bytes: [u8; 8] = val_bytes
.try_into()
.expect("Exit epoch patch must be 8 bytes");
let exit_epoch = u64::from_le_bytes(exit_bytes);
let withdrawable_epoch =
exit_epoch.saturating_add(MIN_VALIDATOR_WITHDRAWABILITY_DELAY);
base[start + 113..start + 121]
.copy_from_slice(&withdrawable_epoch.to_le_bytes());
}
}
ArchivedValidatorField::WithdrawableEpochSlashed => {
base[start + 113..start + 121].copy_from_slice(val_bytes);
}
}
}
if !delta.appended_validators.is_empty() {
base.extend_from_slice(delta.appended_validators.as_slice());
}
}