eth_state_diff/eth1_data_votes.rs
1//! Delta encoding for the Eth1 data vote list.
2//!
3//! Eth1 data votes accumulate during an Eth1 voting period and are reset when
4//! the voting period changes. This module represents those transitions using
5//! either an append-only update or a complete replacement.
6//!
7//! The delta operates directly on the serialized SSZ representation and does
8//! not deserialize individual Eth1 data values.
9
10use crate::types::{ArchivedEth1DataVotesDiff, Eth1DataVotesDiff};
11
12/// Computes a delta between two serialized Eth1 data vote lists.
13///
14/// If the target preserves the complete serialized base list, only the bytes
15/// appended to the base are stored in [`Eth1DataVotesDiff::Append`].
16/// Otherwise, the complete target list is stored in
17/// [`Eth1DataVotesDiff::ResetAndAppend`].
18///
19/// This covers both accumulation of votes during an Eth1 voting period and
20/// replacement of the vote list when the voting period changes.
21///
22/// This function operates directly on serialized SSZ bytes and does not
23/// deserialize individual votes.
24///
25/// # Arguments
26///
27/// * `base` - Serialized SSZ representation of the current vote list.
28/// * `target` - Serialized SSZ representation of the target vote list.
29///
30/// # Returns
31///
32/// A delta representing the transition from `base` to `target`.
33///
34/// # Complexity
35///
36/// O(n), where *n* is the length of `base`, for the prefix comparison.
37/// Additional space is proportional to the selected delta payload.
38///
39/// # Example
40///
41/// ```
42/// use eth_state_diff::eth1_data_votes::diff_eth1_votes;
43/// use eth_state_diff::types::Eth1DataVotesDiff;
44///
45/// let base = b"AAAA";
46/// let target = b"AAAABBBB";
47///
48/// let delta = diff_eth1_votes(base, target);
49///
50/// assert_eq!(delta, Eth1DataVotesDiff::Append(b"BBBB".to_vec()));
51/// ```
52pub fn diff_eth1_votes(base: &[u8], target: &[u8]) -> Eth1DataVotesDiff {
53 if let Some(appended_votes) = target
54 .get(base.len()..)
55 .filter(|_| target.starts_with(base))
56 {
57 Eth1DataVotesDiff::Append(appended_votes.to_vec())
58 } else {
59 Eth1DataVotesDiff::ResetAndAppend(target.to_vec())
60 }
61}
62
63/// Applies an Eth1 data vote delta to a serialized vote list in place.
64///
65/// [`Eth1DataVotesDiff::Append`] preserves the existing bytes and appends the
66/// delta payload.
67///
68/// [`Eth1DataVotesDiff::ResetAndAppend`] clears the existing vote list before
69/// writing the replacement payload.
70///
71/// The delta is assumed to have been produced for the current base state.
72/// This function does not validate that an append delta's base bytes match
73/// the state being modified.
74///
75/// # Arguments
76///
77/// * `base` - Serialized SSZ vote list to modify.
78/// * `delta` - Archived delta to apply.
79///
80/// # Complexity
81///
82/// - [`Eth1DataVotesDiff::Append`]: O(k), where *k* is the number of appended
83/// bytes.
84/// - [`Eth1DataVotesDiff::ResetAndAppend`]: O(k), where *k* is the size of the
85/// replacement payload.
86///
87/// # Example
88///
89/// ```
90/// use eth_state_diff::eth1_data_votes::{apply_eth1_votes, diff_eth1_votes};
91/// use eth_state_diff::types::Eth1DataVotesDiff;
92///
93/// let mut base = b"AAAA".to_vec();
94/// let delta = diff_eth1_votes(&base, b"AAAABBBB");
95///
96/// // The delta is generated locally, so its contents can be applied directly.
97/// match delta {
98/// Eth1DataVotesDiff::Append(appended_votes) => {
99/// base.extend_from_slice(&appended_votes);
100/// }
101/// Eth1DataVotesDiff::ResetAndAppend(replacement) => {
102/// base.clear();
103/// base.extend_from_slice(&replacement);
104/// }
105/// }
106///
107/// assert_eq!(base, b"AAAABBBB");
108/// ```
109///
110/// [`Eth1DataVotesDiff`]: crate::types::Eth1DataVotesDiff
111pub fn apply_eth1_votes(base: &mut Vec<u8>, delta: &ArchivedEth1DataVotesDiff) {
112 match delta {
113 ArchivedEth1DataVotesDiff::Append(appended_votes) => {
114 base.extend_from_slice(appended_votes.as_slice());
115 }
116 ArchivedEth1DataVotesDiff::ResetAndAppend(replacement) => {
117 base.clear();
118 base.extend_from_slice(replacement.as_slice());
119 }
120 }
121}