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}