eth_state_diff/inactivity_scores.rs
1//! Delta encoding for validator inactivity scores.
2//!
3//! Inactivity scores are encoded as sparse updates. Only indices whose values
4//! differ from the base vector are stored together with their replacement
5//! values.
6//!
7//! When the target vector consists entirely of zero-valued scores, a dedicated
8//! all-zero representation is emitted, avoiding any per-validator storage.
9//!
10//! Deltas produced by [`diff_inactivity`] can be applied in-place using
11//! [`apply_inactivity`].
12
13use crate::types::{ArchivedInactivityDiff, InactivityDiff};
14
15/// Computes a compact inactivity-score delta.
16///
17/// The returned delta contains sufficient information to reconstruct `target`
18/// from `base`.
19///
20/// If every inactivity score in the target vector is zero, the encoder emits an
21/// [`InactivityDiff::AllZeros`] representation. Otherwise, only modified
22/// indices and their replacement values are stored.
23///
24/// Newly appended validator scores are included as an extension.
25///
26/// # Arguments
27///
28/// * `base` - Base inactivity score vector.
29/// * `target` - Target inactivity score vector.
30///
31/// # Complexity
32///
33/// O(n)
34pub fn diff_inactivity(base: &[u64], target: &[u64]) -> InactivityDiff {
35 let target_is_zero = target.iter().all(|&v| v == 0);
36
37 if target_is_zero {
38 let base_has_non_zero = base.iter().any(|&v| v != 0);
39
40 if base_has_non_zero {
41 return InactivityDiff::AllZeros(target.len() as u32);
42 }
43 }
44
45 let common_len = base.len().min(target.len());
46 let mut indices = Vec::with_capacity(100);
47 let mut new_values = Vec::with_capacity(100);
48
49 for (i, (&v1, &v2)) in base.iter().zip(target.iter()).take(common_len).enumerate() {
50 if v1 != v2 {
51 indices.push(i as u32);
52 new_values.push(v2);
53 }
54 }
55
56 let extensions = target[common_len..].to_vec();
57
58 InactivityDiff::Sparse {
59 indices,
60 new_values,
61 extensions,
62 }
63}
64
65/// Applies an inactivity-score delta in-place.
66///
67/// After successful execution, `base` is identical to the target inactivity
68/// score vector used to produce `delta`.
69///
70/// Newly appended inactivity scores are added to the end of the destination
71/// vector.
72///
73/// # Complexity
74///
75/// O(number of recorded updates + appended scores)
76pub fn apply_inactivity(base: &mut Vec<u64>, delta: &ArchivedInactivityDiff) {
77 match delta {
78 ArchivedInactivityDiff::AllZeros(len) => {
79 base.clear();
80 base.resize(len.to_native() as usize, 0);
81 }
82 ArchivedInactivityDiff::Sparse {
83 indices,
84 new_values,
85 extensions,
86 } => {
87 for (idx, val) in indices.iter().zip(new_values.iter()) {
88 let i = (*idx).to_native() as usize;
89 base[i] = val.to_native();
90 }
91 base.reserve(extensions.len());
92 base.extend(extensions.iter().map(|v| v.to_native()));
93 }
94 }
95}