Skip to main content

eth_state_diff/
lib.rs

1//! High-performance delta encoding for Ethereum consensus state.
2//!
3//! `eth_state_diff` computes compact deltas between two beacon states and
4//! efficiently reconstructs the target state by applying those deltas.
5//!
6//! The crate is designed for consensus clients, archival storage, state
7//! synchronization, and historical state reconstruction.
8//!
9//! Individual state components use specialized encodings chosen for their
10//! respective data structures, including sparse patches, circular buffer
11//! updates, packed bit vectors, and FIFO queue deltas.
12//!
13//! Deltas are designed to serialize efficiently with `rkyv`, although the
14//! library itself remains serialization-agnostic.
15
16pub mod balances;
17pub mod eth1_data_votes;
18pub mod fifo_queue;
19pub mod inactivity_scores;
20pub mod participation;
21pub mod randao_mixes;
22pub mod recent_roots;
23pub mod slashings;
24pub mod types;
25pub mod validators;
26
27use rkyv::{Archive, Deserialize, Serialize};
28
29use crate::types::{
30    BalancesDiff, Eth1DataVotesDiff, FifoQueueDiff, InactivityDiff, ParticipationDiff, RandaoDiff,
31    RootsDiff, SlashingsDiff, ValidatorsDiff,
32};
33
34/// Ethereum consensus fork supported by this delta.
35///
36/// Deltas may only be applied to states from the same fork to ensure layout
37/// compatibility.
38#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
39pub enum ForkName {
40    Phase0,
41    Altair,
42    Bellatrix,
43    Capella,
44    Deneb,
45    Electra,
46    Fulu,
47}
48
49/// Complete delta describing the transition between two beacon states.
50///
51/// Each field uses a specialized encoding optimized for the corresponding
52/// consensus data structure.
53///
54/// A `BeaconStateDelta` can be serialized, persisted, transmitted, and later
55/// applied to a compatible base state to reconstruct the target state.
56#[derive(Archive, Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
57pub struct BeaconStateDelta {
58    pub fork: ForkName,
59    pub base_slot: u64,
60    pub scalar_header: Vec<u8>,
61
62    pub balances: BalancesDiff,
63    pub previous_participation: ParticipationDiff,
64    pub validators: ValidatorsDiff,
65    pub block_roots: RootsDiff,
66    pub state_roots: RootsDiff,
67    pub randao_mixes: RandaoDiff,
68    pub slashings: SlashingsDiff,
69    pub inactivity_scores: InactivityDiff,
70
71    pub eth1_data_votes: Eth1DataVotesDiff,
72
73    pub pending_deposits: FifoQueueDiff,
74    pub pending_partial_withdrawals: FifoQueueDiff,
75    pub pending_consolidations: FifoQueueDiff,
76}
77
78/// Mutable view of a beacon state.
79///
80/// Implement this trait for your beacon-state representation to allow
81/// [`apply`] to reconstruct a target state from a [`BeaconStateDelta`].
82///
83/// The trait intentionally operates on primitive buffers and slices rather
84/// than client-specific types, allowing integration with any consensus client.
85pub trait DiffTarget {
86    fn get_fork(&self) -> ForkName;
87    fn scalar_header_mut(&mut self) -> &mut Vec<u8>;
88
89    fn balances_mut(&mut self) -> &mut Vec<u64>;
90    fn previous_participation_mut(&mut self) -> &mut Vec<u8>;
91    fn validators_mut(&mut self) -> &mut Vec<u8>;
92    fn block_roots_mut(&mut self) -> &mut [[u8; 32]];
93    fn state_roots_mut(&mut self) -> &mut [[u8; 32]];
94    fn randao_mixes_mut(&mut self) -> &mut [[u8; 32]];
95    fn slashings_mut(&mut self) -> &mut [u64];
96    fn inactivity_scores_mut(&mut self) -> &mut Vec<u64>;
97
98    fn eth1_data_votes_mut(&mut self) -> &mut Vec<u8>;
99
100    fn pending_deposits_mut(&mut self) -> &mut Vec<u8>;
101    fn pending_partial_withdrawals_mut(&mut self) -> &mut Vec<u8>;
102    fn pending_consolidations_mut(&mut self) -> &mut Vec<u8>;
103}
104
105/// Applies a previously created beacon-state delta.
106///
107/// The supplied [`DiffTarget`] is modified in place by applying each component
108/// delta to reconstruct the target state.
109///
110/// The state's fork must match the fork recorded in the delta.
111///
112/// # Panics
113///
114/// Panics if the delta was created for a different consensus fork.
115///
116/// # Complexity
117///
118/// Linear in the size of the recorded delta.
119pub fn apply<M: DiffTarget>(mut state: M, delta: &ArchivedBeaconStateDelta) -> M {
120    use rkyv::deserialize;
121
122    let delta_fork: ForkName = deserialize::<ForkName, rkyv::rancor::Error>(&delta.fork)
123        .expect("failed to deserialize fork");
124
125    let state_fork = state.get_fork();
126    assert_eq!(
127        state_fork, delta_fork,
128        "Fork mismatch: cannot apply {delta_fork:?} delta to {state_fork:?} state",
129    );
130
131    let base_slot = delta.base_slot.to_native();
132    let slots_per_epoch: u64 = 32;
133
134    *state.scalar_header_mut() = delta.scalar_header.as_slice().to_vec();
135
136    balances::apply_balances(state.balances_mut(), &delta.balances);
137    participation::apply_participation(
138        state.previous_participation_mut(),
139        &delta.previous_participation,
140    );
141    validators::apply_validators(state.validators_mut(), &delta.validators);
142
143    recent_roots::apply_roots(base_slot, state.block_roots_mut(), &delta.block_roots);
144    recent_roots::apply_roots(base_slot, state.state_roots_mut(), &delta.state_roots);
145    randao_mixes::apply_randao(
146        base_slot,
147        state.randao_mixes_mut(),
148        &delta.randao_mixes,
149        slots_per_epoch,
150    );
151    slashings::apply_slashings(state.slashings_mut(), &delta.slashings);
152    inactivity_scores::apply_inactivity(state.inactivity_scores_mut(), &delta.inactivity_scores);
153
154    eth1_data_votes::apply_eth1_votes(state.eth1_data_votes_mut(), &delta.eth1_data_votes);
155
156    fifo_queue::apply_fifo_queue(state.pending_deposits_mut(), &delta.pending_deposits, 88);
157    fifo_queue::apply_fifo_queue(
158        state.pending_partial_withdrawals_mut(),
159        &delta.pending_partial_withdrawals,
160        121,
161    );
162    fifo_queue::apply_fifo_queue(
163        state.pending_consolidations_mut(),
164        &delta.pending_consolidations,
165        169,
166    );
167
168    state
169}
170
171/// Read-only view of two beacon states.
172///
173/// Implement this trait to allow [`create`] to compute a
174/// [`BeaconStateDelta`] between two states.
175///
176/// Each method exposes the state component required by the corresponding delta
177/// encoder without imposing any storage layout on the implementation.
178pub trait DiffSource {
179    fn fork(&self) -> ForkName;
180    fn slot(&self) -> (u64, u64);
181    fn scalar_header(&self) -> Vec<u8>;
182
183    fn balances(&self) -> (&[u64], &[u64]);
184    fn previous_participation(&self) -> (&[u8], &[u8]);
185    fn validators(&self) -> (&[u8], &[u8]);
186
187    fn block_roots(&self) -> &[[u8; 32]];
188    fn state_roots(&self) -> &[[u8; 32]];
189    fn randao_mixes(&self) -> &[[u8; 32]];
190    fn slashings(&self) -> (&[u64], &[u64]);
191    fn inactivity_scores(&self) -> (&[u64], &[u64]);
192
193    fn eth1_data_votes(&self) -> (&[u8], &[u8]);
194
195    fn pending_deposits(&self) -> FifoQueueDiff;
196    fn pending_partial_withdrawals(&self) -> FifoQueueDiff;
197    fn pending_consolidations(&self) -> FifoQueueDiff;
198}
199
200/// Creates a delta between two beacon states.
201///
202/// The supplied [`DiffSource`] provides access to the base and target state
203/// components required by each specialized encoder.
204///
205/// The returned [`BeaconStateDelta`] contains only the information necessary
206/// to reconstruct the target state from the base state.
207///
208/// # Complexity
209///
210/// Linear in the size of the state components being compared.
211pub fn create<R: DiffSource>(state: &R) -> BeaconStateDelta {
212    let (base_balances, target_balances) = state.balances();
213    let (base_prev_participation, target_prev_participation) = state.previous_participation();
214    let (base_validators, target_validators) = state.validators();
215    let (base_inactivity, target_inactivity) = state.inactivity_scores();
216    let (base_slashings, target_slashings) = state.slashings();
217    let (base_eth1_data_votes, target_eth1_data_votes) = state.eth1_data_votes();
218
219    let (base_slot, target_slot) = state.slot();
220    let slots_per_epoch: u64 = 32;
221
222    BeaconStateDelta {
223        fork: state.fork(),
224        base_slot,
225        scalar_header: state.scalar_header(),
226
227        balances: balances::diff_balances(base_balances, target_balances),
228        previous_participation: participation::diff_participation(
229            base_prev_participation,
230            target_prev_participation,
231        ),
232        validators: validators::diff_validators(base_validators, target_validators),
233
234        block_roots: recent_roots::diff_roots(base_slot, target_slot, state.block_roots()),
235        state_roots: recent_roots::diff_roots(
236            base_slot,
237            base_slot + slots_per_epoch,
238            state.state_roots(),
239        ),
240        randao_mixes: randao_mixes::diff_randao(
241            base_slot,
242            base_slot + slots_per_epoch,
243            state.randao_mixes(),
244            slots_per_epoch,
245        ),
246        slashings: slashings::diff_slashings(
247            base_slot,
248            base_slot + slots_per_epoch,
249            base_slashings,
250            target_slashings,
251            slots_per_epoch,
252        ),
253        inactivity_scores: inactivity_scores::diff_inactivity(base_inactivity, target_inactivity),
254
255        eth1_data_votes: eth1_data_votes::diff_eth1_votes(
256            base_eth1_data_votes,
257            target_eth1_data_votes,
258        ),
259
260        pending_deposits: state.pending_deposits(),
261        pending_partial_withdrawals: state.pending_partial_withdrawals(),
262        pending_consolidations: state.pending_consolidations(),
263    }
264}