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}