use crate::{
error::Error,
types::{
ArchivedValidatorField, ArchivedValidatorsDiff, ValidatorField, ValidatorPatch,
ValidatorsDiff, MIN_VALIDATOR_WITHDRAWABILITY_DELAY, VALIDATOR_SSZ_SIZE,
},
};
pub trait ValidatorSnapshot {
fn withdrawal_credentials(&self) -> &[u8; 32];
fn effective_balance(&self) -> u64;
fn is_slashed(&self) -> bool;
fn activation_eligibility_epoch(&self) -> u64;
fn activation_epoch(&self) -> u64;
fn exit_epoch(&self) -> u64;
fn withdrawable_epoch(&self) -> u64;
fn to_ssz_bytes(&self) -> Vec<u8>;
}
pub trait ValidatorMut {
fn is_slashed(&self) -> bool;
fn set_withdrawal_credentials(&mut self, value: &[u8; 32]);
fn set_effective_balance(&mut self, value: u64);
fn set_slashed(&mut self, value: bool);
fn set_activation_eligibility_epoch(&mut self, value: u64);
fn set_activation_epoch(&mut self, value: u64);
fn set_exit_epoch(&mut self, value: u64);
fn set_withdrawable_epoch(&mut self, value: u64);
}
pub trait ValidatorMutTarget {
type Validator<'a>: ValidatorMut
where
Self: 'a;
fn get_mut(&mut self, index: usize) -> Option<Self::Validator<'_>>;
fn push_from_ssz(&mut self, ssz_bytes: &[u8]);
}
pub fn diff_validators(base_bytes: &[u8], target_bytes: &[u8]) -> ValidatorsDiff {
diff_validators_impl(
base_bytes
.chunks_exact(VALIDATOR_SSZ_SIZE)
.map(ByteValidator::new),
target_bytes
.chunks_exact(VALIDATOR_SSZ_SIZE)
.map(ByteValidator::new),
)
}
pub fn diff_validators_iter<I1, I2, V1, V2>(base: I1, target: I2) -> ValidatorsDiff
where
I1: ExactSizeIterator<Item = V1>,
I2: ExactSizeIterator<Item = V2>,
V1: ValidatorSnapshot,
V2: ValidatorSnapshot,
{
diff_validators_impl(base, target)
}
fn diff_validators_impl<I1, I2, V1, V2>(mut base: I1, mut target: I2) -> ValidatorsDiff
where
I1: ExactSizeIterator<Item = V1>,
I2: ExactSizeIterator<Item = V2>,
V1: ValidatorSnapshot,
V2: ValidatorSnapshot,
{
let mut patches = Vec::with_capacity(512);
let mut appended_validators = Vec::new();
for (i, (b, t)) in base.by_ref().zip(target.by_ref()).enumerate() {
let wc = t.withdrawal_credentials();
let eb = t.effective_balance();
let slashed = t.is_slashed();
let aee = t.activation_eligibility_epoch();
let ae = t.activation_epoch();
let ee = t.exit_epoch();
if b.withdrawal_credentials() == wc
&& b.effective_balance() == eb
&& b.is_slashed() == slashed
&& b.activation_eligibility_epoch() == aee
&& b.activation_epoch() == ae
&& b.exit_epoch() == ee
&& (!slashed || b.withdrawable_epoch() == t.withdrawable_epoch())
{
continue;
}
let index = u32::try_from(i).expect("validator index exceeds u32 range");
if b.withdrawal_credentials() != wc {
patches.push(ValidatorPatch {
index,
field: ValidatorField::WithdrawalCredentials,
value: wc.to_vec(),
});
}
if b.effective_balance() != eb {
patches.push(ValidatorPatch {
index,
field: ValidatorField::EffectiveBalance,
value: eb.to_le_bytes().to_vec(),
});
}
if b.is_slashed() != slashed {
patches.push(ValidatorPatch {
index,
field: ValidatorField::Slashed,
value: vec![slashed as u8],
});
}
if b.activation_eligibility_epoch() != aee {
patches.push(ValidatorPatch {
index,
field: ValidatorField::ActivationEligibilityEpoch,
value: aee.to_le_bytes().to_vec(),
});
}
if b.activation_epoch() != ae {
patches.push(ValidatorPatch {
index,
field: ValidatorField::ActivationEpoch,
value: ae.to_le_bytes().to_vec(),
});
}
if b.exit_epoch() != ee {
patches.push(ValidatorPatch {
index,
field: ValidatorField::ExitEpoch,
value: ee.to_le_bytes().to_vec(),
});
}
if slashed && b.withdrawable_epoch() != t.withdrawable_epoch() {
patches.push(ValidatorPatch {
index,
field: ValidatorField::WithdrawableEpochSlashed,
value: t.withdrawable_epoch().to_le_bytes().to_vec(),
});
}
}
for t_val in target {
appended_validators.extend(t_val.to_ssz_bytes());
}
ValidatorsDiff {
patches,
appended_validators,
}
}
pub fn apply_validators(base: &mut Vec<u8>, delta: &ArchivedValidatorsDiff) -> Result<(), Error> {
apply_validators_iter(&mut ByteValidatorTarget(base), delta)
}
pub fn apply_validators_iter<T: ValidatorMutTarget>(
target: &mut T,
delta: &ArchivedValidatorsDiff,
) -> Result<(), Error> {
for patch in delta.patches.iter() {
let idx = patch.index.to_native() as usize;
let val_bytes = patch.value.as_slice();
let mut validator = target.get_mut(idx).ok_or_else(|| {
Error::InvalidDelta(format!("validator patch index {idx} is out of bounds"))
})?;
match &patch.field {
ArchivedValidatorField::WithdrawalCredentials => {
let bytes: [u8; 32] = val_bytes.try_into().map_err(|_| {
Error::MalformedDelta(format!(
"withdrawal credentials patch has invalid width: \
expected 32 bytes, got {}",
val_bytes.len()
))
})?;
validator.set_withdrawal_credentials(&bytes);
}
ArchivedValidatorField::EffectiveBalance => {
let bytes: [u8; 8] = val_bytes.try_into().map_err(|_| {
Error::MalformedDelta(format!(
"effective balance patch has invalid width: \
expected 8 bytes, got {}",
val_bytes.len()
))
})?;
let eb = u64::from_le_bytes(bytes);
validator.set_effective_balance(eb);
}
ArchivedValidatorField::Slashed => {
let [value] = <[u8; 1]>::try_from(val_bytes).map_err(|_| {
Error::MalformedDelta(format!(
"slashed patch has invalid width: expected 1 byte, got {}",
val_bytes.len()
))
})?;
validator.set_slashed(value != 0);
}
ArchivedValidatorField::ActivationEligibilityEpoch => {
let bytes: [u8; 8] = val_bytes.try_into().map_err(|_| {
Error::MalformedDelta(format!(
"activation eligibility epoch patch has invalid width: \
expected 8 bytes, got {}",
val_bytes.len()
))
})?;
let epoch = u64::from_le_bytes(bytes);
validator.set_activation_eligibility_epoch(epoch);
}
ArchivedValidatorField::ActivationEpoch => {
let bytes: [u8; 8] = val_bytes.try_into().map_err(|_| {
Error::MalformedDelta(format!(
"activation epoch patch has invalid width: \
expected 8 bytes, got {}",
val_bytes.len()
))
})?;
let epoch = u64::from_le_bytes(bytes);
validator.set_activation_epoch(epoch);
}
ArchivedValidatorField::ExitEpoch => {
let bytes: [u8; 8] = val_bytes.try_into().map_err(|_| {
Error::MalformedDelta(format!(
"exit epoch patch has invalid width: \
expected 8 bytes, got {}",
val_bytes.len()
))
})?;
let ee = u64::from_le_bytes(bytes);
validator.set_exit_epoch(ee);
if !validator.is_slashed() {
let we = ee.saturating_add(MIN_VALIDATOR_WITHDRAWABILITY_DELAY);
validator.set_withdrawable_epoch(we);
}
}
ArchivedValidatorField::WithdrawableEpochSlashed => {
let bytes: [u8; 8] = val_bytes.try_into().map_err(|_| {
Error::MalformedDelta(format!(
"withdrawable epoch patch has invalid width: \
expected 8 bytes, got {}",
val_bytes.len()
))
})?;
let we = u64::from_le_bytes(bytes);
validator.set_withdrawable_epoch(we);
}
}
}
if !delta
.appended_validators
.len()
.is_multiple_of(VALIDATOR_SSZ_SIZE)
{
return Err(Error::MalformedDelta(
"appended validator data does not contain complete SSZ records".into(),
));
}
for chunk in delta
.appended_validators
.as_slice()
.chunks_exact(VALIDATOR_SSZ_SIZE)
{
target.push_from_ssz(chunk);
}
Ok(())
}
struct ByteValidator<'a>(&'a [u8]);
impl<'a> ByteValidator<'a> {
fn new(bytes: &'a [u8]) -> Self {
debug_assert_eq!(
bytes.len(),
VALIDATOR_SSZ_SIZE,
"ByteValidator must contain exactly one complete SSZ validator record",
);
Self(bytes)
}
#[inline]
fn bytes<const N: usize>(&self, start: usize) -> [u8; N] {
self.0
.get(start..start + N)
.and_then(|bytes| bytes.try_into().ok())
.expect("ByteValidator contains a complete SSZ validator record")
}
}
impl<'a> ValidatorSnapshot for ByteValidator<'a> {
#[inline]
fn withdrawal_credentials(&self) -> &[u8; 32] {
self.0
.get(48..80)
.and_then(|bytes| bytes.try_into().ok())
.expect("ByteValidator contains a complete SSZ validator record")
}
#[inline]
fn effective_balance(&self) -> u64 {
u64::from_le_bytes(self.bytes::<8>(80))
}
#[inline]
fn is_slashed(&self) -> bool {
self.bytes::<1>(88)[0] != 0
}
#[inline]
fn activation_eligibility_epoch(&self) -> u64 {
u64::from_le_bytes(self.bytes::<8>(89))
}
#[inline]
fn activation_epoch(&self) -> u64 {
u64::from_le_bytes(self.bytes::<8>(97))
}
#[inline]
fn exit_epoch(&self) -> u64 {
u64::from_le_bytes(self.bytes::<8>(105))
}
#[inline]
fn withdrawable_epoch(&self) -> u64 {
u64::from_le_bytes(self.bytes::<8>(113))
}
#[inline]
fn to_ssz_bytes(&self) -> Vec<u8> {
self.0.to_vec()
}
}
struct ByteValidatorMut<'a>(&'a mut [u8]);
impl<'a> ValidatorMut for ByteValidatorMut<'a> {
#[inline]
fn is_slashed(&self) -> bool {
*self
.0
.get(88)
.expect("ByteValidatorMut contains a complete SSZ validator record")
!= 0
}
#[inline]
fn set_withdrawal_credentials(&mut self, v: &[u8; 32]) {
self.0
.get_mut(48..80)
.expect("ByteValidatorMut contains a complete SSZ validator record")
.copy_from_slice(v);
}
#[inline]
fn set_effective_balance(&mut self, v: u64) {
self.0
.get_mut(80..88)
.expect("ByteValidatorMut contains a complete SSZ validator record")
.copy_from_slice(&v.to_le_bytes());
}
#[inline]
fn set_slashed(&mut self, v: bool) {
*self
.0
.get_mut(88)
.expect("ByteValidatorMut contains a complete SSZ validator record") = v as u8;
}
#[inline]
fn set_activation_eligibility_epoch(&mut self, v: u64) {
self.0
.get_mut(89..97)
.expect("ByteValidatorMut contains a complete SSZ validator record")
.copy_from_slice(&v.to_le_bytes());
}
#[inline]
fn set_activation_epoch(&mut self, v: u64) {
self.0
.get_mut(97..105)
.expect("ByteValidatorMut contains a complete SSZ validator record")
.copy_from_slice(&v.to_le_bytes());
}
#[inline]
fn set_exit_epoch(&mut self, v: u64) {
self.0
.get_mut(105..113)
.expect("ByteValidatorMut contains a complete SSZ validator record")
.copy_from_slice(&v.to_le_bytes());
}
#[inline]
fn set_withdrawable_epoch(&mut self, v: u64) {
self.0
.get_mut(113..121)
.expect("ByteValidatorMut contains a complete SSZ validator record")
.copy_from_slice(&v.to_le_bytes());
}
}
struct ByteValidatorTarget<'a>(&'a mut Vec<u8>);
impl<'a> ValidatorMutTarget for ByteValidatorTarget<'a> {
type Validator<'b>
= ByteValidatorMut<'b>
where
Self: 'b;
fn get_mut(&mut self, index: usize) -> Option<Self::Validator<'_>> {
let start = index.checked_mul(VALIDATOR_SSZ_SIZE)?;
let end = start.checked_add(VALIDATOR_SSZ_SIZE)?;
self.0.get_mut(start..end).map(ByteValidatorMut)
}
fn push_from_ssz(&mut self, ssz_bytes: &[u8]) {
self.0.extend_from_slice(ssz_bytes);
}
}