Skip to main content

eth_state_diff/
slashings.rs

1//! Delta encoding for Ethereum slashing vectors.
2//!
3//! Ethereum consensus stores slashing totals in a fixed-capacity circular
4//! buffer indexed by epoch.
5//!
6//! Unlike root and RANDAO buffers, slashing values change infrequently. This
7//! module therefore records only epochs whose values differ between two states,
8//! producing a sparse delta representation.
9//!
10//! Applying a delta updates only the modified epochs while leaving all other
11//! entries unchanged.
12
13use crate::types::{ArchivedSlashingsDiff, SlashingsDiff, SLOTS_PER_EPOCH};
14
15/// Computes a sparse delta between two slashing buffers.
16///
17/// The encoder compares the epochs traversed while advancing from
18/// `base_slot` to `target_slot` and records only entries whose values have
19/// changed.
20///
21/// The returned delta contains pairs of `(ring_index, value)` representing the
22/// updated slashing totals.
23///
24/// # Arguments
25///
26/// * `base_slot` - Starting slot.
27/// * `target_slot` - Ending slot.
28/// * `base_buffer` - Base slashing ring buffer.
29/// * `target_buffer` - Target slashing ring buffer.
30/// * `slots_per_epoch` - Number of slots in a consensus epoch.
31///
32/// # Complexity
33///
34/// O(number of traversed epochs)
35pub fn diff_slashings(
36    base_slot: u64,
37    target_slot: u64,
38    base_buffer: &[u64],
39    target_buffer: &[u64],
40) -> SlashingsDiff {
41    let base_epoch = base_slot / SLOTS_PER_EPOCH;
42    let target_epoch = target_slot / SLOTS_PER_EPOCH;
43    let modulus = base_buffer.len() as u64;
44
45    let mut updates = Vec::new();
46
47    // Iterate through the epochs that passed in this window.
48    // For a 32-slot window, this loop runs exactly ONCE.
49    let mut current_epoch = base_epoch;
50    while current_epoch < target_epoch {
51        current_epoch += 1;
52        let idx = (current_epoch % modulus) as u16;
53
54        let base_val = base_buffer[idx as usize];
55        let target_val = target_buffer[idx as usize];
56
57        if base_val != target_val {
58            updates.push((idx, target_val));
59        }
60    }
61
62    SlashingsDiff { updates }
63}
64
65/// Applies a slashing delta to a circular slashing buffer.
66///
67/// Each recorded update replaces the value at its corresponding ring-buffer
68/// index. Entries not present in the delta remain unchanged.
69///
70/// After successful application, the destination buffer contains the same
71/// slashing values as the target buffer used to produce `delta`.
72///
73/// # Complexity
74///
75/// O(number of recorded updates)
76pub fn apply_slashings(base_buffer: &mut [u64], delta: &ArchivedSlashingsDiff) {
77    for update in delta.updates.iter() {
78        let idx = update.0.to_native() as usize;
79        let val = update.1.to_native();
80
81        base_buffer[idx] = val;
82    }
83}