Skip to main content

eth_state_diff/
validators.rs

1//! Delta encoding and reconstruction for Ethereum validator registries.
2//!
3//! This module computes compact [`ValidatorsDiff`] values between two validator
4//! registries and applies those deltas to reconstruct the target registry.
5//!
6//! Validator records are fixed-width in their consensus SSZ representation,
7//! with [`VALIDATOR_SSZ_SIZE`] bytes per validator. The delta format avoids
8//! storing complete validator records when only individual fields have
9//! changed.
10//!
11//! ## What is encoded
12//!
13//! For validators present in both the base and target registries, the encoder
14//! stores patches only for fields whose values changed:
15//!
16//! - [`ValidatorField::WithdrawalCredentials`]
17//! - [`ValidatorField::EffectiveBalance`]
18//! - [`ValidatorField::Slashed`]
19//! - [`ValidatorField::ActivationEligibilityEpoch`]
20//! - [`ValidatorField::ActivationEpoch`]
21//! - [`ValidatorField::ExitEpoch`]
22//! - [`ValidatorField::WithdrawableEpochSlashed`]
23//!
24//! Validators that exist only in the target registry are stored as their raw
25//! SSZ representations in [`ValidatorsDiff::appended_validators`].
26//!
27//! The `pubkey` field is not patched because validator public keys are
28//! immutable after registration.
29//!
30//! ## Withdrawable epoch
31//!
32//! `withdrawable_epoch` is handled specially because its value is derivable
33//! for non-slashed validators. When `exit_epoch` changes on a non-slashed
34//! validator, the application side reconstructs:
35//!
36//! ```text
37//! withdrawable_epoch = exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
38//! ```
39//!
40//! For slashed validators, `withdrawable_epoch` is explicitly encoded because
41//! it is not reconstructed from `exit_epoch` alone.
42//!
43//! This makes the delta smaller while preserving the target validator state.
44//!
45//! ## Two integration APIs
46//!
47//! The module provides two equivalent APIs for different validator storage
48//! layouts.
49//!
50//! ### Contiguous SSZ storage
51//!
52//! [`diff_validators`] and [`apply_validators`] operate directly on
53//! `Vec<u8>`/byte slices containing consecutive SSZ validator records.
54//!
55//! This is useful for clients that keep their validator registry in a flat
56//! SSZ-compatible representation.
57//!
58//! ### Native client storage
59//!
60//! [`diff_validators_iter`] and [`apply_validators_iter`] operate through
61//! [`ValidatorSnapshot`] and [`ValidatorMutTarget`].
62//!
63//! This allows a consensus client to diff and reconstruct validators without
64//! first converting its native data structure into one large byte buffer.
65//!
66//! The iterator API is particularly useful for clients whose validator
67//! registry is backed by persistent lists, trees, or other non-contiguous
68//! structures.
69//!
70//! ## SSZ representation
71//!
72//! The flat representation assumed by this module is the canonical
73//! consensus-layer validator SSZ layout:
74//!
75//! | Offset | Size | Field |
76//! |---:|---:|---|
77//! | `0` | `48` | `pubkey` |
78//! | `48` | `32` | `withdrawal_credentials` |
79//! | `80` | `8` | `effective_balance` |
80//! | `88` | `1` | `slashed` |
81//! | `89` | `8` | `activation_eligibility_epoch` |
82//! | `97` | `8` | `activation_epoch` |
83//! | `105` | `8` | `exit_epoch` |
84//! | `113` | `8` | `withdrawable_epoch` |
85//!
86//! The total size is [`VALIDATOR_SSZ_SIZE`] bytes.
87//!
88//! ## Complexity
89//!
90//! Diffing is linear in the number of validators:
91//!
92//! ```text
93//! O(min(base_len, target_len) + appended_validators)
94//! ```
95//!
96//! Applying a delta is linear in the number of patches plus the number of
97//! appended validators:
98//!
99//! ```text
100//! O(patches + appended_validators)
101//! ```
102//!
103//! The contiguous byte implementation performs in-place mutation and does not
104//! require rebuilding the existing validator registry.
105//!
106//! ## Delta validity
107//!
108//! [`apply_validators`] and [`apply_validators_iter`] assume that the supplied
109//! delta was produced for the corresponding base validator registry.
110//! Application does not independently verify that every patch matches the
111//! expected base value.
112//!
113//! Patch values are validated for the width required by their corresponding
114//! [`ValidatorField`]. Invalid patch data is reported as
115//! [`Error::MalformedDelta`] rather than causing reconstruction to panic.
116//!
117//! A patch referring to a validator index that does not exist in the target
118//! collection is reported as [`Error::InvalidDelta`].
119//!
120//! Appended validator data must contain complete
121//! [`VALIDATOR_SSZ_SIZE`]-byte SSZ records.
122//!
123//! [`ValidatorsDiff`]  
124//! [`ValidatorField`]  
125//! [`ValidatorSnapshot`]  
126//! [`ValidatorMut`]  
127//! [`ValidatorMutTarget`]  
128//! [`VALIDATOR_SSZ_SIZE`]  
129//! [`MIN_VALIDATOR_WITHDRAWABILITY_DELAY`]  
130
131use crate::{
132    error::Error,
133    types::{
134        ArchivedValidatorField, ArchivedValidatorsDiff, ValidatorField, ValidatorPatch,
135        ValidatorsDiff, MIN_VALIDATOR_WITHDRAWABILITY_DELAY, VALIDATOR_SSZ_SIZE,
136    },
137};
138
139/// Read-only view of a validator used by the delta encoder.
140///
141/// Implementations provide access to the validator fields that can participate
142/// in a delta. The trait deliberately does not require a concrete validator
143/// type, allowing the same diff algorithm to operate on both flat SSZ records
144/// and native consensus-client data structures.
145///
146/// [`to_ssz_bytes`](Self::to_ssz_bytes) is only required when a validator is
147/// present in the target registry but not in the base registry.
148pub trait ValidatorSnapshot {
149    fn withdrawal_credentials(&self) -> &[u8; 32];
150    fn effective_balance(&self) -> u64;
151    fn is_slashed(&self) -> bool;
152    fn activation_eligibility_epoch(&self) -> u64;
153    fn activation_epoch(&self) -> u64;
154    fn exit_epoch(&self) -> u64;
155    fn withdrawable_epoch(&self) -> u64;
156
157    /// Serializes the validator to its consensus SSZ representation.
158    /// Only required for validators appended to the target registry.
159    fn to_ssz_bytes(&self) -> Vec<u8>;
160}
161
162/// Mutable view of a validator used during delta reconstruction.
163///
164/// Implementations expose the fields that may be modified by a
165/// [`ValidatorField`] patch.
166///
167/// The current slashed status is required so that applying an `exit_epoch`
168/// patch can deterministically reconstruct `withdrawable_epoch` for
169/// non-slashed validators.
170pub trait ValidatorMut {
171    /// Returns the current slashed status. Required for deterministic
172    /// reconstruction of `withdrawable_epoch` when `exit_epoch` changes.
173    fn is_slashed(&self) -> bool;
174
175    fn set_withdrawal_credentials(&mut self, value: &[u8; 32]);
176    fn set_effective_balance(&mut self, value: u64);
177    fn set_slashed(&mut self, value: bool);
178    fn set_activation_eligibility_epoch(&mut self, value: u64);
179    fn set_activation_epoch(&mut self, value: u64);
180    fn set_exit_epoch(&mut self, value: u64);
181    fn set_withdrawable_epoch(&mut self, value: u64);
182}
183
184/// Mutable access to a validator collection used during reconstruction.
185///
186/// This trait abstracts over the client's validator storage representation.
187/// Implementations may back the collection with a contiguous vector, a
188/// persistent list, a tree, or another native data structure.
189///
190/// The collection is expected to preserve validator ordering: validator index
191/// `i` in the delta must refer to the same validator index `i` in the target
192/// collection.
193///
194/// New validators are supplied as complete consensus SSZ records through
195/// [`push_from_ssz`](Self::push_from_ssz).
196pub trait ValidatorMutTarget {
197    type Validator<'a>: ValidatorMut
198    where
199        Self: 'a;
200
201    /// Fetches a mutable validator view by index.
202    fn get_mut(&mut self, index: usize) -> Option<Self::Validator<'_>>;
203
204    /// Appends a newly seen validator from its raw SSZ bytes.
205    fn push_from_ssz(&mut self, ssz_bytes: &[u8]);
206}
207
208/// Computes a compact validator delta from two contiguous SSZ byte buffers.
209///
210/// Both buffers must contain zero or more complete validator records, with
211/// each record occupying exactly [`VALIDATOR_SSZ_SIZE`] bytes.
212///
213/// The resulting [`ValidatorsDiff`] contains:
214///
215/// - field-level patches for validators present in both buffers; and
216/// - complete SSZ records for validators appended to the target buffer.
217///
218/// The function does not allocate or deserialize individual validator
219/// structures while scanning the common portion of the buffers. Fields are
220/// read directly from their fixed offsets in the SSZ representation.
221///
222/// # Examples
223///
224/// A delta can be applied to the original buffer to reconstruct the target:
225///
226/// ```ignore
227/// let delta = diff_validators(&base, &target);
228/// apply_validators(&mut base, &delta);
229///
230/// assert_eq!(base, target);
231/// ```
232///
233/// [`VALIDATOR_SSZ_SIZE`]: crate::types::VALIDATOR_SSZ_SIZE
234pub fn diff_validators(base_bytes: &[u8], target_bytes: &[u8]) -> ValidatorsDiff {
235    diff_validators_impl(
236        base_bytes
237            .chunks_exact(VALIDATOR_SSZ_SIZE)
238            .map(ByteValidator::new),
239        target_bytes
240            .chunks_exact(VALIDATOR_SSZ_SIZE)
241            .map(ByteValidator::new),
242    )
243}
244
245/// Computes a compact validator delta using client-provided validator views.
246///
247/// This is the native-storage counterpart to [`diff_validators`]. Instead of
248/// requiring the validator registries to be represented as contiguous SSZ
249/// buffers, the caller supplies iterators of [`ValidatorSnapshot`] values.
250///
251/// Only the fields exposed by [`ValidatorSnapshot`] are inspected. Validators
252/// that occur only in the target iterator are serialized through
253/// [`ValidatorSnapshot::to_ssz_bytes`] and stored in the resulting delta.
254///
255/// The iterators must report their exact validator counts through
256/// [`ExactSizeIterator`].
257///
258/// This function is useful when a consensus client stores validators in a
259/// persistent list, tree, or another non-contiguous data structure and wants
260/// to avoid materializing the complete registry as SSZ bytes.
261///
262/// [`diff_validators`]
263/// [`ValidatorSnapshot`]
264pub fn diff_validators_iter<I1, I2, V1, V2>(base: I1, target: I2) -> ValidatorsDiff
265where
266    I1: ExactSizeIterator<Item = V1>,
267    I2: ExactSizeIterator<Item = V2>,
268    V1: ValidatorSnapshot,
269    V2: ValidatorSnapshot,
270{
271    diff_validators_impl(base, target)
272}
273
274/// The core diffing algorithm, agnostic to the input memory layout.
275fn diff_validators_impl<I1, I2, V1, V2>(mut base: I1, mut target: I2) -> ValidatorsDiff
276where
277    I1: ExactSizeIterator<Item = V1>,
278    I2: ExactSizeIterator<Item = V2>,
279    V1: ValidatorSnapshot,
280    V2: ValidatorSnapshot,
281{
282    let mut patches = Vec::with_capacity(512);
283    let mut appended_validators = Vec::new();
284
285    for (i, (b, t)) in base.by_ref().zip(target.by_ref()).enumerate() {
286        let wc = t.withdrawal_credentials();
287        let eb = t.effective_balance();
288        let slashed = t.is_slashed();
289        let aee = t.activation_eligibility_epoch();
290        let ae = t.activation_epoch();
291        let ee = t.exit_epoch();
292
293        if b.withdrawal_credentials() == wc
294            && b.effective_balance() == eb
295            && b.is_slashed() == slashed
296            && b.activation_eligibility_epoch() == aee
297            && b.activation_epoch() == ae
298            && b.exit_epoch() == ee
299            && (!slashed || b.withdrawable_epoch() == t.withdrawable_epoch())
300        {
301            continue;
302        }
303
304        let index = u32::try_from(i).expect("validator index exceeds u32 range");
305
306        if b.withdrawal_credentials() != wc {
307            patches.push(ValidatorPatch {
308                index,
309                field: ValidatorField::WithdrawalCredentials,
310                value: wc.to_vec(),
311            });
312        }
313        if b.effective_balance() != eb {
314            patches.push(ValidatorPatch {
315                index,
316                field: ValidatorField::EffectiveBalance,
317                value: eb.to_le_bytes().to_vec(),
318            });
319        }
320        if b.is_slashed() != slashed {
321            patches.push(ValidatorPatch {
322                index,
323                field: ValidatorField::Slashed,
324                value: vec![slashed as u8],
325            });
326        }
327        if b.activation_eligibility_epoch() != aee {
328            patches.push(ValidatorPatch {
329                index,
330                field: ValidatorField::ActivationEligibilityEpoch,
331                value: aee.to_le_bytes().to_vec(),
332            });
333        }
334        if b.activation_epoch() != ae {
335            patches.push(ValidatorPatch {
336                index,
337                field: ValidatorField::ActivationEpoch,
338                value: ae.to_le_bytes().to_vec(),
339            });
340        }
341        if b.exit_epoch() != ee {
342            patches.push(ValidatorPatch {
343                index,
344                field: ValidatorField::ExitEpoch,
345                value: ee.to_le_bytes().to_vec(),
346            });
347        }
348        if slashed && b.withdrawable_epoch() != t.withdrawable_epoch() {
349            patches.push(ValidatorPatch {
350                index,
351                field: ValidatorField::WithdrawableEpochSlashed,
352                value: t.withdrawable_epoch().to_le_bytes().to_vec(),
353            });
354        }
355    }
356
357    for t_val in target {
358        appended_validators.extend(t_val.to_ssz_bytes());
359    }
360
361    ValidatorsDiff {
362        patches,
363        appended_validators,
364    }
365}
366
367/// Applies a validator delta to a contiguous SSZ byte buffer in place.
368///
369/// The buffer must represent the same base validator registry against which
370/// the delta was generated.
371///
372/// Existing validators are modified according to the delta's field patches.
373/// Newly appended validators are added from their raw SSZ representations.
374///
375/// This function does not require deserializing the validator registry into
376/// Rust validator structures. Existing records are modified directly at their
377/// fixed SSZ offsets.
378///
379/// # Errors
380///
381/// Returns [`Error::MalformedDelta`] if a patch contains a value with an
382/// invalid width for its field.
383///
384/// Returns [`Error::InvalidDelta`] if a patch refers to a validator index
385/// that does not exist in the buffer.
386///
387/// # Correctness
388///
389/// The delta is expected to have been generated from the same base registry.
390/// This function does not verify that the current contents of the buffer
391/// correspond to the original base state.
392///
393/// [`ArchivedValidatorsDiff`]: crate::types::ArchivedValidatorsDiff
394/// [`Error`]: crate::Error
395pub fn apply_validators(base: &mut Vec<u8>, delta: &ArchivedValidatorsDiff) -> Result<(), Error> {
396    apply_validators_iter(&mut ByteValidatorTarget(base), delta)
397}
398
399/// Applies a validator delta directly to a client's native validator
400/// collection.
401///
402/// The target collection is accessed through [`ValidatorMutTarget`], allowing
403/// the caller to mutate validators without converting the entire registry
404/// into a contiguous SSZ byte buffer.
405///
406/// For every patch, the corresponding validator is obtained with
407/// [`ValidatorMutTarget::get_mut`] and the specified field is updated.
408///
409/// Newly appended validators are passed to
410/// [`ValidatorMutTarget::push_from_ssz`] as complete
411/// [`VALIDATOR_SSZ_SIZE`]-byte SSZ records.
412///
413/// For a non-slashed validator whose `exit_epoch` is patched, the
414/// `withdrawable_epoch` is reconstructed automatically as:
415///
416/// ```text
417/// exit_epoch + MIN_VALIDATOR_WITHDRAWABILITY_DELAY
418/// ```
419///
420/// For slashed validators, `withdrawable_epoch` is restored explicitly from
421/// the [`ValidatorField::WithdrawableEpochSlashed`] patch.
422///
423/// # Errors
424///
425/// Returns [`Error::MalformedDelta`] if a patch contains a value with an
426/// invalid width for its field.
427///
428/// Returns [`Error::InvalidDelta`] if a patch refers to a validator index
429/// that does not exist in the target collection.
430///
431/// [`ValidatorMutTarget`]: crate::validators::ValidatorMutTarget
432/// [`ValidatorField::WithdrawableEpochSlashed`]:
433///     crate::types::ValidatorField::WithdrawableEpochSlashed
434/// [`VALIDATOR_SSZ_SIZE`]: crate::types::VALIDATOR_SSZ_SIZE
435/// [`MIN_VALIDATOR_WITHDRAWABILITY_DELAY`]:
436///     crate::types::MIN_VALIDATOR_WITHDRAWABILITY_DELAY
437/// [`Error`]: crate::Error
438pub fn apply_validators_iter<T: ValidatorMutTarget>(
439    target: &mut T,
440    delta: &ArchivedValidatorsDiff,
441) -> Result<(), Error> {
442    for patch in delta.patches.iter() {
443        let idx = patch.index.to_native() as usize;
444        let val_bytes = patch.value.as_slice();
445
446        let mut validator = target.get_mut(idx).ok_or_else(|| {
447            Error::InvalidDelta(format!("validator patch index {idx} is out of bounds"))
448        })?;
449
450        match &patch.field {
451            ArchivedValidatorField::WithdrawalCredentials => {
452                let bytes: [u8; 32] = val_bytes.try_into().map_err(|_| {
453                    Error::MalformedDelta(format!(
454                        "withdrawal credentials patch has invalid width: \
455                         expected 32 bytes, got {}",
456                        val_bytes.len()
457                    ))
458                })?;
459
460                validator.set_withdrawal_credentials(&bytes);
461            }
462            ArchivedValidatorField::EffectiveBalance => {
463                let bytes: [u8; 8] = val_bytes.try_into().map_err(|_| {
464                    Error::MalformedDelta(format!(
465                        "effective balance patch has invalid width: \
466                         expected 8 bytes, got {}",
467                        val_bytes.len()
468                    ))
469                })?;
470
471                let eb = u64::from_le_bytes(bytes);
472                validator.set_effective_balance(eb);
473            }
474            ArchivedValidatorField::Slashed => {
475                let [value] = <[u8; 1]>::try_from(val_bytes).map_err(|_| {
476                    Error::MalformedDelta(format!(
477                        "slashed patch has invalid width: expected 1 byte, got {}",
478                        val_bytes.len()
479                    ))
480                })?;
481
482                validator.set_slashed(value != 0);
483            }
484            ArchivedValidatorField::ActivationEligibilityEpoch => {
485                let bytes: [u8; 8] = val_bytes.try_into().map_err(|_| {
486                    Error::MalformedDelta(format!(
487                        "activation eligibility epoch patch has invalid width: \
488                         expected 8 bytes, got {}",
489                        val_bytes.len()
490                    ))
491                })?;
492
493                let epoch = u64::from_le_bytes(bytes);
494                validator.set_activation_eligibility_epoch(epoch);
495            }
496            ArchivedValidatorField::ActivationEpoch => {
497                let bytes: [u8; 8] = val_bytes.try_into().map_err(|_| {
498                    Error::MalformedDelta(format!(
499                        "activation epoch patch has invalid width: \
500                         expected 8 bytes, got {}",
501                        val_bytes.len()
502                    ))
503                })?;
504
505                let epoch = u64::from_le_bytes(bytes);
506                validator.set_activation_epoch(epoch);
507            }
508            ArchivedValidatorField::ExitEpoch => {
509                let bytes: [u8; 8] = val_bytes.try_into().map_err(|_| {
510                    Error::MalformedDelta(format!(
511                        "exit epoch patch has invalid width: \
512                         expected 8 bytes, got {}",
513                        val_bytes.len()
514                    ))
515                })?;
516
517                let ee = u64::from_le_bytes(bytes);
518                validator.set_exit_epoch(ee);
519
520                // Deterministic reconstruction for non-slashed validators.
521                if !validator.is_slashed() {
522                    let we = ee.saturating_add(MIN_VALIDATOR_WITHDRAWABILITY_DELAY);
523                    validator.set_withdrawable_epoch(we);
524                }
525            }
526            ArchivedValidatorField::WithdrawableEpochSlashed => {
527                let bytes: [u8; 8] = val_bytes.try_into().map_err(|_| {
528                    Error::MalformedDelta(format!(
529                        "withdrawable epoch patch has invalid width: \
530                         expected 8 bytes, got {}",
531                        val_bytes.len()
532                    ))
533                })?;
534
535                let we = u64::from_le_bytes(bytes);
536                validator.set_withdrawable_epoch(we);
537            }
538        }
539    }
540
541    if !delta
542        .appended_validators
543        .len()
544        .is_multiple_of(VALIDATOR_SSZ_SIZE)
545    {
546        return Err(Error::MalformedDelta(
547            "appended validator data does not contain complete SSZ records".into(),
548        ));
549    }
550
551    for chunk in delta
552        .appended_validators
553        .as_slice()
554        .chunks_exact(VALIDATOR_SSZ_SIZE)
555    {
556        target.push_from_ssz(chunk);
557    }
558
559    Ok(())
560}
561
562/// A zero-cost wrapper that allows byte slices to implement
563/// [`ValidatorSnapshot`].
564///
565/// The wrapped slice must contain exactly one complete validator SSZ record.
566struct ByteValidator<'a>(&'a [u8]);
567
568impl<'a> ByteValidator<'a> {
569    fn new(bytes: &'a [u8]) -> Self {
570        debug_assert_eq!(
571            bytes.len(),
572            VALIDATOR_SSZ_SIZE,
573            "ByteValidator must contain exactly one complete SSZ validator record",
574        );
575
576        Self(bytes)
577    }
578
579    #[inline]
580    fn bytes<const N: usize>(&self, start: usize) -> [u8; N] {
581        self.0
582            .get(start..start + N)
583            .and_then(|bytes| bytes.try_into().ok())
584            .expect("ByteValidator contains a complete SSZ validator record")
585    }
586}
587
588impl<'a> ValidatorSnapshot for ByteValidator<'a> {
589    #[inline]
590    fn withdrawal_credentials(&self) -> &[u8; 32] {
591        self.0
592            .get(48..80)
593            .and_then(|bytes| bytes.try_into().ok())
594            .expect("ByteValidator contains a complete SSZ validator record")
595    }
596
597    #[inline]
598    fn effective_balance(&self) -> u64 {
599        u64::from_le_bytes(self.bytes::<8>(80))
600    }
601
602    #[inline]
603    fn is_slashed(&self) -> bool {
604        self.bytes::<1>(88)[0] != 0
605    }
606
607    #[inline]
608    fn activation_eligibility_epoch(&self) -> u64 {
609        u64::from_le_bytes(self.bytes::<8>(89))
610    }
611
612    #[inline]
613    fn activation_epoch(&self) -> u64 {
614        u64::from_le_bytes(self.bytes::<8>(97))
615    }
616
617    #[inline]
618    fn exit_epoch(&self) -> u64 {
619        u64::from_le_bytes(self.bytes::<8>(105))
620    }
621
622    #[inline]
623    fn withdrawable_epoch(&self) -> u64 {
624        u64::from_le_bytes(self.bytes::<8>(113))
625    }
626
627    #[inline]
628    fn to_ssz_bytes(&self) -> Vec<u8> {
629        self.0.to_vec()
630    }
631}
632
633/// Mutable byte slice wrapper implementing `ValidatorMut`.
634struct ByteValidatorMut<'a>(&'a mut [u8]);
635
636impl<'a> ValidatorMut for ByteValidatorMut<'a> {
637    #[inline]
638    fn is_slashed(&self) -> bool {
639        *self
640            .0
641            .get(88)
642            .expect("ByteValidatorMut contains a complete SSZ validator record")
643            != 0
644    }
645
646    #[inline]
647    fn set_withdrawal_credentials(&mut self, v: &[u8; 32]) {
648        self.0
649            .get_mut(48..80)
650            .expect("ByteValidatorMut contains a complete SSZ validator record")
651            .copy_from_slice(v);
652    }
653
654    #[inline]
655    fn set_effective_balance(&mut self, v: u64) {
656        self.0
657            .get_mut(80..88)
658            .expect("ByteValidatorMut contains a complete SSZ validator record")
659            .copy_from_slice(&v.to_le_bytes());
660    }
661
662    #[inline]
663    fn set_slashed(&mut self, v: bool) {
664        *self
665            .0
666            .get_mut(88)
667            .expect("ByteValidatorMut contains a complete SSZ validator record") = v as u8;
668    }
669
670    #[inline]
671    fn set_activation_eligibility_epoch(&mut self, v: u64) {
672        self.0
673            .get_mut(89..97)
674            .expect("ByteValidatorMut contains a complete SSZ validator record")
675            .copy_from_slice(&v.to_le_bytes());
676    }
677
678    #[inline]
679    fn set_activation_epoch(&mut self, v: u64) {
680        self.0
681            .get_mut(97..105)
682            .expect("ByteValidatorMut contains a complete SSZ validator record")
683            .copy_from_slice(&v.to_le_bytes());
684    }
685
686    #[inline]
687    fn set_exit_epoch(&mut self, v: u64) {
688        self.0
689            .get_mut(105..113)
690            .expect("ByteValidatorMut contains a complete SSZ validator record")
691            .copy_from_slice(&v.to_le_bytes());
692    }
693
694    #[inline]
695    fn set_withdrawable_epoch(&mut self, v: u64) {
696        self.0
697            .get_mut(113..121)
698            .expect("ByteValidatorMut contains a complete SSZ validator record")
699            .copy_from_slice(&v.to_le_bytes());
700    }
701}
702
703/// Collection wrapper implementing `ValidatorMutTarget` for `Vec<u8>`.
704struct ByteValidatorTarget<'a>(&'a mut Vec<u8>);
705
706impl<'a> ValidatorMutTarget for ByteValidatorTarget<'a> {
707    type Validator<'b>
708        = ByteValidatorMut<'b>
709    where
710        Self: 'b;
711
712    fn get_mut(&mut self, index: usize) -> Option<Self::Validator<'_>> {
713        let start = index.checked_mul(VALIDATOR_SSZ_SIZE)?;
714        let end = start.checked_add(VALIDATOR_SSZ_SIZE)?;
715        self.0.get_mut(start..end).map(ByteValidatorMut)
716    }
717
718    fn push_from_ssz(&mut self, ssz_bytes: &[u8]) {
719        self.0.extend_from_slice(ssz_bytes);
720    }
721}