Skip to main content

eth_state_diff/
balances.rs

1//! Compact balance-state delta encoding.
2//!
3//! This module computes compact binary deltas between two validator balance
4//! snapshots.
5//!
6//! The encoding is optimized for Ethereum beacon chain balance updates where
7//! the overwhelming majority of balances either:
8//!
9//! - remain unchanged,
10//! - change by a small amount,
11//! - become zero, or
12//! - are newly initialized.
13//!
14//! The resulting delta is designed for:
15//!
16//! - efficient serialization with `rkyv`,
17//! - high compression with `zstd`,
18//! - zero-copy decoding, and
19//! - fast in-place reconstruction.
20
21use rustc_hash::FxHashMap;
22
23use crate::types::{
24    ArchivedBalancesDiff, BalancesDiff, BitTagVec, SET_NO_CHANGE, SET_TO_DIFF, SET_TO_TARGET_VALUE,
25    SET_TO_ZERO,
26};
27
28#[inline]
29fn zigzag_encode(n: i64) -> u64 {
30    ((n << 1) ^ (n >> 63)) as u64
31}
32
33pub(super) fn write_varint(mut val: u64, buf: &mut Vec<u8>) {
34    loop {
35        if val < 0x80 {
36            buf.push(val as u8);
37            break;
38        }
39        buf.push((val as u8) | 0x80);
40        val >>= 7;
41    }
42}
43
44/// Computes a compact delta between two balance snapshots.
45///
46/// The returned delta contains sufficient information to reconstruct
47/// `target` from `base`.
48///
49/// # Complexity
50///
51/// O(n)
52///
53/// # Panics
54///
55/// Never.
56///
57/// # Notes
58///
59/// The most common balance difference is stored separately as a mode and all
60/// encoded differences are stored relative to this mode, improving varint
61/// compression.
62pub fn diff_balances(base: &[u64], target: &[u64]) -> BalancesDiff {
63    let common_len = base.len().min(target.len());
64
65    let mut freq_map = FxHashMap::default();
66    freq_map.reserve(1024);
67
68    for i in 0..common_len {
69        let v1 = base[i];
70        let v2 = target[i];
71        if v1 != v2 {
72            let diff = v2 as i64 - v1 as i64;
73            *freq_map.entry(diff).or_insert(0usize) += 1;
74        }
75    }
76
77    let mode = freq_map
78        .into_iter()
79        .max_by_key(|&(_, count)| count)
80        .map(|(val, _)| val)
81        .unwrap_or(0);
82
83    let mut tags = BitTagVec::new(common_len);
84
85    let mut varint_payload = Vec::with_capacity(common_len / 2);
86    let mut target_values = Vec::new();
87
88    for i in 0..common_len {
89        let v1 = base[i];
90        let v2 = target[i];
91
92        if v1 == v2 {
93            continue; // SET_NO_CHANGE is implicitly 0
94        }
95
96        if v2 == 0 {
97            tags.set(i, SET_TO_ZERO);
98        } else if v1 == 0 {
99            tags.set(i, SET_TO_TARGET_VALUE);
100            target_values.push(v2);
101        } else {
102            let diff = v2 as i64 - v1 as i64;
103
104            if let Ok(_diff_i32) = i32::try_from(diff) {
105                tags.set(i, SET_TO_DIFF);
106                let corrected = diff - mode;
107                let encoded = zigzag_encode(corrected);
108                write_varint(encoded, &mut varint_payload);
109            } else {
110                tags.set(i, SET_TO_TARGET_VALUE);
111                target_values.push(v2);
112            }
113        }
114    }
115
116    let appended_balances = if target.len() > base.len() {
117        target[base.len()..].to_vec()
118    } else {
119        Vec::new()
120    };
121
122    BalancesDiff {
123        tags,
124        mode,
125        varint_payload,
126        target_values,
127        appended_balances,
128    }
129}
130
131#[inline]
132fn zigzag_decode(n: u64) -> i64 {
133    ((n >> 1) as i64) ^ -((n & 1) as i64)
134}
135
136#[inline]
137pub(super) fn read_varint(buf: &[u8], cursor: &mut usize) -> u64 {
138    let mut val = 0u64;
139    let mut shift = 0u32;
140
141    loop {
142        // SAFETY: The diff logic guarantees the payload is perfectly valid
143        // and we will not read past the end of the buffer.
144        let byte = buf[*cursor];
145        *cursor += 1;
146
147        val |= ((byte & 0x7F) as u64) << shift;
148
149        // If the high bit is 0, this is the last byte of the varint
150        if (byte & 0x80) == 0 {
151            break;
152        }
153        shift += 7;
154    }
155
156    val
157}
158
159/// Applies a balance delta in-place.
160///
161/// After successful execution, `base` is transformed into the original target
162/// snapshot used to produce `delta`.
163///
164/// This function performs no heap allocations except when new validators are
165/// appended.
166///
167/// # Complexity
168///
169/// O(n)
170pub fn apply_balances(base: &mut Vec<u64>, delta: &ArchivedBalancesDiff) {
171    let mode = delta.mode.to_native();
172
173    let tag_len = delta.tags.len.to_native() as usize;
174
175    debug_assert_eq!(
176        base.len(),
177        tag_len,
178        "Base balance length does not match delta tag length"
179    );
180
181    let mut target_iter = delta.target_values.iter();
182    let payload = delta.varint_payload.as_slice();
183    let mut payload_cursor = 0usize;
184
185    let mut base_idx = 0usize;
186
187    for &tag_byte in delta.tags.data.iter() {
188        if base_idx >= tag_len {
189            break;
190        }
191
192        // Fast path: four consecutive SET_NO_CHANGE entries.
193        if tag_byte == 0 {
194            base_idx = (base_idx + 4).min(tag_len);
195            continue;
196        }
197
198        for bit in 0..4 {
199            if base_idx >= tag_len {
200                break;
201            }
202
203            let tag = (tag_byte >> (bit * 2)) & 0b11;
204
205            match tag {
206                SET_NO_CHANGE => {}
207                SET_TO_ZERO => {
208                    base[base_idx] = 0;
209                }
210                SET_TO_TARGET_VALUE => {
211                    base[base_idx] = target_iter.next().unwrap().to_native();
212                }
213                SET_TO_DIFF => {
214                    let encoded = read_varint(payload, &mut payload_cursor);
215                    let corrected = zigzag_decode(encoded);
216                    let diff = corrected + mode;
217                    base[base_idx] = (base[base_idx] as i64 + diff) as u64;
218                }
219                _ => unreachable!("Invalid 2-bit tag state encountered during apply"),
220            }
221
222            base_idx += 1;
223        }
224    }
225
226    if !delta.appended_balances.is_empty() {
227        base.extend(delta.appended_balances.iter().map(|v| v.to_native()));
228    }
229}