1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
//! Delta encoding for Ethereum RANDAO mix buffers.
//!
//! Ethereum consensus maintains historical RANDAO mixes in a fixed-capacity
//! circular buffer. Each epoch writes one mix, with the buffer index derived
//! from the epoch number modulo the buffer capacity.
//!
//! Rather than storing the complete RANDAO buffer, this module stores only the
//! sequence of mixes needed to reconstruct the target buffer over the epochs
//! covered by a slot transition. Applying the delta replays those epoch writes
//! using the same circular-buffer indexing rule.
//!
//! The delta therefore contains no buffer indices. Indices are reconstructed
//! deterministically from the starting slot and the destination buffer's
//! capacity.
//!
//! # Representation
//!
//! [`RandaoDiff`] stores one 32-byte RANDAO mix for each epoch in the inclusive
//! range from the epoch containing `base_slot` through the epoch containing
//! `target_slot`.
//!
//! For an epoch `e` and buffer capacity `N`, the mix is stored at:
//!
//! ```text
//! buffer_index = e % N
//! ```
//!
//! During application, the same indexing rule is used starting from the epoch
//! containing `base_slot`.
//!
//! # Correctness
//!
//! The source and destination buffers must have the same capacity. The delta
//! does not store explicit buffer indices; instead, indices are reconstructed
//! from the starting epoch and the destination buffer capacity.
//!
//! Consequently, applying a delta to a buffer with a different capacity can
//! write mixes to different positions and will not reconstruct the original
//! target state.
//!
//! The caller must also provide the same `base_slot` used when generating the
//! delta. Because the delta stores only the sequence of mixes, changing the
//! starting slot changes the epochs and therefore the destination indices at
//! which those mixes are written.
//!
//! # Workflow
//!
//! The typical workflow is:
//!
//! 1. Call [`diff_randao`] with the base slot, target slot, and target RANDAO
//! buffer.
//! 2. Serialize the resulting [`RandaoDiff`] using `rkyv`.
//! 3. Store or compress the serialized delta.
//! 4. Deserialize/access the archived delta and apply it with
//! [`apply_randao`] using the same `base_slot`.
//!
//! # Complexity
//!
//! If `E` is the number of epochs covered by the transition:
//!
//! - [`diff_randao`] runs in O(E) time and uses O(E) additional space.
//! - [`apply_randao`] runs in O(E) time and uses O(1) additional space.
use crate;
/// Computes a RANDAO delta between two consensus slots.
///
/// The returned delta contains one RANDAO mix for every epoch in the inclusive
/// range from the epoch containing `base_slot` through the epoch containing
/// `target_slot`.
///
/// Each mix is read from `target_buffer` using the circular-buffer indexing
/// rule:
///
/// ```text
/// buffer_index = epoch % target_buffer.len()
/// ```
///
/// Only the mixes corresponding to the covered epochs are stored. The complete
/// target buffer is not copied.
///
/// # Arguments
///
/// * `base_slot` - Starting consensus slot. The epoch containing this slot is
/// the first epoch represented in the delta.
/// * `target_slot` - Ending consensus slot. The epoch containing this slot is
/// the final epoch represented in the delta.
/// * `target_buffer` - RANDAO mix buffer belonging to the target state.
///
/// # Returns
///
/// A [`RandaoDiff`] containing one mix for each epoch from the base epoch
/// through the target epoch, inclusive.
///
/// # Panics
///
/// Panics if `target_slot < base_slot`.
///
/// Panics if `target_buffer` is empty because circular-buffer indexing requires
/// a non-zero capacity.
///
/// # Correctness
///
/// The delta stores mixes in chronological epoch order rather than storing
/// their circular-buffer indices. During application, indices are reconstructed
/// using modulo arithmetic and the capacity of the destination buffer.
///
/// The buffer used with [`apply_randao`] must therefore have the same capacity
/// as `target_buffer`, and `apply_randao` must be given the same `base_slot`.
///
/// # Example
///
/// ```
/// use eth_state_diff::randao_mixes::diff_randao;
///
/// let base_slot = 0;
/// let target_slot = 64;
///
/// let target_buffer = vec![[0u8; 32]; 4];
///
/// let delta = diff_randao(base_slot, target_slot, &target_buffer);
///
/// // With 32 slots per epoch, slots 0 and 64 belong to epochs 0 and 2.
/// // The inclusive epoch range is therefore 0..=2.
/// assert_eq!(delta.mixes.len(), 3);
/// ```
///
/// # Complexity
///
/// If `E` is the number of epochs from the base epoch through the target
/// epoch, inclusive:
///
/// - Time: O(E)
/// - Additional space: O(E)
/// Applies a RANDAO delta to a circular mix buffer in place.
///
/// Each mix stored in `delta` is written to the destination buffer at the
/// position corresponding to its epoch. The first mix is written to the epoch
/// containing `base_slot`; each subsequent mix advances by one epoch.
///
/// The destination index is reconstructed using:
///
/// ```text
/// buffer_index = epoch % base_buffer.len()
/// ```
///
/// No allocation is performed while applying the delta.
///
/// # Arguments
///
/// * `base_slot` - Starting consensus slot corresponding to the first mix in
/// `delta`. This must be the same starting slot used by [`diff_randao`].
/// * `base_buffer` - Destination RANDAO circular buffer. It is modified in
/// place and must have the same capacity as the buffer used to generate
/// `delta`.
/// * `delta` - Archived [`RandaoDiff`] containing the mixes to replay.
///
/// # Correctness
///
/// This function is the application counterpart to [`diff_randao`].
///
/// For correct reconstruction, `base_buffer` must have the same capacity as
/// the target buffer supplied to [`diff_randao`], and `base_slot` must be the
/// same starting slot used when generating the delta.
///
/// The delta does not contain explicit buffer indices. The indices are derived
/// from the starting epoch and the buffer capacity. Changing either value
/// changes where the recorded mixes are written.
///
/// # Panics
///
/// Panics if `base_buffer` is empty because circular-buffer indexing requires
/// a non-zero capacity.
///
/// # Example
///
/// ```
/// use eth_state_diff::randao_mixes::{apply_randao, diff_randao};
/// use eth_state_diff::types::ArchivedRandaoDiff;
///
/// let base_slot = 0;
/// let target_slot = 64;
///
/// // With 32 slots per epoch, epochs 0, 1, and 2 are covered.
/// let mut target_buffer = vec![[0u8; 32]; 4];
/// target_buffer[0] = [1u8; 32];
/// target_buffer[1] = [2u8; 32];
/// target_buffer[2] = [3u8; 32];
///
/// let delta = diff_randao(base_slot, target_slot, &target_buffer);
///
/// let bytes = rkyv::to_bytes::<rkyv::rancor::Error>(&delta).unwrap();
/// let archived = unsafe {
/// rkyv::access_unchecked::<ArchivedRandaoDiff>(&bytes)
/// };
///
/// let mut reconstructed = vec![[0u8; 32]; 4];
/// apply_randao(base_slot, &mut reconstructed, archived);
///
/// assert_eq!(reconstructed, target_buffer);
/// ```
///
/// # Complexity
///
/// If `E` is the number of mixes stored in `delta`:
///
/// - Time: O(E)
/// - Additional space: O(1)