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};
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)
35///
36/// # Panics
37///
38/// Panics if `target_slot < base_slot`.
39pub fn diff_slashings(
40    base_slot: u64,
41    target_slot: u64,
42    base_buffer: &[u64],
43    target_buffer: &[u64],
44    slots_per_epoch: u64,
45) -> SlashingsDiff {
46    let base_epoch = base_slot / slots_per_epoch;
47    let target_epoch = target_slot / slots_per_epoch;
48    let modulus = base_buffer.len() as u64;
49
50    let mut updates = Vec::new();
51
52    // Iterate through the epochs that passed in this window.
53    // For a 32-slot window, this loop runs exactly ONCE.
54    let mut current_epoch = base_epoch;
55    while current_epoch < target_epoch {
56        current_epoch += 1;
57        let idx = (current_epoch % modulus) as u16;
58
59        let base_val = base_buffer[idx as usize];
60        let target_val = target_buffer[idx as usize];
61
62        if base_val != target_val {
63            updates.push((idx, target_val));
64        }
65    }
66
67    SlashingsDiff { updates }
68}
69
70/// Applies a slashing delta to a circular slashing buffer.
71///
72/// Each recorded update replaces the value at its corresponding ring-buffer
73/// index. Entries not present in the delta remain unchanged.
74///
75/// After successful application, the destination buffer contains the same
76/// slashing values as the target buffer used to produce `delta`.
77///
78/// # Complexity
79///
80/// O(number of recorded updates)
81pub fn apply_slashings(base_buffer: &mut [u64], delta: &ArchivedSlashingsDiff) {
82    for update in delta.updates.iter() {
83        let idx = update.0.to_native() as usize;
84        let val = update.1.to_native();
85
86        base_buffer[idx] = val;
87    }
88}