Skip to main content

eth_state_diff/
types.rs

1use rkyv::{Archive, Deserialize, Serialize};
2
3pub const SLOTS_PER_EPOCH: u64 = 32;
4
5/// Size, in bytes, of an SSZ-serialized validator record.
6pub const VALIDATOR_SSZ_SIZE: usize = 121;
7
8/// Size, in bytes, of an SSZ-serialized historical root.
9pub const HISTORICAL_ROOTS_SSZ_SIZE: usize = 32;
10
11/// Size, in bytes, of an SSZ-serialized historical summary.
12pub const HISTORICAL_SUMMARIES_SSZ_SIZE: usize = 64;
13
14/// Protocol-defined withdrawability delay for non-slashed validators.
15pub const MIN_VALIDATOR_WITHDRAWABILITY_DELAY: u64 = 256;
16
17/// A field-level modification to a validator record.
18///
19/// Validator fields are diffed independently to avoid rewriting entire
20/// 121-byte SSZ validator records when only a small subset of fields change.
21#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
22pub enum ValidatorField {
23    WithdrawalCredentials,
24    EffectiveBalance,
25    Slashed,
26    ActivationEligibilityEpoch,
27    ActivationEpoch,
28    ExitEpoch,
29    WithdrawableEpochSlashed,
30}
31
32/// A modification to a single validator field.
33///
34/// Each patch identifies the validator index, the field being modified,
35/// and the replacement SSZ bytes for that field.
36#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
37pub struct ValidatorPatch {
38    pub index: u32,
39    pub field: ValidatorField,
40    pub value: Vec<u8>,
41}
42
43/// Compact representation of the difference between two validator registries.
44///
45/// Only modified validator fields are recorded. Newly appended validators are
46/// stored as raw SSZ bytes and appended during reconstruction.
47#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
48pub struct ValidatorsDiff {
49    pub patches: Vec<ValidatorPatch>,
50    pub appended_validators: Vec<u8>,
51}
52
53/// Compact representation of the difference between two balance snapshots.
54///
55/// A `BalanceDiffs` stores only the information required to transform one
56/// balance vector into another.
57///
58/// Small balance differences are encoded as mode-adjusted zig-zag varints,
59/// while values that can not be represented efficiently are stored explicitly.
60///
61/// This structure is intended to be serialized with `rkyv`.
62#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
63pub struct BalancesDiff {
64    pub tags: BitTagVec,
65
66    pub mode: i64,
67
68    pub varint_payload: Vec<u8>,
69    pub target_values: Vec<u64>,
70    pub appended_balances: Vec<u64>,
71}
72
73/// Compact delta encoding for participation flags.
74///
75/// A participation delta stores only the information required to transform one
76/// participation vector into another.
77///
78/// The encoder automatically selects the most compact representation:
79///
80/// - [`ParticipationDiff::AllZeros`] when the target vector contains only
81///   zero-valued flags.
82/// - [`ParticipationDiff::Sparse`] when only a subset of indices change.
83///
84/// This type is intended to be serialized using `rkyv`.
85#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
86pub enum ParticipationDiff {
87    /// Represents a participation vector consisting entirely of zeros.
88    ///
89    /// During reconstruction the destination vector is resized to `len`
90    /// and every entry is set to `0`.
91    AllZeros(usize),
92
93    /// Sparse delta representation.
94    ///
95    /// Only indices whose values differ from the base vector are stored.
96    ///
97    /// Changed indices are encoded as delta-varint gaps to reduce storage
98    /// requirements for sparsely changing participation flags.
99    Sparse {
100        /// Delta-varint encoded gaps between successive modified indices.
101        ///
102        /// Each decoded gap is added to the previous modified index to recover
103        /// the absolute index.
104        sparse_indices: Vec<u8>,
105
106        /// Replacement participation flags.
107        ///
108        /// Each entry corresponds one-to-one with a decoded index from
109        /// `sparse_indices`.
110        new_values: Vec<u8>,
111
112        /// Participation flags for validators that exist only in the target
113        /// vector.
114        extension: Vec<u8>,
115    },
116}
117
118/// Compact representation of the difference between two inactivity-score
119/// vectors.
120///
121/// The encoder automatically selects the most compact representation:
122///
123/// - [`InactivityDiff::AllZeros`] when the target vector contains only zero
124///   scores.
125/// - [`InactivityDiff::Sparse`] when only a subset of scores change.
126#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
127pub enum InactivityDiff {
128    /// Fast path for the 99.9% case where no scores change in the overlapping set.
129    AllZeros(u32),
130
131    /// Sparse inactivity-score updates.
132    ///
133    /// Only modified indices are stored.
134    Sparse {
135        /// Indices of modified inactivity scores.
136        indices: Vec<u32>,
137
138        /// Replacement scores corresponding one-to-one with `indices`.
139        new_values: Vec<u64>,
140
141        /// Scores for validators appended to the target vector.
142        extensions: Vec<u64>,
143    },
144}
145
146/// Sequence of roots written while advancing a circular root buffer.
147///
148/// Roots are stored in chronological slot order beginning at the supplied
149/// base slot used during reconstruction.
150///
151/// The buffer capacity is intentionally omitted from the encoding since it is
152/// determined by the destination buffer.
153#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
154pub struct RootsDiff {
155    /// Sequential list of 32-byte hashes added to the circular buffer
156    /// between the base slot and target slot.
157    pub roots: Vec<[u8; 32]>,
158}
159
160/// Sparse updates for a slashing ring buffer.
161///
162/// Each update stores the destination ring index together with its new slashing
163/// value.
164///
165/// Only epochs whose values changed are included.
166#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
167pub struct SlashingsDiff {
168    /// A sparse list of (index, new_slashing_amount).
169    /// Index fits in u16 (max size is 8192).
170    /// In a 32-epoch window, this Vec will have a length of 0 or 1.
171    pub updates: Vec<(u16, u64)>,
172}
173
174/// Sequence of RANDAO mixes written while advancing through epochs.
175///
176/// Mixes are stored in chronological epoch order beginning with the epoch
177/// containing the supplied base slot used during reconstruction.
178///
179/// The circular buffer capacity is intentionally omitted from the encoding, as
180/// it is determined by the destination buffer during application.
181#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
182pub struct RandaoDiff {
183    /// Sequential list of 32-byte randao reveals added during this window.
184    pub mixes: Vec<[u8; 32]>,
185}
186
187/// Delta representation of the Eth1 data vote list.
188///
189/// The encoder distinguishes between ordinary vote accumulation and the
190/// protocol-defined reset that occurs after an Eth1 voting period.
191#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
192pub enum Eth1DataVotesDiff {
193    /// Additional votes appended to the existing list.
194    Append(Vec<u8>),
195
196    /// The vote list was reset before appending new votes.
197    ResetAndAppend(Vec<u8>),
198}
199
200/// Universal delta representation for list-like state structures.
201///
202/// Handles both pure FIFO queues (consolidations, withdrawals) and
203/// queues with rare reordering mutations (deposits) safely via strict validation.
204#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
205pub enum QueueDiff {
206    /// Pure FIFO behavior validated by strict byte-matching.
207    ///
208    /// Records the number of items consumed from the front and the raw SSZ
209    /// bytes of newly appended items at the back.
210    Fifo {
211        /// The number of items consumed from the front of the base queue.
212        consumed_count: u32,
213        /// The raw SSZ bytes of *only* the newly appended items at the end.
214        appended_items: Vec<u8>,
215    },
216
217    /// Fallback for non-FIFO mutations (e.g., a deposit was postponed).
218    ///
219    /// Stores the complete serialized target list.
220    FullReplacement(Vec<u8>),
221}
222
223/// Delta representation for sync committees.
224///
225/// Sync committees only change once per sync committee period (256 epochs on
226/// Mainnet). For 32-epoch diff windows, the committee is almost always unchanged.
227/// Therefore, we use a simple unchanged/full-replacement strategy.
228#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
229pub enum SyncCommitteeDiff {
230    /// The sync committee did not change during this delta window.
231    Unchanged,
232    /// The sync committee period boundary was crossed. The entire new committee
233    /// is stored as raw SSZ bytes (24,624 bytes).
234    FullReplacement(Vec<u8>),
235}
236
237/// Delta representation for the historical summaries list.
238///
239/// Because historical summaries/roots are appended strictly once every 8,192 slots,
240/// the number of new summaries/roots between two slots is mathematically deterministic.
241/// This diff enforces that invariant rather than comparing byte lengths.
242#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
243pub enum HistoricalLogDiff {
244    /// The 8,192-slot boundary was not crossed.
245    Unchanged,
246    /// Contains the raw SSZ bytes of the new items (64 bytes each).
247    Append(Vec<u8>),
248}
249
250/// Delta representation for Phase0 pending attestations.
251#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
252pub enum AttestationsDiff {
253    /// The list did not change.
254    Unchanged,
255    /// New attestations were appended to the end of the list.
256    Append(Vec<u8>),
257    /// The list was entirely replaced (used for `previous_epoch_attestations`
258    /// when it becomes the old `current_epoch_attestations`).
259    FullReplacement(Vec<u8>),
260}
261
262#[derive(Eq, PartialEq, Debug, Clone, Default, Archive, Deserialize, Serialize)]
263pub struct BitTagVec {
264    /// Packed storage containing four 2-bit tags per byte.
265    pub data: Vec<u8>,
266    pub len: usize,
267}
268
269/// Balance is unchanged.
270pub const SET_NO_CHANGE: u8 = 0b00;
271
272/// Balance is replaced with zero.
273pub const SET_TO_ZERO: u8 = 0b10;
274
275/// Balance is reconstructed by applying a stored difference.
276pub const SET_TO_DIFF: u8 = 0b11;
277
278/// Balance is replaced with its absolute target value.
279pub const SET_TO_TARGET_VALUE: u8 = 0b01;
280
281/// Dense 2-bit tag vector.
282///
283/// Four entries are packed into each byte.
284///
285/// Each entry describes how the corresponding validator balance should be
286/// reconstructed.
287///
288/// Encoding:
289///
290/// - `00` — unchanged
291/// - `01` — absolute target value
292/// - `10` — set to zero
293/// - `11` — apply encoded difference
294impl BitTagVec {
295    pub fn new(len: usize) -> Self {
296        let bytes = len.div_ceil(4);
297        Self {
298            data: vec![0; bytes],
299            len,
300        }
301    }
302
303    /// Sets the tag at `idx`.
304    #[inline]
305    pub fn set(&mut self, idx: usize, tag: u8) {
306        let byte = idx / 4;
307        let shift = (idx % 4) * 2;
308
309        self.data[byte] |= (tag & 0b11) << shift;
310    }
311
312    /// Returns the tag stored at `idx`.
313    #[inline]
314    pub fn get(&self, idx: usize) -> u8 {
315        let byte = idx / 4;
316        let shift = (idx % 4) * 2;
317        (self.data[byte] >> shift) & 0b11
318    }
319}