Skip to main content

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}